diff options
Diffstat (limited to 'drivers/media/platform/exynos4-is')
27 files changed, 1360 insertions, 1042 deletions
diff --git a/drivers/media/platform/exynos4-is/Kconfig b/drivers/media/platform/exynos4-is/Kconfig index 53ad0f08017..5dcaa0a8054 100644 --- a/drivers/media/platform/exynos4-is/Kconfig +++ b/drivers/media/platform/exynos4-is/Kconfig @@ -1,8 +1,9 @@ config VIDEO_SAMSUNG_EXYNOS4_IS bool "Samsung S5P/EXYNOS4 SoC series Camera Subsystem driver" - depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && PM_RUNTIME + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on (PLAT_S5P || ARCH_EXYNOS) + depends on OF && COMMON_CLK help Say Y here to enable camera host interface devices for Samsung S5P and EXYNOS SoC series. @@ -17,7 +18,7 @@ config VIDEO_S5P_FIMC depends on I2C select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV - select MFD_SYSCON if OF + select MFD_SYSCON select VIDEO_EXYNOS4_IS_COMMON help This is a V4L2 driver for Samsung S5P and EXYNOS4 SoC camera host @@ -29,7 +30,7 @@ config VIDEO_S5P_FIMC config VIDEO_S5P_MIPI_CSIS tristate "S5P/EXYNOS MIPI-CSI2 receiver (MIPI-CSIS) driver" depends on REGULATOR - select S5P_SETUP_MIPIPHY + select GENERIC_PHY help This is a V4L2 driver for Samsung S5P and EXYNOS4 SoC MIPI-CSI2 receiver (MIPI-CSIS) devices. @@ -64,4 +65,13 @@ config VIDEO_EXYNOS4_FIMC_IS To compile this driver as a module, choose M here: the module will be called exynos4-fimc-is. +config VIDEO_EXYNOS4_ISP_DMA_CAPTURE + bool "EXYNOS4x12 FIMC-IS ISP Direct DMA capture support" + depends on VIDEO_EXYNOS4_FIMC_IS + select VIDEO_EXYNOS4_IS_COMMON + default y + help + This option enables an additional video device node exposing a V4L2 + video capture interface for the FIMC-IS ISP raw (Bayer) capture DMA. + endif # VIDEO_SAMSUNG_EXYNOS4_IS diff --git a/drivers/media/platform/exynos4-is/Makefile b/drivers/media/platform/exynos4-is/Makefile index c2ff29ba685..eed1b185d81 100644 --- a/drivers/media/platform/exynos4-is/Makefile +++ b/drivers/media/platform/exynos4-is/Makefile @@ -6,6 +6,10 @@ exynos4-is-common-objs := common.o exynos-fimc-is-objs := fimc-is.o fimc-isp.o fimc-is-sensor.o fimc-is-regs.o exynos-fimc-is-objs += fimc-is-param.o fimc-is-errno.o fimc-is-i2c.o +ifeq ($(CONFIG_VIDEO_EXYNOS4_ISP_DMA_CAPTURE),y) +exynos-fimc-is-objs += fimc-isp-video.o +endif + obj-$(CONFIG_VIDEO_S5P_MIPI_CSIS) += s5p-csis.o obj-$(CONFIG_VIDEO_EXYNOS_FIMC_LITE) += exynos-fimc-lite.o obj-$(CONFIG_VIDEO_EXYNOS4_FIMC_IS) += exynos-fimc-is.o diff --git a/drivers/media/platform/exynos4-is/common.c b/drivers/media/platform/exynos4-is/common.c index 0ec210b4da1..0eb34ecb8ee 100644 --- a/drivers/media/platform/exynos4-is/common.c +++ b/drivers/media/platform/exynos4-is/common.c @@ -10,7 +10,7 @@ */ #include <linux/module.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> #include "common.h" /* Called with the media graph mutex held or entity->stream_count > 0. */ diff --git a/drivers/media/platform/exynos4-is/fimc-capture.c b/drivers/media/platform/exynos4-is/fimc-capture.c index fb27ff7e1e0..3d2babd5067 100644 --- a/drivers/media/platform/exynos4-is/fimc-capture.c +++ b/drivers/media/platform/exynos4-is/fimc-capture.c @@ -294,15 +294,15 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) return 0; } -static int stop_streaming(struct vb2_queue *q) +static void stop_streaming(struct vb2_queue *q) { struct fimc_ctx *ctx = q->drv_priv; struct fimc_dev *fimc = ctx->fimc_dev; if (!fimc_capture_active(fimc)) - return -EINVAL; + return; - return fimc_stop_capture(fimc, false); + fimc_stop_capture(fimc, false); } int fimc_capture_suspend(struct fimc_dev *fimc) @@ -549,7 +549,7 @@ static int fimc_capture_release(struct file *file) vc->streaming = false; } - ret = vb2_fop_release(file); + ret = _vb2_fop_release(file, NULL); if (close) { clear_bit(ST_CAPT_BUSY, &fimc->state); @@ -1782,7 +1782,7 @@ static int fimc_register_capture_device(struct fimc_dev *fimc, q->ops = &fimc_capture_qops; q->mem_ops = &vb2_dma_contig_memops; q->buf_struct_size = sizeof(struct fimc_vid_buffer); - q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->lock = &fimc->lock; ret = vb2_queue_init(q); diff --git a/drivers/media/platform/exynos4-is/fimc-core.c b/drivers/media/platform/exynos4-is/fimc-core.c index 6489c5160ee..b70fd996d79 100644 --- a/drivers/media/platform/exynos4-is/fimc-core.c +++ b/drivers/media/platform/exynos4-is/fimc-core.c @@ -56,8 +56,8 @@ static struct fimc_fmt fimc_formats[] = { .colplanes = 1, .flags = FMT_FLAGS_M2M, }, { - .name = "ARGB8888, 32 bpp", - .fourcc = V4L2_PIX_FMT_RGB32, + .name = "BGRA8888, 32 bpp", + .fourcc = V4L2_PIX_FMT_BGR32, .depth = { 32 }, .color = FIMC_FMT_RGB888, .memplanes = 1, @@ -122,7 +122,7 @@ static struct fimc_fmt fimc_formats[] = { }, { .name = "YUV 4:2:2 planar, Y/Cb/Cr", .fourcc = V4L2_PIX_FMT_YUV422P, - .depth = { 12 }, + .depth = { 16 }, .color = FIMC_FMT_YCBYCR422, .memplanes = 1, .colplanes = 3, @@ -450,7 +450,7 @@ void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f) bool pix_hoff = ctx->fimc_dev->drv_data->dma_pix_hoff; u32 i, depth = 0; - for (i = 0; i < f->fmt->colplanes; i++) + for (i = 0; i < f->fmt->memplanes; i++) depth += f->fmt->depth[i]; f->dma_offset.y_h = f->offs_h; @@ -998,48 +998,53 @@ static int fimc_probe(struct platform_device *pdev) ret = devm_request_irq(dev, res->start, fimc_irq_handler, 0, dev_name(dev), fimc); - if (ret) { + if (ret < 0) { dev_err(dev, "failed to install irq (%d)\n", ret); - goto err_clk; + goto err_sclk; } ret = fimc_initialize_capture_subdev(fimc); - if (ret) - goto err_clk; + if (ret < 0) + goto err_sclk; platform_set_drvdata(pdev, fimc); pm_runtime_enable(dev); - ret = pm_runtime_get_sync(dev); - if (ret < 0) - goto err_sd; + + if (!pm_runtime_enabled(dev)) { + ret = clk_enable(fimc->clock[CLK_GATE]); + if (ret < 0) + goto err_sd; + } + /* Initialize contiguous memory allocator */ fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(fimc->alloc_ctx)) { ret = PTR_ERR(fimc->alloc_ctx); - goto err_pm; + goto err_gclk; } dev_dbg(dev, "FIMC.%d registered successfully\n", fimc->id); - - pm_runtime_put(dev); return 0; -err_pm: - pm_runtime_put(dev); + +err_gclk: + if (!pm_runtime_enabled(dev)) + clk_disable(fimc->clock[CLK_GATE]); err_sd: fimc_unregister_capture_subdev(fimc); -err_clk: +err_sclk: clk_disable(fimc->clock[CLK_BUS]); fimc_clk_put(fimc); return ret; } +#ifdef CONFIG_PM_RUNTIME static int fimc_runtime_resume(struct device *dev) { struct fimc_dev *fimc = dev_get_drvdata(dev); dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); - /* Enable clocks and perform basic initalization */ + /* Enable clocks and perform basic initialization */ clk_enable(fimc->clock[CLK_GATE]); fimc_hw_reset(fimc); @@ -1065,6 +1070,7 @@ static int fimc_runtime_suspend(struct device *dev) dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); return ret; } +#endif #ifdef CONFIG_PM_SLEEP static int fimc_resume(struct device *dev) @@ -1110,6 +1116,8 @@ static int fimc_remove(struct platform_device *pdev) struct fimc_dev *fimc = platform_get_drvdata(pdev); pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + clk_disable(fimc->clock[CLK_GATE]); pm_runtime_set_suspended(&pdev->dev); fimc_unregister_capture_subdev(fimc); diff --git a/drivers/media/platform/exynos4-is/fimc-core.h b/drivers/media/platform/exynos4-is/fimc-core.h index 3d376faec77..6c75c6ced1f 100644 --- a/drivers/media/platform/exynos4-is/fimc-core.h +++ b/drivers/media/platform/exynos4-is/fimc-core.h @@ -27,7 +27,7 @@ #include <media/v4l2-device.h> #include <media/v4l2-mem2mem.h> #include <media/v4l2-mediabus.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> #define dbg(fmt, args...) \ pr_debug("%s:%d: " fmt "\n", __func__, __LINE__, ##args) @@ -481,7 +481,6 @@ struct fimc_ctrls { * @flags: additional flags for image conversion * @state: flags to keep track of user configuration * @fimc_dev: the FIMC device this context applies to - * @m2m_ctx: memory-to-memory device context * @fh: v4l2 file handle * @ctrls: v4l2 controls structure */ @@ -502,7 +501,6 @@ struct fimc_ctx { u32 flags; u32 state; struct fimc_dev *fimc_dev; - struct v4l2_m2m_ctx *m2m_ctx; struct v4l2_fh fh; struct fimc_ctrls ctrls; }; diff --git a/drivers/media/platform/exynos4-is/fimc-is-i2c.c b/drivers/media/platform/exynos4-is/fimc-is-i2c.c index 617a798d923..371cad4fcce 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-i2c.c +++ b/drivers/media/platform/exynos4-is/fimc-is-i2c.c @@ -12,7 +12,7 @@ #include <linux/clk.h> #include <linux/module.h> -#include <linux/of_i2c.h> +#include <linux/i2c.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/slab.h> @@ -67,8 +67,6 @@ static int fimc_is_i2c_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); pm_runtime_enable(&i2c_adap->dev); - of_i2c_register_devices(i2c_adap); - return 0; } @@ -83,21 +81,46 @@ static int fimc_is_i2c_remove(struct platform_device *pdev) return 0; } -static int fimc_is_i2c_suspend(struct device *dev) +#if defined(CONFIG_PM_RUNTIME) || defined(CONFIG_PM_SLEEP) +static int fimc_is_i2c_runtime_suspend(struct device *dev) { struct fimc_is_i2c *isp_i2c = dev_get_drvdata(dev); + clk_disable_unprepare(isp_i2c->clock); return 0; } -static int fimc_is_i2c_resume(struct device *dev) +static int fimc_is_i2c_runtime_resume(struct device *dev) { struct fimc_is_i2c *isp_i2c = dev_get_drvdata(dev); + return clk_prepare_enable(isp_i2c->clock); } +#endif + +#ifdef CONFIG_PM_SLEEP +static int fimc_is_i2c_suspend(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return fimc_is_i2c_runtime_suspend(dev); +} + +static int fimc_is_i2c_resume(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return fimc_is_i2c_runtime_resume(dev); +} +#endif -static UNIVERSAL_DEV_PM_OPS(fimc_is_i2c_pm_ops, fimc_is_i2c_suspend, - fimc_is_i2c_resume, NULL); +static struct dev_pm_ops fimc_is_i2c_pm_ops = { + SET_RUNTIME_PM_OPS(fimc_is_i2c_runtime_suspend, + fimc_is_i2c_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fimc_is_i2c_suspend, fimc_is_i2c_resume) +}; static const struct of_device_id fimc_is_i2c_of_match[] = { { .compatible = FIMC_IS_I2C_COMPATIBLE }, diff --git a/drivers/media/platform/exynos4-is/fimc-is-param.c b/drivers/media/platform/exynos4-is/fimc-is-param.c index c7e7f694c6e..bf1465d1bf6 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-param.c +++ b/drivers/media/platform/exynos4-is/fimc-is-param.c @@ -287,7 +287,7 @@ void __is_set_sensor(struct fimc_is *is, int fps) fimc_is_set_param_bit(is, PARAM_ISP_OTF_INPUT); } -void __is_set_init_isp_aa(struct fimc_is *is) +static void __maybe_unused __is_set_init_isp_aa(struct fimc_is *is) { struct isp_param *isp; diff --git a/drivers/media/platform/exynos4-is/fimc-is-param.h b/drivers/media/platform/exynos4-is/fimc-is-param.h index f9358c27ae2..8e31f764277 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-param.h +++ b/drivers/media/platform/exynos4-is/fimc-is-param.h @@ -911,6 +911,10 @@ struct is_region { u32 shared[MAX_SHARED_COUNT]; } __packed; +/* Offset to the ISP DMA2 output buffer address array. */ +#define DMA2_OUTPUT_ADDR_ARRAY_OFFS \ + (offsetof(struct is_region, shared) + 32 * sizeof(u32)) + struct is_debug_frame_descriptor { u32 sensor_frame_time; u32 sensor_exposure_time; @@ -988,6 +992,7 @@ struct sensor_open_extended { struct fimc_is; int fimc_is_hw_get_sensor_max_framerate(struct fimc_is *is); +int __fimc_is_hw_update_param(struct fimc_is *is, u32 offset); void fimc_is_set_initial_params(struct fimc_is *is); unsigned int __get_pending_param_count(struct fimc_is *is); diff --git a/drivers/media/platform/exynos4-is/fimc-is-regs.c b/drivers/media/platform/exynos4-is/fimc-is-regs.c index 63c68ec7cfa..cfe4406a83f 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-regs.c +++ b/drivers/media/platform/exynos4-is/fimc-is-regs.c @@ -33,47 +33,23 @@ void fimc_is_hw_set_intgr0_gd0(struct fimc_is *is) mcuctl_write(INTGR0_INTGD(0), is, MCUCTL_REG_INTGR0); } -int fimc_is_hw_wait_intsr0_intsd0(struct fimc_is *is) -{ - unsigned int timeout = 2000; - u32 cfg, status; - - cfg = mcuctl_read(is, MCUCTL_REG_INTSR0); - status = INTSR0_GET_INTSD(0, cfg); - - while (status) { - cfg = mcuctl_read(is, MCUCTL_REG_INTSR0); - status = INTSR0_GET_INTSD(0, cfg); - if (timeout == 0) { - dev_warn(&is->pdev->dev, "%s timeout\n", - __func__); - return -ETIME; - } - timeout--; - udelay(1); - } - return 0; -} - int fimc_is_hw_wait_intmsr0_intmsd0(struct fimc_is *is) { unsigned int timeout = 2000; u32 cfg, status; - cfg = mcuctl_read(is, MCUCTL_REG_INTMSR0); - status = INTMSR0_GET_INTMSD(0, cfg); - - while (status) { + do { cfg = mcuctl_read(is, MCUCTL_REG_INTMSR0); status = INTMSR0_GET_INTMSD(0, cfg); - if (timeout == 0) { + + if (--timeout == 0) { dev_warn(&is->pdev->dev, "%s timeout\n", __func__); - return -ETIME; + return -ETIMEDOUT; } - timeout--; udelay(1); - } + } while (status != 0); + return 0; } @@ -96,7 +72,7 @@ int fimc_is_hw_set_param(struct fimc_is *is) return 0; } -int fimc_is_hw_set_tune(struct fimc_is *is) +static int __maybe_unused fimc_is_hw_set_tune(struct fimc_is *is) { fimc_is_hw_wait_intmsr0_intmsd0(is); @@ -129,6 +105,20 @@ int fimc_is_hw_get_params(struct fimc_is *is, unsigned int num_args) return 0; } +void fimc_is_hw_set_isp_buf_mask(struct fimc_is *is, unsigned int mask) +{ + if (hweight32(mask) == 1) { + dev_err(&is->pdev->dev, "%s(): not enough buffers (mask %#x)\n", + __func__, mask); + return; + } + + if (mcuctl_read(is, MCUCTL_REG_ISSR(23)) != 0) + dev_dbg(&is->pdev->dev, "non-zero DMA buffer mask\n"); + + mcuctl_write(mask, is, MCUCTL_REG_ISSR(23)); +} + void fimc_is_hw_set_sensor_num(struct fimc_is *is) { pr_debug("setting sensor index to: %d\n", is->sensor_index); @@ -136,7 +126,7 @@ void fimc_is_hw_set_sensor_num(struct fimc_is *is) mcuctl_write(IH_REPLY_DONE, is, MCUCTL_REG_ISSR(0)); mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); mcuctl_write(IHC_GET_SENSOR_NUM, is, MCUCTL_REG_ISSR(2)); - mcuctl_write(FIMC_IS_SENSOR_NUM, is, MCUCTL_REG_ISSR(3)); + mcuctl_write(FIMC_IS_SENSORS_NUM, is, MCUCTL_REG_ISSR(3)); } void fimc_is_hw_close_sensor(struct fimc_is *is, unsigned int index) @@ -236,7 +226,7 @@ int fimc_is_itf_mode_change(struct fimc_is *is) fimc_is_hw_change_mode(is); ret = fimc_is_wait_event(is, IS_ST_CHANGE_MODE, 1, FIMC_IS_CONFIG_TIMEOUT); - if (!ret < 0) + if (ret < 0) dev_err(&is->pdev->dev, "%s(): mode change (%d) timeout\n", __func__, is->config_index); return ret; diff --git a/drivers/media/platform/exynos4-is/fimc-is-regs.h b/drivers/media/platform/exynos4-is/fimc-is-regs.h index 5fa2fda4674..141e5ddadbe 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-regs.h +++ b/drivers/media/platform/exynos4-is/fimc-is-regs.h @@ -145,9 +145,9 @@ void fimc_is_fw_clear_irq2(struct fimc_is *is); int fimc_is_hw_get_params(struct fimc_is *is, unsigned int num); void fimc_is_hw_set_intgr0_gd0(struct fimc_is *is); -int fimc_is_hw_wait_intsr0_intsd0(struct fimc_is *is); int fimc_is_hw_wait_intmsr0_intmsd0(struct fimc_is *is); void fimc_is_hw_set_sensor_num(struct fimc_is *is); +void fimc_is_hw_set_isp_buf_mask(struct fimc_is *is, unsigned int mask); void fimc_is_hw_stream_on(struct fimc_is *is); void fimc_is_hw_stream_off(struct fimc_is *is); int fimc_is_hw_set_param(struct fimc_is *is); diff --git a/drivers/media/platform/exynos4-is/fimc-is-sensor.c b/drivers/media/platform/exynos4-is/fimc-is-sensor.c index 6647421e5d3..10e82e21b5d 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-sensor.c +++ b/drivers/media/platform/exynos4-is/fimc-is-sensor.c @@ -2,276 +2,21 @@ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver * * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> * * 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/device.h> -#include <linux/errno.h> -#include <linux/gpio.h> -#include <linux/i2c.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/of_gpio.h> -#include <linux/pm_runtime.h> -#include <linux/regulator/consumer.h> -#include <linux/slab.h> -#include <media/v4l2-subdev.h> -#include "fimc-is.h" #include "fimc-is-sensor.h" -#define DRIVER_NAME "FIMC-IS-SENSOR" - -static const char * const sensor_supply_names[] = { - "svdda", - "svddio", -}; - -static const struct v4l2_mbus_framefmt fimc_is_sensor_formats[] = { - { - .code = V4L2_MBUS_FMT_SGRBG10_1X10, - .colorspace = V4L2_COLORSPACE_SRGB, - .field = V4L2_FIELD_NONE, - } -}; - -static const struct v4l2_mbus_framefmt *find_sensor_format( - struct v4l2_mbus_framefmt *mf) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(fimc_is_sensor_formats); i++) - if (mf->code == fimc_is_sensor_formats[i].code) - return &fimc_is_sensor_formats[i]; - - return &fimc_is_sensor_formats[0]; -} - -static int fimc_is_sensor_enum_mbus_code(struct v4l2_subdev *sd, - struct v4l2_subdev_fh *fh, - struct v4l2_subdev_mbus_code_enum *code) -{ - if (code->index >= ARRAY_SIZE(fimc_is_sensor_formats)) - return -EINVAL; - - code->code = fimc_is_sensor_formats[code->index].code; - return 0; -} - -static void fimc_is_sensor_try_format(struct fimc_is_sensor *sensor, - struct v4l2_mbus_framefmt *mf) -{ - const struct sensor_drv_data *dd = sensor->drvdata; - const struct v4l2_mbus_framefmt *fmt; - - fmt = find_sensor_format(mf); - mf->code = fmt->code; - v4l_bound_align_image(&mf->width, 16 + 8, dd->width, 0, - &mf->height, 12 + 8, dd->height, 0, 0); -} - -static struct v4l2_mbus_framefmt *__fimc_is_sensor_get_format( - struct fimc_is_sensor *sensor, struct v4l2_subdev_fh *fh, - u32 pad, enum v4l2_subdev_format_whence which) -{ - if (which == V4L2_SUBDEV_FORMAT_TRY) - return fh ? v4l2_subdev_get_try_format(fh, pad) : NULL; - - return &sensor->format; -} - -static int fimc_is_sensor_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_fh *fh, - struct v4l2_subdev_format *fmt) -{ - struct fimc_is_sensor *sensor = sd_to_fimc_is_sensor(sd); - struct v4l2_mbus_framefmt *mf; - - fimc_is_sensor_try_format(sensor, &fmt->format); - - mf = __fimc_is_sensor_get_format(sensor, fh, fmt->pad, fmt->which); - if (mf) { - mutex_lock(&sensor->lock); - if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) - *mf = fmt->format; - mutex_unlock(&sensor->lock); - } - return 0; -} - -static int fimc_is_sensor_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_fh *fh, - struct v4l2_subdev_format *fmt) -{ - struct fimc_is_sensor *sensor = sd_to_fimc_is_sensor(sd); - struct v4l2_mbus_framefmt *mf; - - mf = __fimc_is_sensor_get_format(sensor, fh, fmt->pad, fmt->which); - - mutex_lock(&sensor->lock); - fmt->format = *mf; - mutex_unlock(&sensor->lock); - return 0; -} - -static struct v4l2_subdev_pad_ops fimc_is_sensor_pad_ops = { - .enum_mbus_code = fimc_is_sensor_enum_mbus_code, - .get_fmt = fimc_is_sensor_get_fmt, - .set_fmt = fimc_is_sensor_set_fmt, -}; - -static int fimc_is_sensor_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(fh, 0); - - *format = fimc_is_sensor_formats[0]; - format->width = FIMC_IS_SENSOR_DEF_PIX_WIDTH; - format->height = FIMC_IS_SENSOR_DEF_PIX_HEIGHT; - - return 0; -} - -static const struct v4l2_subdev_internal_ops fimc_is_sensor_sd_internal_ops = { - .open = fimc_is_sensor_open, -}; - -static int fimc_is_sensor_s_power(struct v4l2_subdev *sd, int on) -{ - struct fimc_is_sensor *sensor = sd_to_fimc_is_sensor(sd); - int gpio = sensor->gpio_reset; - int ret; - - if (on) { - ret = pm_runtime_get(sensor->dev); - if (ret < 0) - return ret; - - ret = regulator_bulk_enable(SENSOR_NUM_SUPPLIES, - sensor->supplies); - if (ret < 0) { - pm_runtime_put(sensor->dev); - return ret; - } - if (gpio_is_valid(gpio)) { - gpio_set_value(gpio, 1); - usleep_range(600, 800); - gpio_set_value(gpio, 0); - usleep_range(10000, 11000); - gpio_set_value(gpio, 1); - } - - /* A delay needed for the sensor initialization. */ - msleep(20); - } else { - if (gpio_is_valid(gpio)) - gpio_set_value(gpio, 0); - - ret = regulator_bulk_disable(SENSOR_NUM_SUPPLIES, - sensor->supplies); - if (!ret) - pm_runtime_put(sensor->dev); - } - - pr_info("%s:%d: on: %d, ret: %d\n", __func__, __LINE__, on, ret); - - return ret; -} - -static struct v4l2_subdev_core_ops fimc_is_sensor_core_ops = { - .s_power = fimc_is_sensor_s_power, -}; - -static struct v4l2_subdev_ops fimc_is_sensor_subdev_ops = { - .core = &fimc_is_sensor_core_ops, - .pad = &fimc_is_sensor_pad_ops, -}; - -static const struct of_device_id fimc_is_sensor_of_match[]; - -static int fimc_is_sensor_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct device *dev = &client->dev; - struct fimc_is_sensor *sensor; - const struct of_device_id *of_id; - struct v4l2_subdev *sd; - int gpio, i, ret; - - sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); - if (!sensor) - return -ENOMEM; - - mutex_init(&sensor->lock); - sensor->gpio_reset = -EINVAL; - - gpio = of_get_gpio_flags(dev->of_node, 0, NULL); - if (gpio_is_valid(gpio)) { - ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_LOW, - DRIVER_NAME); - if (ret < 0) - return ret; - } - sensor->gpio_reset = gpio; - - for (i = 0; i < SENSOR_NUM_SUPPLIES; i++) - sensor->supplies[i].supply = sensor_supply_names[i]; - - ret = devm_regulator_bulk_get(&client->dev, SENSOR_NUM_SUPPLIES, - sensor->supplies); - if (ret < 0) - return ret; - - of_id = of_match_node(fimc_is_sensor_of_match, dev->of_node); - if (!of_id) - return -ENODEV; - - sensor->drvdata = of_id->data; - sensor->dev = dev; - - sd = &sensor->subdev; - v4l2_i2c_subdev_init(sd, client, &fimc_is_sensor_subdev_ops); - snprintf(sd->name, sizeof(sd->name), sensor->drvdata->subdev_name); - sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - - sensor->format.code = fimc_is_sensor_formats[0].code; - sensor->format.width = FIMC_IS_SENSOR_DEF_PIX_WIDTH; - sensor->format.height = FIMC_IS_SENSOR_DEF_PIX_HEIGHT; - - sensor->pad.flags = MEDIA_PAD_FL_SOURCE; - ret = media_entity_init(&sd->entity, 1, &sensor->pad, 0); - if (ret < 0) - return ret; - - pm_runtime_no_callbacks(dev); - pm_runtime_enable(dev); - - return ret; -} - -static int fimc_is_sensor_remove(struct i2c_client *client) -{ - struct v4l2_subdev *sd = i2c_get_clientdata(client); - media_entity_cleanup(&sd->entity); - return 0; -} - -static const struct i2c_device_id fimc_is_sensor_ids[] = { - { } -}; - static const struct sensor_drv_data s5k6a3_drvdata = { .id = FIMC_IS_SENSOR_ID_S5K6A3, - .subdev_name = "S5K6A3", - .width = S5K6A3_SENSOR_WIDTH, - .height = S5K6A3_SENSOR_HEIGHT, + .open_timeout = S5K6A3_OPEN_TIMEOUT, }; -static const struct of_device_id fimc_is_sensor_of_match[] = { +static const struct of_device_id fimc_is_sensor_of_ids[] = { { .compatible = "samsung,s5k6a3", .data = &s5k6a3_drvdata, @@ -279,27 +24,11 @@ static const struct of_device_id fimc_is_sensor_of_match[] = { { } }; -static struct i2c_driver fimc_is_sensor_driver = { - .driver = { - .of_match_table = fimc_is_sensor_of_match, - .name = DRIVER_NAME, - .owner = THIS_MODULE, - }, - .probe = fimc_is_sensor_probe, - .remove = fimc_is_sensor_remove, - .id_table = fimc_is_sensor_ids, -}; - -int fimc_is_register_sensor_driver(void) +const struct sensor_drv_data *fimc_is_sensor_get_drvdata( + struct device_node *node) { - return i2c_add_driver(&fimc_is_sensor_driver); -} + const struct of_device_id *of_id; -void fimc_is_unregister_sensor_driver(void) -{ - i2c_del_driver(&fimc_is_sensor_driver); + of_id = of_match_node(fimc_is_sensor_of_ids, node); + return of_id ? of_id->data : NULL; } - -MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); -MODULE_DESCRIPTION("Exynos4x12 FIMC-IS image sensor subdev driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/exynos4-is/fimc-is-sensor.h b/drivers/media/platform/exynos4-is/fimc-is-sensor.h index 6036d49a6c6..173ccffa4bc 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-sensor.h +++ b/drivers/media/platform/exynos4-is/fimc-is-sensor.h @@ -13,24 +13,13 @@ #ifndef FIMC_IS_SENSOR_H_ #define FIMC_IS_SENSOR_H_ -#include <linux/clk.h> -#include <linux/device.h> -#include <linux/kernel.h> -#include <linux/platform_device.h> -#include <linux/regulator/consumer.h> -#include <linux/videodev2.h> -#include <media/v4l2-subdev.h> - -#define FIMC_IS_SENSOR_OPEN_TIMEOUT 2000 /* ms */ - -#define FIMC_IS_SENSOR_DEF_PIX_WIDTH 1296 -#define FIMC_IS_SENSOR_DEF_PIX_HEIGHT 732 +#include <linux/of.h> +#include <linux/types.h> +#define S5K6A3_OPEN_TIMEOUT 2000 /* ms */ #define S5K6A3_SENSOR_WIDTH 1392 #define S5K6A3_SENSOR_HEIGHT 1392 -#define SENSOR_NUM_SUPPLIES 2 - enum fimc_is_sensor_id { FIMC_IS_SENSOR_ID_S5K3H2 = 1, FIMC_IS_SENSOR_ID_S5K6A3, @@ -45,45 +34,23 @@ enum fimc_is_sensor_id { struct sensor_drv_data { enum fimc_is_sensor_id id; - const char * const subdev_name; - unsigned int width; - unsigned int height; + /* sensor open timeout in ms */ + unsigned short open_timeout; }; /** * struct fimc_is_sensor - fimc-is sensor data structure - * @dev: pointer to this I2C client device structure - * @subdev: the image sensor's v4l2 subdev - * @pad: subdev media source pad - * @supplies: image sensor's voltage regulator supplies - * @gpio_reset: GPIO connected to the sensor's reset pin * @drvdata: a pointer to the sensor's parameters data structure * @i2c_bus: ISP I2C bus index (0...1) * @test_pattern: true to enable video test pattern - * @lock: mutex protecting the structure's members below - * @format: media bus format at the sensor's source pad */ struct fimc_is_sensor { - struct device *dev; - struct v4l2_subdev subdev; - struct media_pad pad; - struct regulator_bulk_data supplies[SENSOR_NUM_SUPPLIES]; - int gpio_reset; const struct sensor_drv_data *drvdata; unsigned int i2c_bus; - bool test_pattern; - - struct mutex lock; - struct v4l2_mbus_framefmt format; + u8 test_pattern; }; -static inline -struct fimc_is_sensor *sd_to_fimc_is_sensor(struct v4l2_subdev *sd) -{ - return container_of(sd, struct fimc_is_sensor, subdev); -} - -int fimc_is_register_sensor_driver(void); -void fimc_is_unregister_sensor_driver(void); +const struct sensor_drv_data *fimc_is_sensor_get_drvdata( + struct device_node *node); #endif /* FIMC_IS_SENSOR_H_ */ diff --git a/drivers/media/platform/exynos4-is/fimc-is.c b/drivers/media/platform/exynos4-is/fimc-is.c index 967f6a93934..5476dce3ad2 100644 --- a/drivers/media/platform/exynos4-is/fimc-is.c +++ b/drivers/media/platform/exynos4-is/fimc-is.c @@ -21,16 +21,16 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_i2c.h> +#include <linux/i2c.h> #include <linux/of_irq.h> #include <linux/of_address.h> +#include <linux/of_graph.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/types.h> #include <linux/videodev2.h> -#include <media/v4l2-of.h> #include <media/videobuf2-dma-contig.h> #include "media-dev.h" @@ -161,78 +161,69 @@ static void fimc_is_disable_clocks(struct fimc_is *is) } } -static int fimc_is_parse_sensor_config(struct fimc_is_sensor *sensor, - struct device_node *np) +static int fimc_is_parse_sensor_config(struct fimc_is *is, unsigned int index, + struct device_node *node) { + struct fimc_is_sensor *sensor = &is->sensor[index]; u32 tmp = 0; int ret; - np = v4l2_of_get_next_endpoint(np, NULL); - if (!np) + sensor->drvdata = fimc_is_sensor_get_drvdata(node); + if (!sensor->drvdata) { + dev_err(&is->pdev->dev, "no driver data found for: %s\n", + node->full_name); + return -EINVAL; + } + + node = of_graph_get_next_endpoint(node, NULL); + if (!node) return -ENXIO; - np = v4l2_of_get_remote_port(np); - if (!np) + + node = of_graph_get_remote_port(node); + if (!node) return -ENXIO; /* Use MIPI-CSIS channel id to determine the ISP I2C bus index. */ - ret = of_property_read_u32(np, "reg", &tmp); - sensor->i2c_bus = tmp - FIMC_INPUT_MIPI_CSI2_0; + ret = of_property_read_u32(node, "reg", &tmp); + if (ret < 0) { + dev_err(&is->pdev->dev, "reg property not found at: %s\n", + node->full_name); + return ret; + } - return ret; + sensor->i2c_bus = tmp - FIMC_INPUT_MIPI_CSI2_0; + return 0; } static int fimc_is_register_subdevs(struct fimc_is *is) { - struct device_node *adapter, *child; - int ret; + struct device_node *i2c_bus, *child; + int ret, index = 0; ret = fimc_isp_subdev_create(&is->isp); if (ret < 0) return ret; - for_each_compatible_node(adapter, NULL, FIMC_IS_I2C_COMPATIBLE) { - if (!of_find_device_by_node(adapter)) { - of_node_put(adapter); - return -EPROBE_DEFER; - } - - for_each_available_child_of_node(adapter, child) { - struct i2c_client *client; - struct v4l2_subdev *sd; - - client = of_find_i2c_device_by_node(child); - if (!client) - goto e_retry; - - sd = i2c_get_clientdata(client); - if (!sd) - goto e_retry; + /* Initialize memory allocator context for the ISP DMA. */ + is->isp.alloc_ctx = is->alloc_ctx; - /* FIXME: Add support for multiple sensors. */ - if (WARN_ON(is->sensor)) - continue; + for_each_compatible_node(i2c_bus, NULL, FIMC_IS_I2C_COMPATIBLE) { + for_each_available_child_of_node(i2c_bus, child) { + ret = fimc_is_parse_sensor_config(is, index, child); - is->sensor = sd_to_fimc_is_sensor(sd); - - if (fimc_is_parse_sensor_config(is->sensor, child)) { - dev_warn(&is->pdev->dev, "DT parse error: %s\n", - child->full_name); + if (ret < 0 || index >= FIMC_IS_SENSORS_NUM) { + of_node_put(child); + return ret; } - pr_debug("%s(): registered subdev: %p\n", - __func__, sd->name); + index++; } } return 0; - -e_retry: - of_node_put(child); - return -EPROBE_DEFER; } static int fimc_is_unregister_subdevs(struct fimc_is *is) { fimc_isp_subdev_destroy(&is->isp); - is->sensor = NULL; return 0; } @@ -376,6 +367,9 @@ static void fimc_is_free_cpu_memory(struct fimc_is *is) { struct device *dev = &is->pdev->dev; + if (is->memory.vaddr == NULL) + return; + dma_free_coherent(dev, is->memory.size, is->memory.vaddr, is->memory.paddr); } @@ -647,7 +641,7 @@ static int fimc_is_hw_open_sensor(struct fimc_is *is, fimc_is_hw_set_intgr0_gd0(is); return fimc_is_wait_event(is, IS_ST_OPEN_SENSOR, 1, - FIMC_IS_SENSOR_OPEN_TIMEOUT); + sensor->drvdata->open_timeout); } @@ -661,8 +655,8 @@ int fimc_is_hw_initialize(struct fimc_is *is) u32 prev_id; int i, ret; - /* Sensor initialization. */ - ret = fimc_is_hw_open_sensor(is, is->sensor); + /* Sensor initialization. Only one sensor is currently supported. */ + ret = fimc_is_hw_open_sensor(is, &is->sensor[0]); if (ret < 0) return ret; @@ -781,6 +775,9 @@ static int fimc_is_debugfs_create(struct fimc_is *is) return is->debugfs_entry == NULL ? -EIO : 0; } +static int fimc_is_runtime_resume(struct device *dev); +static int fimc_is_runtime_suspend(struct device *dev); + static int fimc_is_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -835,14 +832,20 @@ static int fimc_is_probe(struct platform_device *pdev) } pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = fimc_is_runtime_resume(dev); + if (ret < 0) + goto err_irq; + } + ret = pm_runtime_get_sync(dev); if (ret < 0) - goto err_irq; + goto err_pm; is->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(is->alloc_ctx)) { ret = PTR_ERR(is->alloc_ctx); - goto err_irq; + goto err_pm; } /* * Register FIMC-IS V4L2 subdevs to this driver. The video nodes @@ -867,10 +870,13 @@ static int fimc_is_probe(struct platform_device *pdev) err_dfs: fimc_is_debugfs_remove(is); -err_vb: - vb2_dma_contig_cleanup_ctx(is->alloc_ctx); err_sd: fimc_is_unregister_subdevs(is); +err_vb: + vb2_dma_contig_cleanup_ctx(is->alloc_ctx); +err_pm: + if (!pm_runtime_enabled(dev)) + fimc_is_runtime_suspend(dev); err_irq: free_irq(is->irq, is); err_clk: @@ -919,10 +925,13 @@ static int fimc_is_suspend(struct device *dev) static int fimc_is_remove(struct platform_device *pdev) { - struct fimc_is *is = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + struct fimc_is *is = dev_get_drvdata(dev); - pm_runtime_disable(&pdev->dev); - pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + if (!pm_runtime_status_suspended(dev)) + fimc_is_runtime_suspend(dev); free_irq(is->irq, is); fimc_is_unregister_subdevs(is); vb2_dma_contig_cleanup_ctx(is->alloc_ctx); @@ -962,27 +971,20 @@ static int fimc_is_module_init(void) { int ret; - ret = fimc_is_register_sensor_driver(); - if (ret < 0) - return ret; - ret = fimc_is_register_i2c_driver(); if (ret < 0) - goto err_sens; + return ret; ret = platform_driver_register(&fimc_is_driver); - if (!ret) - return ret; - fimc_is_unregister_i2c_driver(); -err_sens: - fimc_is_unregister_sensor_driver(); + if (ret < 0) + fimc_is_unregister_i2c_driver(); + return ret; } static void fimc_is_module_exit(void) { - fimc_is_unregister_sensor_driver(); fimc_is_unregister_i2c_driver(); platform_driver_unregister(&fimc_is_driver); } @@ -993,3 +995,4 @@ module_exit(fimc_is_module_exit); MODULE_ALIAS("platform:" FIMC_IS_DRV_NAME); MODULE_AUTHOR("Younghwan Joo <yhwan.joo@samsung.com>"); MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/exynos4-is/fimc-is.h b/drivers/media/platform/exynos4-is/fimc-is.h index 61bb0127e19..e0be691af2d 100644 --- a/drivers/media/platform/exynos4-is/fimc-is.h +++ b/drivers/media/platform/exynos4-is/fimc-is.h @@ -39,7 +39,7 @@ #define FIMC_IS_FW_LOAD_TIMEOUT 1000 /* ms */ #define FIMC_IS_POWER_ON_TIMEOUT 1000 /* us */ -#define FIMC_IS_SENSOR_NUM 2 +#define FIMC_IS_SENSORS_NUM 2 /* Memory definitions */ #define FIMC_IS_CPU_MEM_SIZE (0xa00000) @@ -253,7 +253,7 @@ struct fimc_is { struct firmware *f_w; struct fimc_isp isp; - struct fimc_is_sensor *sensor; + struct fimc_is_sensor sensor[FIMC_IS_SENSORS_NUM]; struct fimc_is_setfile setfile; struct vb2_alloc_ctx *alloc_ctx; @@ -292,6 +292,11 @@ static inline struct fimc_is *fimc_isp_to_is(struct fimc_isp *isp) return container_of(isp, struct fimc_is, isp); } +static inline struct chain_config *__get_curr_is_config(struct fimc_is *is) +{ + return &is->config[is->config_index]; +} + static inline void fimc_is_mem_barrier(void) { mb(); diff --git a/drivers/media/platform/exynos4-is/fimc-isp-video.c b/drivers/media/platform/exynos4-is/fimc-isp-video.c new file mode 100644 index 00000000000..93f9cf2ebcd --- /dev/null +++ b/drivers/media/platform/exynos4-is/fimc-isp-video.c @@ -0,0 +1,659 @@ +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * FIMC-IS ISP video input and video output DMA interface driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> + * + * The hardware handling code derived from a driver written by + * Younghwan Joo <yhwan.joo@samsung.com>. + * + * 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/bitops.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/printk.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> +#include <media/exynos-fimc.h> + +#include "common.h" +#include "media-dev.h" +#include "fimc-is.h" +#include "fimc-isp-video.h" +#include "fimc-is-param.h" + +static int isp_video_capture_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *pfmt, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], void *allocators[]) +{ + struct fimc_isp *isp = vb2_get_drv_priv(vq); + struct v4l2_pix_format_mplane *vid_fmt = &isp->video_capture.pixfmt; + const struct v4l2_pix_format_mplane *pixm = NULL; + const struct fimc_fmt *fmt; + unsigned int wh, i; + + if (pfmt) { + pixm = &pfmt->fmt.pix_mp; + fmt = fimc_isp_find_format(&pixm->pixelformat, NULL, -1); + wh = pixm->width * pixm->height; + } else { + fmt = isp->video_capture.format; + wh = vid_fmt->width * vid_fmt->height; + } + + if (fmt == NULL) + return -EINVAL; + + *num_buffers = clamp_t(u32, *num_buffers, FIMC_ISP_REQ_BUFS_MIN, + FIMC_ISP_REQ_BUFS_MAX); + *num_planes = fmt->memplanes; + + for (i = 0; i < fmt->memplanes; i++) { + unsigned int size = (wh * fmt->depth[i]) / 8; + if (pixm) + sizes[i] = max(size, pixm->plane_fmt[i].sizeimage); + else + sizes[i] = size; + allocators[i] = isp->alloc_ctx; + } + + return 0; +} + +static inline struct param_dma_output *__get_isp_dma2(struct fimc_is *is) +{ + return &__get_curr_is_config(is)->isp.dma2_output; +} + +static int isp_video_capture_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct fimc_isp *isp = vb2_get_drv_priv(q); + struct fimc_is *is = fimc_isp_to_is(isp); + struct param_dma_output *dma = __get_isp_dma2(is); + struct fimc_is_video *video = &isp->video_capture; + int ret; + + if (!test_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state) || + test_bit(ST_ISP_VID_CAP_STREAMING, &isp->state)) + return 0; + + + dma->cmd = DMA_OUTPUT_COMMAND_ENABLE; + dma->notify_dma_done = DMA_OUTPUT_NOTIFY_DMA_DONE_ENABLE; + dma->buffer_address = is->is_dma_p_region + + DMA2_OUTPUT_ADDR_ARRAY_OFFS; + dma->buffer_number = video->reqbufs_count; + dma->dma_out_mask = video->buf_mask; + + isp_dbg(2, &video->ve.vdev, + "buf_count: %d, planes: %d, dma addr table: %#x\n", + video->buf_count, video->format->memplanes, + dma->buffer_address); + + fimc_is_mem_barrier(); + + fimc_is_set_param_bit(is, PARAM_ISP_DMA2_OUTPUT); + __fimc_is_hw_update_param(is, PARAM_ISP_DMA2_OUTPUT); + + ret = fimc_is_itf_s_param(is, false); + if (ret < 0) + return ret; + + ret = fimc_pipeline_call(&video->ve, set_stream, 1); + if (ret < 0) + return ret; + + set_bit(ST_ISP_VID_CAP_STREAMING, &isp->state); + return ret; +} + +static void isp_video_capture_stop_streaming(struct vb2_queue *q) +{ + struct fimc_isp *isp = vb2_get_drv_priv(q); + struct fimc_is *is = fimc_isp_to_is(isp); + struct param_dma_output *dma = __get_isp_dma2(is); + int ret; + + ret = fimc_pipeline_call(&isp->video_capture.ve, set_stream, 0); + if (ret < 0) + return; + + dma->cmd = DMA_OUTPUT_COMMAND_DISABLE; + dma->notify_dma_done = DMA_OUTPUT_NOTIFY_DMA_DONE_DISABLE; + dma->buffer_number = 0; + dma->buffer_address = 0; + dma->dma_out_mask = 0; + + fimc_is_set_param_bit(is, PARAM_ISP_DMA2_OUTPUT); + __fimc_is_hw_update_param(is, PARAM_ISP_DMA2_OUTPUT); + + ret = fimc_is_itf_s_param(is, false); + if (ret < 0) + dev_warn(&is->pdev->dev, "%s: DMA stop failed\n", __func__); + + fimc_is_hw_set_isp_buf_mask(is, 0); + + clear_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state); + clear_bit(ST_ISP_VID_CAP_STREAMING, &isp->state); + + isp->video_capture.buf_count = 0; +} + +static int isp_video_capture_buffer_prepare(struct vb2_buffer *vb) +{ + struct fimc_isp *isp = vb2_get_drv_priv(vb->vb2_queue); + struct fimc_is_video *video = &isp->video_capture; + int i; + + if (video->format == NULL) + return -EINVAL; + + for (i = 0; i < video->format->memplanes; i++) { + unsigned long size = video->pixfmt.plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb, i) < size) { + v4l2_err(&video->ve.vdev, + "User buffer too small (%ld < %ld)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + vb2_set_plane_payload(vb, i, size); + } + + /* Check if we get one of the already known buffers. */ + if (test_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state)) { + dma_addr_t dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + int i; + + for (i = 0; i < video->buf_count; i++) + if (video->buffers[i]->dma_addr[0] == dma_addr) + return 0; + return -ENXIO; + } + + return 0; +} + +static void isp_video_capture_buffer_queue(struct vb2_buffer *vb) +{ + struct fimc_isp *isp = vb2_get_drv_priv(vb->vb2_queue); + struct fimc_is_video *video = &isp->video_capture; + struct fimc_is *is = fimc_isp_to_is(isp); + struct isp_video_buf *ivb = to_isp_video_buf(vb); + unsigned long flags; + unsigned int i; + + if (test_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state)) { + spin_lock_irqsave(&is->slock, flags); + video->buf_mask |= BIT(ivb->index); + spin_unlock_irqrestore(&is->slock, flags); + } else { + unsigned int num_planes = video->format->memplanes; + + ivb->index = video->buf_count; + video->buffers[ivb->index] = ivb; + + for (i = 0; i < num_planes; i++) { + int buf_index = ivb->index * num_planes + i; + + ivb->dma_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); + is->is_p_region->shared[32 + buf_index] = + ivb->dma_addr[i]; + + isp_dbg(2, &video->ve.vdev, + "dma_buf %d (%d/%d/%d) addr: %#x\n", + buf_index, ivb->index, i, vb->v4l2_buf.index, + ivb->dma_addr[i]); + } + + if (++video->buf_count < video->reqbufs_count) + return; + + video->buf_mask = (1UL << video->buf_count) - 1; + set_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state); + } + + if (!test_bit(ST_ISP_VID_CAP_STREAMING, &isp->state)) + isp_video_capture_start_streaming(vb->vb2_queue, 0); +} + +/* + * FIMC-IS ISP input and output DMA interface interrupt handler. + * Locking: called with is->slock spinlock held. + */ +void fimc_isp_video_irq_handler(struct fimc_is *is) +{ + struct fimc_is_video *video = &is->isp.video_capture; + struct vb2_buffer *vb; + int buf_index; + + /* TODO: Ensure the DMA is really stopped in stop_streaming callback */ + if (!test_bit(ST_ISP_VID_CAP_STREAMING, &is->isp.state)) + return; + + buf_index = (is->i2h_cmd.args[1] - 1) % video->buf_count; + vb = &video->buffers[buf_index]->vb; + + v4l2_get_timestamp(&vb->v4l2_buf.timestamp); + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + + video->buf_mask &= ~BIT(buf_index); + fimc_is_hw_set_isp_buf_mask(is, video->buf_mask); +} + +static const struct vb2_ops isp_video_capture_qops = { + .queue_setup = isp_video_capture_queue_setup, + .buf_prepare = isp_video_capture_buffer_prepare, + .buf_queue = isp_video_capture_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = isp_video_capture_start_streaming, + .stop_streaming = isp_video_capture_stop_streaming, +}; + +static int isp_video_open(struct file *file) +{ + struct fimc_isp *isp = video_drvdata(file); + struct exynos_video_entity *ve = &isp->video_capture.ve; + struct media_entity *me = &ve->vdev.entity; + int ret; + + if (mutex_lock_interruptible(&isp->video_lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto unlock; + + ret = pm_runtime_get_sync(&isp->pdev->dev); + if (ret < 0) + goto rel_fh; + + if (v4l2_fh_is_singular_file(file)) { + mutex_lock(&me->parent->graph_mutex); + + ret = fimc_pipeline_call(ve, open, me, true); + + /* Mark the video pipeline as in use. */ + if (ret == 0) + me->use_count++; + + mutex_unlock(&me->parent->graph_mutex); + } + if (!ret) + goto unlock; +rel_fh: + v4l2_fh_release(file); +unlock: + mutex_unlock(&isp->video_lock); + return ret; +} + +static int isp_video_release(struct file *file) +{ + struct fimc_isp *isp = video_drvdata(file); + struct fimc_is_video *ivc = &isp->video_capture; + struct media_entity *entity = &ivc->ve.vdev.entity; + struct media_device *mdev = entity->parent; + int ret = 0; + + mutex_lock(&isp->video_lock); + + if (v4l2_fh_is_singular_file(file) && ivc->streaming) { + media_entity_pipeline_stop(entity); + ivc->streaming = 0; + } + + vb2_fop_release(file); + + if (v4l2_fh_is_singular_file(file)) { + fimc_pipeline_call(&ivc->ve, close); + + mutex_lock(&mdev->graph_mutex); + entity->use_count--; + mutex_unlock(&mdev->graph_mutex); + } + + pm_runtime_put(&isp->pdev->dev); + mutex_unlock(&isp->video_lock); + + return ret; +} + +static const struct v4l2_file_operations isp_video_fops = { + .owner = THIS_MODULE, + .open = isp_video_open, + .release = isp_video_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* + * Video node ioctl operations + */ +static int isp_video_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct fimc_isp *isp = video_drvdata(file); + + __fimc_vidioc_querycap(&isp->pdev->dev, cap, V4L2_CAP_STREAMING); + return 0; +} + +static int isp_video_enum_fmt_mplane(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + const struct fimc_fmt *fmt; + + if (f->index >= FIMC_ISP_NUM_FORMATS) + return -EINVAL; + + fmt = fimc_isp_find_format(NULL, NULL, f->index); + if (WARN_ON(fmt == NULL)) + return -EINVAL; + + strlcpy(f->description, fmt->name, sizeof(f->description)); + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int isp_video_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_isp *isp = video_drvdata(file); + + f->fmt.pix_mp = isp->video_capture.pixfmt; + return 0; +} + +static void __isp_video_try_fmt(struct fimc_isp *isp, + struct v4l2_pix_format_mplane *pixm, + const struct fimc_fmt **fmt) +{ + *fmt = fimc_isp_find_format(&pixm->pixelformat, NULL, 2); + + pixm->colorspace = V4L2_COLORSPACE_SRGB; + pixm->field = V4L2_FIELD_NONE; + pixm->num_planes = (*fmt)->memplanes; + pixm->pixelformat = (*fmt)->fourcc; + /* + * TODO: double check with the docmentation these width/height + * constraints are correct. + */ + v4l_bound_align_image(&pixm->width, FIMC_ISP_SOURCE_WIDTH_MIN, + FIMC_ISP_SOURCE_WIDTH_MAX, 3, + &pixm->height, FIMC_ISP_SOURCE_HEIGHT_MIN, + FIMC_ISP_SOURCE_HEIGHT_MAX, 0, 0); +} + +static int isp_video_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_isp *isp = video_drvdata(file); + + __isp_video_try_fmt(isp, &f->fmt.pix_mp, NULL); + return 0; +} + +static int isp_video_s_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct fimc_isp *isp = video_drvdata(file); + struct fimc_is *is = fimc_isp_to_is(isp); + struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp; + const struct fimc_fmt *ifmt = NULL; + struct param_dma_output *dma = __get_isp_dma2(is); + + __isp_video_try_fmt(isp, pixm, &ifmt); + + if (WARN_ON(ifmt == NULL)) + return -EINVAL; + + dma->format = DMA_OUTPUT_FORMAT_BAYER; + dma->order = DMA_OUTPUT_ORDER_GB_BG; + dma->plane = ifmt->memplanes; + dma->bitwidth = ifmt->depth[0]; + dma->width = pixm->width; + dma->height = pixm->height; + + fimc_is_mem_barrier(); + + isp->video_capture.format = ifmt; + isp->video_capture.pixfmt = *pixm; + + return 0; +} + +/* + * Check for source/sink format differences at each link. + * Return 0 if the formats match or -EPIPE otherwise. + */ +static int isp_video_pipeline_validate(struct fimc_isp *isp) +{ + struct v4l2_subdev *sd = &isp->subdev; + struct v4l2_subdev_format sink_fmt, src_fmt; + struct media_pad *pad; + int ret; + + while (1) { + /* Retrieve format at the sink pad */ + pad = &sd->entity.pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + sink_fmt.pad = pad->index; + sink_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &sink_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + + /* Retrieve format at the source pad */ + pad = media_entity_remote_pad(pad); + if (pad == NULL || + media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + break; + + sd = media_entity_to_v4l2_subdev(pad->entity); + src_fmt.pad = pad->index; + src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &src_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + + if (src_fmt.format.width != sink_fmt.format.width || + src_fmt.format.height != sink_fmt.format.height || + src_fmt.format.code != sink_fmt.format.code) + return -EPIPE; + } + + return 0; +} + +static int isp_video_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_isp *isp = video_drvdata(file); + struct exynos_video_entity *ve = &isp->video_capture.ve; + struct media_entity *me = &ve->vdev.entity; + int ret; + + ret = media_entity_pipeline_start(me, &ve->pipe->mp); + if (ret < 0) + return ret; + + ret = isp_video_pipeline_validate(isp); + if (ret < 0) + goto p_stop; + + ret = vb2_ioctl_streamon(file, priv, type); + if (ret < 0) + goto p_stop; + + isp->video_capture.streaming = 1; + return 0; +p_stop: + media_entity_pipeline_stop(me); + return ret; +} + +static int isp_video_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_isp *isp = video_drvdata(file); + struct fimc_is_video *video = &isp->video_capture; + int ret; + + ret = vb2_ioctl_streamoff(file, priv, type); + if (ret < 0) + return ret; + + media_entity_pipeline_stop(&video->ve.vdev.entity); + video->streaming = 0; + return 0; +} + +static int isp_video_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + struct fimc_isp *isp = video_drvdata(file); + int ret; + + ret = vb2_ioctl_reqbufs(file, priv, rb); + if (ret < 0) + return ret; + + if (rb->count && rb->count < FIMC_ISP_REQ_BUFS_MIN) { + rb->count = 0; + vb2_ioctl_reqbufs(file, priv, rb); + ret = -ENOMEM; + } + + isp->video_capture.reqbufs_count = rb->count; + return ret; +} + +static const struct v4l2_ioctl_ops isp_video_ioctl_ops = { + .vidioc_querycap = isp_video_querycap, + .vidioc_enum_fmt_vid_cap_mplane = isp_video_enum_fmt_mplane, + .vidioc_try_fmt_vid_cap_mplane = isp_video_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = isp_video_s_fmt_mplane, + .vidioc_g_fmt_vid_cap_mplane = isp_video_g_fmt_mplane, + .vidioc_reqbufs = isp_video_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = isp_video_streamon, + .vidioc_streamoff = isp_video_streamoff, +}; + +int fimc_isp_video_device_register(struct fimc_isp *isp, + struct v4l2_device *v4l2_dev, + enum v4l2_buf_type type) +{ + struct vb2_queue *q = &isp->video_capture.vb_queue; + struct fimc_is_video *iv; + struct video_device *vdev; + int ret; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + iv = &isp->video_capture; + else + return -ENOSYS; + + mutex_init(&isp->video_lock); + INIT_LIST_HEAD(&iv->pending_buf_q); + INIT_LIST_HEAD(&iv->active_buf_q); + iv->format = fimc_isp_find_format(NULL, NULL, 0); + iv->pixfmt.width = IS_DEFAULT_WIDTH; + iv->pixfmt.height = IS_DEFAULT_HEIGHT; + iv->pixfmt.pixelformat = iv->format->fourcc; + iv->pixfmt.colorspace = V4L2_COLORSPACE_SRGB; + iv->reqbufs_count = 0; + + memset(q, 0, sizeof(*q)); + q->type = type; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->ops = &isp_video_capture_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct isp_video_buf); + q->drv_priv = isp; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &isp->video_lock; + + ret = vb2_queue_init(q); + if (ret < 0) + return ret; + + vdev = &iv->ve.vdev; + memset(vdev, 0, sizeof(*vdev)); + snprintf(vdev->name, sizeof(vdev->name), "fimc-is-isp.%s", + type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ? + "capture" : "output"); + vdev->queue = q; + vdev->fops = &isp_video_fops; + vdev->ioctl_ops = &isp_video_ioctl_ops; + vdev->v4l2_dev = v4l2_dev; + vdev->minor = -1; + vdev->release = video_device_release_empty; + vdev->lock = &isp->video_lock; + + iv->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_init(&vdev->entity, 1, &iv->pad, 0); + if (ret < 0) + return ret; + + video_set_drvdata(vdev, isp); + + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + media_entity_cleanup(&vdev->entity); + return ret; + } + + v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n", + vdev->name, video_device_node_name(vdev)); + + return 0; +} + +void fimc_isp_video_device_unregister(struct fimc_isp *isp, + enum v4l2_buf_type type) +{ + struct exynos_video_entity *ve; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + ve = &isp->video_capture.ve; + else + return; + + mutex_lock(&isp->video_lock); + + if (video_is_registered(&ve->vdev)) { + video_unregister_device(&ve->vdev); + media_entity_cleanup(&ve->vdev.entity); + ve->pipe = NULL; + } + + mutex_unlock(&isp->video_lock); +} diff --git a/drivers/media/platform/exynos4-is/fimc-isp-video.h b/drivers/media/platform/exynos4-is/fimc-isp-video.h new file mode 100644 index 00000000000..98c662654bb --- /dev/null +++ b/drivers/media/platform/exynos4-is/fimc-isp-video.h @@ -0,0 +1,44 @@ +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Sylwester Nawrocki <s.nawrocki@samsung.com> + * + * 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_ISP_VIDEO__ +#define FIMC_ISP_VIDEO__ + +#include <media/videobuf2-core.h> +#include "fimc-isp.h" + +#ifdef CONFIG_VIDEO_EXYNOS4_ISP_DMA_CAPTURE +int fimc_isp_video_device_register(struct fimc_isp *isp, + struct v4l2_device *v4l2_dev, + enum v4l2_buf_type type); + +void fimc_isp_video_device_unregister(struct fimc_isp *isp, + enum v4l2_buf_type type); + +void fimc_isp_video_irq_handler(struct fimc_is *is); +#else +static inline void fimc_isp_video_irq_handler(struct fimc_is *is) +{ +} + +static inline int fimc_isp_video_device_register(struct fimc_isp *isp, + struct v4l2_device *v4l2_dev, + enum v4l2_buf_type type) +{ + return 0; +} + +void fimc_isp_video_device_unregister(struct fimc_isp *isp, + enum v4l2_buf_type type) +{ +} +#endif /* !CONFIG_VIDEO_EXYNOS4_ISP_DMA_CAPTURE */ + +#endif /* FIMC_ISP_VIDEO__ */ diff --git a/drivers/media/platform/exynos4-is/fimc-isp.c b/drivers/media/platform/exynos4-is/fimc-isp.c index cf520a7d7f7..be62d6b9ac4 100644 --- a/drivers/media/platform/exynos4-is/fimc-isp.c +++ b/drivers/media/platform/exynos4-is/fimc-isp.c @@ -25,6 +25,7 @@ #include <media/v4l2-device.h> #include "media-dev.h" +#include "fimc-isp-video.h" #include "fimc-is-command.h" #include "fimc-is-param.h" #include "fimc-is-regs.h" @@ -93,8 +94,8 @@ void fimc_isp_irq_handler(struct fimc_is *is) is->i2h_cmd.args[1] = mcuctl_read(is, MCUCTL_REG_ISSR(21)); fimc_is_fw_clear_irq1(is, FIMC_IS_INT_FRAME_DONE_ISP); + fimc_isp_video_irq_handler(is); - /* TODO: Complete ISP DMA interrupt handler */ wake_up(&is->irq_queue); } @@ -388,7 +389,33 @@ static int fimc_isp_subdev_open(struct v4l2_subdev *sd, return 0; } +static int fimc_isp_subdev_registered(struct v4l2_subdev *sd) +{ + struct fimc_isp *isp = v4l2_get_subdevdata(sd); + int ret; + + /* Use pipeline object allocated by the media device. */ + isp->video_capture.ve.pipe = v4l2_get_subdev_hostdata(sd); + + ret = fimc_isp_video_device_register(isp, sd->v4l2_dev, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (ret < 0) + isp->video_capture.ve.pipe = NULL; + + return ret; +} + +static void fimc_isp_subdev_unregistered(struct v4l2_subdev *sd) +{ + struct fimc_isp *isp = v4l2_get_subdevdata(sd); + + fimc_isp_video_device_unregister(isp, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); +} + static const struct v4l2_subdev_internal_ops fimc_is_subdev_internal_ops = { + .registered = fimc_isp_subdev_registered, + .unregistered = fimc_isp_subdev_unregistered, .open = fimc_isp_subdev_open, }; @@ -511,7 +538,7 @@ static int __ctrl_set_metering(struct fimc_is *is, unsigned int value) break; default: return -EINVAL; - }; + } __is_set_isp_metering(is, IS_METERING_CONFIG_CMD, val); return 0; @@ -672,6 +699,8 @@ int fimc_isp_subdev_create(struct fimc_isp *isp) mutex_init(&isp->subdev_lock); v4l2_subdev_init(sd, &fimc_is_subdev_ops); + + sd->owner = THIS_MODULE; sd->grp_id = GRP_ID_FIMC_IS; sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; snprintf(sd->name, sizeof(sd->name), "FIMC-IS-ISP"); diff --git a/drivers/media/platform/exynos4-is/fimc-isp.h b/drivers/media/platform/exynos4-is/fimc-isp.h index 03bf95ab017..b99be09b49f 100644 --- a/drivers/media/platform/exynos4-is/fimc-isp.h +++ b/drivers/media/platform/exynos4-is/fimc-isp.h @@ -24,7 +24,7 @@ #include <media/videobuf2-core.h> #include <media/v4l2-device.h> #include <media/v4l2-mediabus.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> extern int fimc_isp_debug; @@ -35,17 +35,18 @@ extern int fimc_isp_debug; #define FIMC_ISP_SINK_WIDTH_MIN (16 + 8) #define FIMC_ISP_SINK_HEIGHT_MIN (12 + 8) #define FIMC_ISP_SOURCE_WIDTH_MIN 8 -#define FIMC_ISP_SOURC_HEIGHT_MIN 8 +#define FIMC_ISP_SOURCE_HEIGHT_MIN 8 #define FIMC_ISP_CAC_MARGIN_WIDTH 16 #define FIMC_ISP_CAC_MARGIN_HEIGHT 12 #define FIMC_ISP_SINK_WIDTH_MAX (4000 - 16) #define FIMC_ISP_SINK_HEIGHT_MAX (4000 + 12) #define FIMC_ISP_SOURCE_WIDTH_MAX 4000 -#define FIMC_ISP_SOURC_HEIGHT_MAX 4000 +#define FIMC_ISP_SOURCE_HEIGHT_MAX 4000 #define FIMC_ISP_NUM_FORMATS 3 #define FIMC_ISP_REQ_BUFS_MIN 2 +#define FIMC_ISP_REQ_BUFS_MAX 32 #define FIMC_ISP_SD_PAD_SINK 0 #define FIMC_ISP_SD_PAD_SRC_FIFO 1 @@ -100,6 +101,16 @@ struct fimc_isp_ctrls { struct v4l2_ctrl *colorfx; }; +struct isp_video_buf { + struct vb2_buffer vb; + dma_addr_t dma_addr[FIMC_ISP_MAX_PLANES]; + unsigned int index; +}; + +#define to_isp_video_buf(_b) container_of(_b, struct isp_video_buf, vb) + +#define FIMC_ISP_MAX_BUFS 4 + /** * struct fimc_is_video - fimc-is video device structure * @vdev: video_device structure @@ -114,18 +125,26 @@ struct fimc_isp_ctrls { * @format: current pixel format */ struct fimc_is_video { - struct video_device vdev; + struct exynos_video_entity ve; enum v4l2_buf_type type; struct media_pad pad; struct list_head pending_buf_q; struct list_head active_buf_q; struct vb2_queue vb_queue; - unsigned int frame_count; unsigned int reqbufs_count; + unsigned int buf_count; + unsigned int buf_mask; + unsigned int frame_count; int streaming; + struct isp_video_buf *buffers[FIMC_ISP_MAX_BUFS]; const struct fimc_fmt *format; + struct v4l2_pix_format_mplane pixfmt; }; +/* struct fimc_isp:state bit definitions */ +#define ST_ISP_VID_CAP_BUF_PREP 0 +#define ST_ISP_VID_CAP_STREAMING 1 + /** * struct fimc_isp - FIMC-IS ISP data structure * @pdev: pointer to FIMC-IS platform device diff --git a/drivers/media/platform/exynos4-is/fimc-lite-reg.c b/drivers/media/platform/exynos4-is/fimc-lite-reg.c index 72a343e3b5e..bc3ec7d25a3 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite-reg.c +++ b/drivers/media/platform/exynos4-is/fimc-lite-reg.c @@ -12,7 +12,7 @@ #include <linux/bitops.h> #include <linux/delay.h> #include <linux/io.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> #include "fimc-lite-reg.h" #include "fimc-lite.h" @@ -133,7 +133,7 @@ void flite_hw_set_source_format(struct fimc_lite *dev, struct flite_frame *f) int i = ARRAY_SIZE(src_pixfmt_map); u32 cfg; - while (--i >= 0) { + while (--i) { if (src_pixfmt_map[i][0] == pixelcode) break; } @@ -240,7 +240,7 @@ static void flite_hw_set_out_order(struct fimc_lite *dev, struct flite_frame *f) u32 cfg = readl(dev->regs + FLITE_REG_CIODMAFMT); int i = ARRAY_SIZE(pixcode); - while (--i >= 0) + while (--i) if (pixcode[i][0] == f->fmt->mbus_code) break; cfg &= ~FLITE_REG_CIODMAFMT_YCBCR_ORDER_MASK; diff --git a/drivers/media/platform/exynos4-is/fimc-lite.c b/drivers/media/platform/exynos4-is/fimc-lite.c index 08fbfedea90..a97d2352f1d 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite.c +++ b/drivers/media/platform/exynos4-is/fimc-lite.c @@ -30,7 +30,7 @@ #include <media/v4l2-mem2mem.h> #include <media/videobuf2-core.h> #include <media/videobuf2-dma-contig.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> #include "common.h" #include "fimc-core.h" @@ -90,7 +90,7 @@ static const struct fimc_fmt fimc_lite_formats[] = { .name = "RAW10 (GRBG)", .fourcc = V4L2_PIX_FMT_SGRBG10, .colorspace = V4L2_COLORSPACE_SRGB, - .depth = { 10 }, + .depth = { 16 }, .color = FIMC_FMT_RAW10, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGRBG10_1X10, @@ -99,7 +99,7 @@ static const struct fimc_fmt fimc_lite_formats[] = { .name = "RAW12 (GRBG)", .fourcc = V4L2_PIX_FMT_SGRBG12, .colorspace = V4L2_COLORSPACE_SRGB, - .depth = { 12 }, + .depth = { 16 }, .color = FIMC_FMT_RAW12, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGRBG12_1X12, @@ -350,14 +350,14 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) return 0; } -static int stop_streaming(struct vb2_queue *q) +static void stop_streaming(struct vb2_queue *q) { struct fimc_lite *fimc = q->drv_priv; if (!fimc_lite_active(fimc)) - return -EINVAL; + return; - return fimc_lite_stop_capture(fimc, false); + fimc_lite_stop_capture(fimc, false); } static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *pfmt, @@ -546,7 +546,7 @@ static int fimc_lite_release(struct file *file) mutex_unlock(&entity->parent->graph_mutex); } - vb2_fop_release(file); + _vb2_fop_release(file, NULL); pm_runtime_put(&fimc->pdev->dev); clear_bit(ST_FLITE_SUSPENDED, &fimc->state); @@ -1313,7 +1313,7 @@ static int fimc_lite_subdev_registered(struct v4l2_subdev *sd) q->mem_ops = &vb2_dma_contig_memops; q->buf_struct_size = sizeof(struct flite_buffer); q->drv_priv = fimc; - q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->lock = &fimc->lock; ret = vb2_queue_init(q); @@ -1504,16 +1504,17 @@ static int fimc_lite_probe(struct platform_device *pdev) struct resource *res; int ret; + if (!dev->of_node) + return -ENODEV; + fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL); if (!fimc) return -ENOMEM; - if (dev->of_node) { - of_id = of_match_node(flite_of_match, dev->of_node); - if (of_id) - drv_data = (struct flite_drvdata *)of_id->data; - fimc->index = of_alias_get_id(dev->of_node, "fimc-lite"); - } + of_id = of_match_node(flite_of_match, dev->of_node); + if (of_id) + drv_data = (struct flite_drvdata *)of_id->data; + fimc->index = of_alias_get_id(dev->of_node, "fimc-lite"); if (!drv_data || fimc->index >= drv_data->num_instances || fimc->index < 0) { @@ -1548,42 +1549,46 @@ static int fimc_lite_probe(struct platform_device *pdev) 0, dev_name(dev), fimc); if (ret) { dev_err(dev, "Failed to install irq (%d)\n", ret); - goto err_clk; + goto err_clk_put; } /* The video node will be created within the subdev's registered() op */ ret = fimc_lite_create_capture_subdev(fimc); if (ret) - goto err_clk; + goto err_clk_put; platform_set_drvdata(pdev, fimc); pm_runtime_enable(dev); - ret = pm_runtime_get_sync(dev); - if (ret < 0) - goto err_sd; + + if (!pm_runtime_enabled(dev)) { + ret = clk_enable(fimc->clock); + if (ret < 0) + goto err_sd; + } fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(fimc->alloc_ctx)) { ret = PTR_ERR(fimc->alloc_ctx); - goto err_pm; + goto err_clk_dis; } - pm_runtime_put(dev); - fimc_lite_set_default_config(fimc); dev_dbg(dev, "FIMC-LITE.%d registered successfully\n", fimc->index); return 0; -err_pm: - pm_runtime_put(dev); + +err_clk_dis: + if (!pm_runtime_enabled(dev)) + clk_disable(fimc->clock); err_sd: fimc_lite_unregister_capture_subdev(fimc); -err_clk: +err_clk_put: fimc_lite_clk_put(fimc); return ret; } +#ifdef CONFIG_PM_RUNTIME static int fimc_lite_runtime_resume(struct device *dev) { struct fimc_lite *fimc = dev_get_drvdata(dev); @@ -1599,6 +1604,7 @@ static int fimc_lite_runtime_suspend(struct device *dev) clk_disable(fimc->clock); return 0; } +#endif #ifdef CONFIG_PM_SLEEP static int fimc_lite_resume(struct device *dev) diff --git a/drivers/media/platform/exynos4-is/fimc-lite.h b/drivers/media/platform/exynos4-is/fimc-lite.h index 7428b2d22b5..ea19dc7be63 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite.h +++ b/drivers/media/platform/exynos4-is/fimc-lite.h @@ -23,7 +23,7 @@ #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-mediabus.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> #define FIMC_LITE_DRV_NAME "exynos-fimc-lite" #define FLITE_CLK_NAME "flite" diff --git a/drivers/media/platform/exynos4-is/fimc-m2m.c b/drivers/media/platform/exynos4-is/fimc-m2m.c index 8d33b68c76b..0ad1b6f84a2 100644 --- a/drivers/media/platform/exynos4-is/fimc-m2m.c +++ b/drivers/media/platform/exynos4-is/fimc-m2m.c @@ -44,17 +44,17 @@ void fimc_m2m_job_finish(struct fimc_ctx *ctx, int vb_state) { struct vb2_buffer *src_vb, *dst_vb; - if (!ctx || !ctx->m2m_ctx) + if (!ctx || !ctx->fh.m2m_ctx) return; - src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); - dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); if (src_vb && dst_vb) { v4l2_m2m_buf_done(src_vb, vb_state); v4l2_m2m_buf_done(dst_vb, vb_state); v4l2_m2m_job_finish(ctx->fimc_dev->m2m.m2m_dev, - ctx->m2m_ctx); + ctx->fh.m2m_ctx); } } @@ -85,7 +85,7 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) return ret > 0 ? 0 : ret; } -static int stop_streaming(struct vb2_queue *q) +static void stop_streaming(struct vb2_queue *q) { struct fimc_ctx *ctx = q->drv_priv; int ret; @@ -95,7 +95,6 @@ static int stop_streaming(struct vb2_queue *q) fimc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR); pm_runtime_put(&ctx->fimc_dev->pdev->dev); - return 0; } static void fimc_device_run(void *priv) @@ -123,17 +122,20 @@ static void fimc_device_run(void *priv) fimc_prepare_dma_offset(ctx, df); } - src_vb = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + src_vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); ret = fimc_prepare_addr(ctx, src_vb, sf, &sf->paddr); if (ret) goto dma_unlock; - dst_vb = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + dst_vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); ret = fimc_prepare_addr(ctx, dst_vb, df, &df->paddr); if (ret) goto dma_unlock; dst_vb->v4l2_buf.timestamp = src_vb->v4l2_buf.timestamp; + dst_vb->v4l2_buf.flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->v4l2_buf.flags |= + src_vb->v4l2_buf.flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; /* Reconfigure hardware if the context has changed. */ if (fimc->m2m.ctx != ctx) { @@ -194,7 +196,7 @@ static int fimc_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, *num_planes = f->fmt->memplanes; for (i = 0; i < f->fmt->memplanes; i++) { - sizes[i] = (f->f_width * f->f_height * f->fmt->depth[i]) / 8; + sizes[i] = f->payload[i]; allocators[i] = ctx->fimc_dev->alloc_ctx; } return 0; @@ -219,31 +221,15 @@ static int fimc_buf_prepare(struct vb2_buffer *vb) static void fimc_buf_queue(struct vb2_buffer *vb) { struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); - - dbg("ctx: %p, ctx->state: 0x%x", ctx, ctx->state); - - if (ctx->m2m_ctx) - v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); -} - -static void fimc_lock(struct vb2_queue *vq) -{ - struct fimc_ctx *ctx = vb2_get_drv_priv(vq); - mutex_lock(&ctx->fimc_dev->lock); -} - -static void fimc_unlock(struct vb2_queue *vq) -{ - struct fimc_ctx *ctx = vb2_get_drv_priv(vq); - mutex_unlock(&ctx->fimc_dev->lock); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb); } static struct vb2_ops fimc_qops = { .queue_setup = fimc_queue_setup, .buf_prepare = fimc_buf_prepare, .buf_queue = fimc_buf_queue, - .wait_prepare = fimc_unlock, - .wait_finish = fimc_lock, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, .stop_streaming = stop_streaming, .start_streaming = start_streaming, }; @@ -355,7 +341,7 @@ static void __set_frame_format(struct fimc_frame *frame, struct fimc_fmt *fmt, { int i; - for (i = 0; i < fmt->colplanes; i++) { + for (i = 0; i < fmt->memplanes; i++) { frame->bytesperline[i] = pixm->plane_fmt[i].bytesperline; frame->payload[i] = pixm->plane_fmt[i].sizeimage; } @@ -385,7 +371,7 @@ static int fimc_m2m_s_fmt_mplane(struct file *file, void *fh, if (ret) return ret; - vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (vb2_is_busy(vq)) { v4l2_err(&fimc->m2m.vfd, "queue (%d) busy\n", f->type); @@ -410,56 +396,6 @@ static int fimc_m2m_s_fmt_mplane(struct file *file, void *fh, return 0; } -static int fimc_m2m_reqbufs(struct file *file, void *fh, - struct v4l2_requestbuffers *reqbufs) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); -} - -static int fimc_m2m_querybuf(struct file *file, void *fh, - struct v4l2_buffer *buf) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); -} - -static int fimc_m2m_qbuf(struct file *file, void *fh, - struct v4l2_buffer *buf) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); -} - -static int fimc_m2m_dqbuf(struct file *file, void *fh, - struct v4l2_buffer *buf) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); -} - -static int fimc_m2m_expbuf(struct file *file, void *fh, - struct v4l2_exportbuffer *eb) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_expbuf(file, ctx->m2m_ctx, eb); -} - - -static int fimc_m2m_streamon(struct file *file, void *fh, - enum v4l2_buf_type type) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); -} - -static int fimc_m2m_streamoff(struct file *file, void *fh, - enum v4l2_buf_type type) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); -} - static int fimc_m2m_cropcap(struct file *file, void *fh, struct v4l2_cropcap *cr) { @@ -524,7 +460,7 @@ static int fimc_m2m_try_crop(struct fimc_ctx *ctx, struct v4l2_crop *cr) else halign = ffs(fimc->variant->min_vsize_align) - 1; - for (i = 0; i < f->fmt->colplanes; i++) + for (i = 0; i < f->fmt->memplanes; i++) depth += f->fmt->depth[i]; v4l_bound_align_image(&cr->c.width, min_size, f->o_width, @@ -598,13 +534,13 @@ static const struct v4l2_ioctl_ops fimc_m2m_ioctl_ops = { .vidioc_try_fmt_vid_out_mplane = fimc_m2m_try_fmt_mplane, .vidioc_s_fmt_vid_cap_mplane = fimc_m2m_s_fmt_mplane, .vidioc_s_fmt_vid_out_mplane = fimc_m2m_s_fmt_mplane, - .vidioc_reqbufs = fimc_m2m_reqbufs, - .vidioc_querybuf = fimc_m2m_querybuf, - .vidioc_qbuf = fimc_m2m_qbuf, - .vidioc_dqbuf = fimc_m2m_dqbuf, - .vidioc_expbuf = fimc_m2m_expbuf, - .vidioc_streamon = fimc_m2m_streamon, - .vidioc_streamoff = fimc_m2m_streamoff, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_g_crop = fimc_m2m_g_crop, .vidioc_s_crop = fimc_m2m_s_crop, .vidioc_cropcap = fimc_m2m_cropcap @@ -623,7 +559,8 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, src_vq->ops = &fimc_qops; src_vq->mem_ops = &vb2_dma_contig_memops; src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); - src_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->fimc_dev->lock; ret = vb2_queue_init(src_vq); if (ret) @@ -635,7 +572,8 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, dst_vq->ops = &fimc_qops; dst_vq->mem_ops = &vb2_dma_contig_memops; dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); - dst_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->fimc_dev->lock; return vb2_queue_init(dst_vq); } @@ -708,9 +646,9 @@ static int fimc_m2m_open(struct file *file) ctx->out_path = FIMC_IO_DMA; ctx->scaler.enabled = 1; - ctx->m2m_ctx = v4l2_m2m_ctx_init(fimc->m2m.m2m_dev, ctx, queue_init); - if (IS_ERR(ctx->m2m_ctx)) { - ret = PTR_ERR(ctx->m2m_ctx); + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(fimc->m2m.m2m_dev, ctx, queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); goto error_c; } @@ -725,7 +663,7 @@ static int fimc_m2m_open(struct file *file) return 0; error_m2m_ctx: - v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); error_c: fimc_ctrls_delete(ctx); error_fh: @@ -747,7 +685,7 @@ static int fimc_m2m_release(struct file *file) mutex_lock(&fimc->lock); - v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); fimc_ctrls_delete(ctx); v4l2_fh_del(&ctx->fh); v4l2_fh_exit(&ctx->fh); @@ -760,45 +698,13 @@ static int fimc_m2m_release(struct file *file) return 0; } -static unsigned int fimc_m2m_poll(struct file *file, - struct poll_table_struct *wait) -{ - struct fimc_ctx *ctx = fh_to_ctx(file->private_data); - struct fimc_dev *fimc = ctx->fimc_dev; - int ret; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - - ret = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); - mutex_unlock(&fimc->lock); - - return ret; -} - - -static int fimc_m2m_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct fimc_ctx *ctx = fh_to_ctx(file->private_data); - struct fimc_dev *fimc = ctx->fimc_dev; - int ret; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - - ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); - mutex_unlock(&fimc->lock); - - return ret; -} - static const struct v4l2_file_operations fimc_m2m_fops = { .owner = THIS_MODULE, .open = fimc_m2m_open, .release = fimc_m2m_release, - .poll = fimc_m2m_poll, + .poll = v4l2_m2m_fop_poll, .unlocked_ioctl = video_ioctl2, - .mmap = fimc_m2m_mmap, + .mmap = v4l2_m2m_fop_mmap, }; static struct v4l2_m2m_ops m2m_ops = { diff --git a/drivers/media/platform/exynos4-is/fimc-reg.c b/drivers/media/platform/exynos4-is/fimc-reg.c index 1db8cb4c46e..2d77fd8f440 100644 --- a/drivers/media/platform/exynos4-is/fimc-reg.c +++ b/drivers/media/platform/exynos4-is/fimc-reg.c @@ -13,7 +13,7 @@ #include <linux/io.h> #include <linux/regmap.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> #include "media-dev.h" #include "fimc-reg.h" diff --git a/drivers/media/platform/exynos4-is/media-dev.c b/drivers/media/platform/exynos4-is/media-dev.c index 19f556c5957..344718df5c6 100644 --- a/drivers/media/platform/exynos4-is/media-dev.c +++ b/drivers/media/platform/exynos4-is/media-dev.c @@ -11,6 +11,8 @@ */ #include <linux/bug.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/device.h> #include <linux/errno.h> #include <linux/i2c.h> @@ -20,15 +22,16 @@ #include <linux/of.h> #include <linux/of_platform.h> #include <linux/of_device.h> -#include <linux/of_i2c.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/types.h> #include <linux/slab.h> +#include <media/v4l2-async.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-of.h> #include <media/media-device.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> #include "media-dev.h" #include "fimc-core.h" @@ -36,10 +39,6 @@ #include "fimc-lite.h" #include "mipi-csis.h" -static int __fimc_md_set_camclk(struct fimc_md *fmd, - struct fimc_source_info *si, - bool on); - /* Set up image sensor subdev -> FIMC capture node notifications. */ static void __setup_sensor_notification(struct fimc_md *fmd, struct v4l2_subdev *sensor, @@ -219,17 +218,11 @@ static int __fimc_pipeline_open(struct exynos_media_pipeline *ep, if (ret < 0) return ret; } - ret = fimc_md_set_camclk(sd, true); - if (ret < 0) - goto err_wbclk; ret = fimc_pipeline_s_power(p, 1); if (!ret) return 0; - fimc_md_set_camclk(sd, false); - -err_wbclk: if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP]) clk_disable_unprepare(fmd->wbclk[CLK_IDX_WB_B]); @@ -255,7 +248,6 @@ static int __fimc_pipeline_close(struct exynos_media_pipeline *ep) } ret = fimc_pipeline_s_power(p, 0); - fimc_md_set_camclk(sd, false); fmd = entity_to_fimc_mdev(&sd->entity); @@ -333,135 +325,14 @@ static void fimc_md_pipelines_free(struct fimc_md *fmd) } } -/* - * Sensor subdevice helper functions - */ -static struct v4l2_subdev *fimc_md_register_sensor(struct fimc_md *fmd, - struct fimc_source_info *si) -{ - struct i2c_adapter *adapter; - struct v4l2_subdev *sd = NULL; - - if (!si || !fmd) - return NULL; - /* - * If FIMC bus type is not Writeback FIFO assume it is same - * as sensor_bus_type. - */ - si->fimc_bus_type = si->sensor_bus_type; - - adapter = i2c_get_adapter(si->i2c_bus_num); - if (!adapter) { - v4l2_warn(&fmd->v4l2_dev, - "Failed to get I2C adapter %d, deferring probe\n", - si->i2c_bus_num); - return ERR_PTR(-EPROBE_DEFER); - } - sd = v4l2_i2c_new_subdev_board(&fmd->v4l2_dev, adapter, - si->board_info, NULL); - if (IS_ERR_OR_NULL(sd)) { - i2c_put_adapter(adapter); - v4l2_warn(&fmd->v4l2_dev, - "Failed to acquire subdev %s, deferring probe\n", - si->board_info->type); - return ERR_PTR(-EPROBE_DEFER); - } - v4l2_set_subdev_hostdata(sd, si); - sd->grp_id = GRP_ID_SENSOR; - - v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice %s\n", - sd->name); - return sd; -} - -static void fimc_md_unregister_sensor(struct v4l2_subdev *sd) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - struct i2c_adapter *adapter; - - if (!client) - return; - - v4l2_device_unregister_subdev(sd); - - if (!client->dev.of_node) { - adapter = client->adapter; - i2c_unregister_device(client); - if (adapter) - i2c_put_adapter(adapter); - } -} - -#ifdef CONFIG_OF -/* Register I2C client subdev associated with @node. */ -static int fimc_md_of_add_sensor(struct fimc_md *fmd, - struct device_node *node, int index) -{ - struct fimc_sensor_info *si; - struct i2c_client *client; - struct v4l2_subdev *sd; - int ret; - - if (WARN_ON(index >= ARRAY_SIZE(fmd->sensor))) - return -EINVAL; - si = &fmd->sensor[index]; - - client = of_find_i2c_device_by_node(node); - if (!client) - return -EPROBE_DEFER; - - device_lock(&client->dev); - - if (!client->driver || - !try_module_get(client->driver->driver.owner)) { - ret = -EPROBE_DEFER; - v4l2_info(&fmd->v4l2_dev, "No driver found for %s\n", - node->full_name); - goto dev_put; - } - - /* Enable sensor's master clock */ - ret = __fimc_md_set_camclk(fmd, &si->pdata, true); - if (ret < 0) - goto mod_put; - sd = i2c_get_clientdata(client); - - ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd); - __fimc_md_set_camclk(fmd, &si->pdata, false); - if (ret < 0) - goto mod_put; - - v4l2_set_subdev_hostdata(sd, &si->pdata); - if (si->pdata.fimc_bus_type == FIMC_BUS_TYPE_ISP_WRITEBACK) - sd->grp_id = GRP_ID_FIMC_IS_SENSOR; - else - sd->grp_id = GRP_ID_SENSOR; - - si->subdev = sd; - v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice: %s (%d)\n", - sd->name, fmd->num_sensors); - fmd->num_sensors++; - -mod_put: - module_put(client->driver->driver.owner); -dev_put: - device_unlock(&client->dev); - put_device(&client->dev); - return ret; -} - /* Parse port node and register as a sub-device any sensor specified there. */ static int fimc_md_parse_port_node(struct fimc_md *fmd, struct device_node *port, unsigned int index) { + struct fimc_source_info *pd = &fmd->sensor[index].pdata; struct device_node *rem, *ep, *np; - struct fimc_source_info *pd; struct v4l2_of_endpoint endpoint; - int ret; - u32 val; - - pd = &fmd->sensor[index].pdata; /* Assume here a port node can have only one endpoint node. */ ep = of_get_next_child(port, NULL); @@ -469,38 +340,26 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, return 0; v4l2_of_parse_endpoint(ep, &endpoint); - if (WARN_ON(endpoint.port == 0) || index >= FIMC_MAX_SENSORS) + if (WARN_ON(endpoint.base.port == 0) || index >= FIMC_MAX_SENSORS) return -EINVAL; - pd->mux_id = (endpoint.port - 1) & 0x1; + pd->mux_id = (endpoint.base.port - 1) & 0x1; - rem = v4l2_of_get_remote_port_parent(ep); + rem = of_graph_get_remote_port_parent(ep); of_node_put(ep); if (rem == NULL) { v4l2_info(&fmd->v4l2_dev, "Remote device at %s not found\n", ep->full_name); return 0; } - if (!of_property_read_u32(rem, "samsung,camclk-out", &val)) - pd->clk_id = val; - - if (!of_property_read_u32(rem, "clock-frequency", &val)) - pd->clk_frequency = val; - if (pd->clk_frequency == 0) { - v4l2_err(&fmd->v4l2_dev, "Wrong clock frequency at node %s\n", - rem->full_name); - of_node_put(rem); - return -EINVAL; - } - - if (fimc_input_is_parallel(endpoint.port)) { + if (fimc_input_is_parallel(endpoint.base.port)) { if (endpoint.bus_type == V4L2_MBUS_PARALLEL) pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_601; else pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_656; pd->flags = endpoint.bus.parallel.flags; - } else if (fimc_input_is_mipi_csi(endpoint.port)) { + } else if (fimc_input_is_mipi_csi(endpoint.base.port)) { /* * MIPI CSI-2: only input mux selection and * the sensor's clock frequency is needed. @@ -508,7 +367,7 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, pd->sensor_bus_type = FIMC_BUS_TYPE_MIPI_CSI2; } else { v4l2_err(&fmd->v4l2_dev, "Wrong port id (%u) at node %s\n", - endpoint.port, rem->full_name); + endpoint.base.port, rem->full_name); } /* * For FIMC-IS handled sensors, that are placed under i2c-isp device @@ -525,21 +384,40 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, else pd->fimc_bus_type = pd->sensor_bus_type; - ret = fimc_md_of_add_sensor(fmd, rem, index); - of_node_put(rem); + if (WARN_ON(index >= ARRAY_SIZE(fmd->sensor))) + return -EINVAL; - return ret; + fmd->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_OF; + fmd->sensor[index].asd.match.of.node = rem; + fmd->async_subdevs[index] = &fmd->sensor[index].asd; + + fmd->num_sensors++; + + of_node_put(rem); + return 0; } /* Register all SoC external sub-devices */ -static int fimc_md_of_sensors_register(struct fimc_md *fmd, - struct device_node *np) +static int fimc_md_register_sensor_entities(struct fimc_md *fmd) { struct device_node *parent = fmd->pdev->dev.of_node; struct device_node *node, *ports; int index = 0; int ret; + /* + * Runtime resume one of the FIMC entities to make sure + * the sclk_cam clocks are not globally disabled. + */ + if (!fmd->pmf) + return -ENXIO; + + ret = pm_runtime_get_sync(fmd->pmf); + if (ret < 0) + return ret; + + fmd->num_sensors = 0; + /* Attach sensors linked to MIPI CSI-2 receivers */ for_each_available_child_of_node(parent, node) { struct device_node *port; @@ -553,14 +431,14 @@ static int fimc_md_of_sensors_register(struct fimc_md *fmd, ret = fimc_md_parse_port_node(fmd, port, index); if (ret < 0) - return ret; + goto rpm_put; index++; } /* Attach sensors listed in the parallel-ports node */ ports = of_get_child_by_name(parent, "parallel-ports"); if (!ports) - return 0; + goto rpm_put; for_each_child_of_node(ports, node) { ret = fimc_md_parse_port_node(fmd, node, index); @@ -568,8 +446,9 @@ static int fimc_md_of_sensors_register(struct fimc_md *fmd, break; index++; } - - return 0; +rpm_put: + pm_runtime_put(fmd->pmf); + return ret; } static int __of_get_csis_id(struct device_node *np) @@ -582,68 +461,10 @@ static int __of_get_csis_id(struct device_node *np) of_property_read_u32(np, "reg", ®); return reg - FIMC_INPUT_MIPI_CSI2_0; } -#else -#define fimc_md_of_sensors_register(fmd, np) (-ENOSYS) -#define __of_get_csis_id(np) (-ENOSYS) -#endif - -static int fimc_md_register_sensor_entities(struct fimc_md *fmd) -{ - struct s5p_platform_fimc *pdata = fmd->pdev->dev.platform_data; - struct device_node *of_node = fmd->pdev->dev.of_node; - int num_clients = 0; - int ret, i; - - /* - * Runtime resume one of the FIMC entities to make sure - * the sclk_cam clocks are not globally disabled. - */ - if (!fmd->pmf) - return -ENXIO; - - ret = pm_runtime_get_sync(fmd->pmf); - if (ret < 0) - return ret; - - if (of_node) { - fmd->num_sensors = 0; - ret = fimc_md_of_sensors_register(fmd, of_node); - } else if (pdata) { - WARN_ON(pdata->num_clients > ARRAY_SIZE(fmd->sensor)); - num_clients = min_t(u32, pdata->num_clients, - ARRAY_SIZE(fmd->sensor)); - fmd->num_sensors = num_clients; - - for (i = 0; i < num_clients; i++) { - struct fimc_sensor_info *si = &fmd->sensor[i]; - struct v4l2_subdev *sd; - - si->pdata = pdata->source_info[i]; - ret = __fimc_md_set_camclk(fmd, &si->pdata, true); - if (ret) - break; - sd = fimc_md_register_sensor(fmd, &si->pdata); - ret = __fimc_md_set_camclk(fmd, &si->pdata, false); - - if (IS_ERR(sd)) { - si->subdev = NULL; - ret = PTR_ERR(sd); - break; - } - si->subdev = sd; - if (ret) - break; - } - } - - pm_runtime_put(fmd->pmf); - return ret; -} /* * MIPI-CSIS, FIMC and FIMC-LITE platform devices registration. */ - static int register_fimc_lite_entity(struct fimc_md *fmd, struct fimc_lite *fimc_lite) { @@ -732,8 +553,16 @@ static int register_csis_entity(struct fimc_md *fmd, static int register_fimc_is_entity(struct fimc_md *fmd, struct fimc_is *is) { struct v4l2_subdev *sd = &is->isp.subdev; + struct exynos_media_pipeline *ep; int ret; + /* Allocate pipeline object for the ISP capture video node. */ + ep = fimc_md_pipeline_create(fmd); + if (!ep) + return -ENOMEM; + + v4l2_set_subdev_hostdata(sd, ep); + ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd); if (ret) { v4l2_err(&fmd->v4l2_dev, @@ -760,7 +589,7 @@ static int fimc_md_register_platform_entity(struct fimc_md *fmd, goto dev_unlock; drvdata = dev_get_drvdata(dev); - /* Some subdev didn't probe succesfully id drvdata is NULL */ + /* Some subdev didn't probe successfully id drvdata is NULL */ if (drvdata) { switch (plat_entity) { case IDX_FIMC: @@ -792,35 +621,9 @@ dev_unlock: return ret; } -static int fimc_md_pdev_match(struct device *dev, void *data) -{ - struct platform_device *pdev = to_platform_device(dev); - int plat_entity = -1; - int ret; - char *p; - - if (!get_device(dev)) - return -ENODEV; - - if (!strcmp(pdev->name, CSIS_DRIVER_NAME)) { - plat_entity = IDX_CSIS; - } else { - p = strstr(pdev->name, "fimc"); - if (p && *(p + 4) == 0) - plat_entity = IDX_FIMC; - } - - if (plat_entity >= 0) - ret = fimc_md_register_platform_entity(data, pdev, - plat_entity); - put_device(dev); - return 0; -} - /* Register FIMC, FIMC-LITE and CSIS media entities */ -#ifdef CONFIG_OF -static int fimc_md_register_of_platform_entities(struct fimc_md *fmd, - struct device_node *parent) +static int fimc_md_register_platform_entities(struct fimc_md *fmd, + struct device_node *parent) { struct device_node *node; int ret = 0; @@ -854,9 +657,6 @@ static int fimc_md_register_of_platform_entities(struct fimc_md *fmd, return ret; } -#else -#define fimc_md_register_of_platform_entities(fmd, node) (-ENOSYS) -#endif static void fimc_md_unregister_entities(struct fimc_md *fmd) { @@ -884,12 +684,6 @@ static void fimc_md_unregister_entities(struct fimc_md *fmd) v4l2_device_unregister_subdev(fmd->csis[i].sd); fmd->csis[i].sd = NULL; } - for (i = 0; i < fmd->num_sensors; i++) { - if (fmd->sensor[i].subdev == NULL) - continue; - fimc_md_unregister_sensor(fmd->sensor[i].subdev); - fmd->sensor[i].subdev = NULL; - } if (fmd->fimc_is) v4l2_device_unregister_subdev(&fmd->fimc_is->isp.subdev); @@ -1005,16 +799,17 @@ static int __fimc_md_create_flite_source_links(struct fimc_md *fmd) /* Create FIMC-IS links */ static int __fimc_md_create_fimc_is_links(struct fimc_md *fmd) { + struct fimc_isp *isp = &fmd->fimc_is->isp; struct media_entity *source, *sink; int i, ret; - source = &fmd->fimc_is->isp.subdev.entity; + source = &isp->subdev.entity; for (i = 0; i < FIMC_MAX_DEVS; i++) { if (fmd->fimc[i] == NULL) continue; - /* Link from IS-ISP subdev to FIMC */ + /* Link from FIMC-IS-ISP subdev to FIMC */ sink = &fmd->fimc[i]->vid_cap.subdev.entity; ret = media_entity_create_link(source, FIMC_ISP_SD_PAD_SRC_FIFO, sink, FIMC_SD_PAD_SINK_FIFO, 0); @@ -1022,7 +817,15 @@ static int __fimc_md_create_fimc_is_links(struct fimc_md *fmd) return ret; } - return ret; + /* Link from FIMC-IS-ISP subdev to fimc-is-isp.capture video node */ + sink = &isp->video_capture.ve.vdev.entity; + + /* Skip this link if the fimc-is-isp video node driver isn't built-in */ + if (sink->num_pads == 0) + return 0; + + return media_entity_create_link(source, FIMC_ISP_SD_PAD_SRC_DMA, + sink, 0, 0); } /** @@ -1150,7 +953,6 @@ static void fimc_md_put_clocks(struct fimc_md *fmd) while (--i >= 0) { if (IS_ERR(fmd->camclk[i].clock)) continue; - clk_unprepare(fmd->camclk[i].clock); clk_put(fmd->camclk[i].clock); fmd->camclk[i].clock = ERR_PTR(-EINVAL); } @@ -1166,33 +968,23 @@ static void fimc_md_put_clocks(struct fimc_md *fmd) static int fimc_md_get_clocks(struct fimc_md *fmd) { - struct device *dev = NULL; + struct device *dev = &fmd->pdev->dev; char clk_name[32]; struct clk *clock; - int ret, i; + int i, ret = 0; for (i = 0; i < FIMC_MAX_CAMCLKS; i++) fmd->camclk[i].clock = ERR_PTR(-EINVAL); - if (fmd->pdev->dev.of_node) - dev = &fmd->pdev->dev; - for (i = 0; i < FIMC_MAX_CAMCLKS; i++) { snprintf(clk_name, sizeof(clk_name), "sclk_cam%u", i); clock = clk_get(dev, clk_name); if (IS_ERR(clock)) { - dev_err(&fmd->pdev->dev, "Failed to get clock: %s\n", - clk_name); + dev_err(dev, "Failed to get clock: %s\n", clk_name); ret = PTR_ERR(clock); break; } - ret = clk_prepare(clock); - if (ret < 0) { - clk_put(clock); - fmd->camclk[i].clock = ERR_PTR(-EINVAL); - break; - } fmd->camclk[i].clock = clock; } if (ret) @@ -1223,70 +1015,6 @@ static int fimc_md_get_clocks(struct fimc_md *fmd) return ret; } -static int __fimc_md_set_camclk(struct fimc_md *fmd, - struct fimc_source_info *si, - bool on) -{ - struct fimc_camclk_info *camclk; - int ret = 0; - - if (WARN_ON(si->clk_id >= FIMC_MAX_CAMCLKS) || !fmd || !fmd->pmf) - return -EINVAL; - - camclk = &fmd->camclk[si->clk_id]; - - dbg("camclk %d, f: %lu, use_count: %d, on: %d", - si->clk_id, si->clk_frequency, camclk->use_count, on); - - if (on) { - if (camclk->use_count > 0 && - camclk->frequency != si->clk_frequency) - return -EINVAL; - - if (camclk->use_count++ == 0) { - clk_set_rate(camclk->clock, si->clk_frequency); - camclk->frequency = si->clk_frequency; - ret = pm_runtime_get_sync(fmd->pmf); - if (ret < 0) - return ret; - ret = clk_enable(camclk->clock); - dbg("Enabled camclk %d: f: %lu", si->clk_id, - clk_get_rate(camclk->clock)); - } - return ret; - } - - if (WARN_ON(camclk->use_count == 0)) - return 0; - - if (--camclk->use_count == 0) { - clk_disable(camclk->clock); - pm_runtime_put(fmd->pmf); - dbg("Disabled camclk %d", si->clk_id); - } - return ret; -} - -/** - * fimc_md_set_camclk - peripheral sensor clock setup - * @sd: sensor subdev to configure sclk_cam clock for - * @on: 1 to enable or 0 to disable the clock - * - * There are 2 separate clock outputs available in the SoC for external - * image processors. These clocks are shared between all registered FIMC - * devices to which sensors can be attached, either directly or through - * the MIPI CSI receiver. The clock is allowed here to be used by - * multiple sensors concurrently if they use same frequency. - * This function should only be called when the graph mutex is held. - */ -int fimc_md_set_camclk(struct v4l2_subdev *sd, bool on) -{ - struct fimc_source_info *si = v4l2_get_subdev_hostdata(sd); - struct fimc_md *fmd = entity_to_fimc_mdev(&sd->entity); - - return __fimc_md_set_camclk(fmd, si, on); -} - static int __fimc_md_modify_pipeline(struct media_entity *entity, bool enable) { struct exynos_video_entity *ve; @@ -1445,6 +1173,148 @@ static int fimc_md_get_pinctrl(struct fimc_md *fmd) return 0; } +static int cam_clk_prepare(struct clk_hw *hw) +{ + struct cam_clk *camclk = to_cam_clk(hw); + int ret; + + if (camclk->fmd->pmf == NULL) + return -ENODEV; + + ret = pm_runtime_get_sync(camclk->fmd->pmf); + return ret < 0 ? ret : 0; +} + +static void cam_clk_unprepare(struct clk_hw *hw) +{ + struct cam_clk *camclk = to_cam_clk(hw); + + if (camclk->fmd->pmf == NULL) + return; + + pm_runtime_put_sync(camclk->fmd->pmf); +} + +static const struct clk_ops cam_clk_ops = { + .prepare = cam_clk_prepare, + .unprepare = cam_clk_unprepare, +}; + +static void fimc_md_unregister_clk_provider(struct fimc_md *fmd) +{ + struct cam_clk_provider *cp = &fmd->clk_provider; + unsigned int i; + + if (cp->of_node) + of_clk_del_provider(cp->of_node); + + for (i = 0; i < cp->num_clocks; i++) + clk_unregister(cp->clks[i]); +} + +static int fimc_md_register_clk_provider(struct fimc_md *fmd) +{ + struct cam_clk_provider *cp = &fmd->clk_provider; + struct device *dev = &fmd->pdev->dev; + int i, ret; + + for (i = 0; i < FIMC_MAX_CAMCLKS; i++) { + struct cam_clk *camclk = &cp->camclk[i]; + struct clk_init_data init; + const char *p_name; + + ret = of_property_read_string_index(dev->of_node, + "clock-output-names", i, &init.name); + if (ret < 0) + break; + + p_name = __clk_get_name(fmd->camclk[i].clock); + + /* It's safe since clk_register() will duplicate the string. */ + init.parent_names = &p_name; + init.num_parents = 1; + init.ops = &cam_clk_ops; + init.flags = CLK_SET_RATE_PARENT; + camclk->hw.init = &init; + camclk->fmd = fmd; + + cp->clks[i] = clk_register(NULL, &camclk->hw); + if (IS_ERR(cp->clks[i])) { + dev_err(dev, "failed to register clock: %s (%ld)\n", + init.name, PTR_ERR(cp->clks[i])); + ret = PTR_ERR(cp->clks[i]); + goto err; + } + cp->num_clocks++; + } + + if (cp->num_clocks == 0) { + dev_warn(dev, "clk provider not registered\n"); + return 0; + } + + cp->clk_data.clks = cp->clks; + cp->clk_data.clk_num = cp->num_clocks; + cp->of_node = dev->of_node; + ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, + &cp->clk_data); + if (ret == 0) + return 0; +err: + fimc_md_unregister_clk_provider(fmd); + return ret; +} + +static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct fimc_md *fmd = notifier_to_fimc_md(notifier); + struct fimc_sensor_info *si = NULL; + int i; + + /* Find platform data for this sensor subdev */ + for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++) + if (fmd->sensor[i].asd.match.of.node == subdev->dev->of_node) + si = &fmd->sensor[i]; + + if (si == NULL) + return -EINVAL; + + v4l2_set_subdev_hostdata(subdev, &si->pdata); + + if (si->pdata.fimc_bus_type == FIMC_BUS_TYPE_ISP_WRITEBACK) + subdev->grp_id = GRP_ID_FIMC_IS_SENSOR; + else + subdev->grp_id = GRP_ID_SENSOR; + + si->subdev = subdev; + + v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice: %s (%d)\n", + subdev->name, fmd->num_sensors); + + fmd->num_sensors++; + + return 0; +} + +static int subdev_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct fimc_md *fmd = notifier_to_fimc_md(notifier); + int ret; + + mutex_lock(&fmd->media_dev.graph_mutex); + + ret = fimc_md_create_links(fmd); + if (ret < 0) + goto unlock; + + ret = v4l2_device_register_subdev_nodes(&fmd->v4l2_dev); +unlock: + mutex_unlock(&fmd->media_dev.graph_mutex); + return ret; +} + static int fimc_md_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1457,8 +1327,8 @@ static int fimc_md_probe(struct platform_device *pdev) return -ENOMEM; spin_lock_init(&fmd->slock); - fmd->pdev = pdev; INIT_LIST_HEAD(&fmd->pipelines); + fmd->pdev = pdev; strlcpy(fmd->media_dev.model, "SAMSUNG S5P FIMC", sizeof(fmd->media_dev.model)); @@ -1471,69 +1341,90 @@ static int fimc_md_probe(struct platform_device *pdev) strlcpy(v4l2_dev->name, "s5p-fimc-md", sizeof(v4l2_dev->name)); fmd->use_isp = fimc_md_is_isp_available(dev->of_node); + fmd->user_subdev_api = true; ret = v4l2_device_register(dev, &fmd->v4l2_dev); if (ret < 0) { v4l2_err(v4l2_dev, "Failed to register v4l2_device: %d\n", ret); return ret; } + ret = media_device_register(&fmd->media_dev); if (ret < 0) { v4l2_err(v4l2_dev, "Failed to register media device: %d\n", ret); - goto err_md; + goto err_v4l2_dev; } + ret = fimc_md_get_clocks(fmd); if (ret) - goto err_clk; - - fmd->user_subdev_api = (dev->of_node != NULL); - - /* Protect the media graph while we're registering entities */ - mutex_lock(&fmd->media_dev.graph_mutex); + goto err_md; ret = fimc_md_get_pinctrl(fmd); if (ret < 0) { if (ret != EPROBE_DEFER) dev_err(dev, "Failed to get pinctrl: %d\n", ret); - goto err_unlock; + goto err_clk; } - if (dev->of_node) - ret = fimc_md_register_of_platform_entities(fmd, dev->of_node); - else - ret = bus_for_each_dev(&platform_bus_type, NULL, fmd, - fimc_md_pdev_match); - if (ret) - goto err_unlock; + platform_set_drvdata(pdev, fmd); - if (dev->platform_data || dev->of_node) { - ret = fimc_md_register_sensor_entities(fmd); - if (ret) - goto err_unlock; + /* Protect the media graph while we're registering entities */ + mutex_lock(&fmd->media_dev.graph_mutex); + + ret = fimc_md_register_platform_entities(fmd, dev->of_node); + if (ret) { + mutex_unlock(&fmd->media_dev.graph_mutex); + goto err_clk; } - ret = fimc_md_create_links(fmd); - if (ret) - goto err_unlock; - ret = v4l2_device_register_subdev_nodes(&fmd->v4l2_dev); - if (ret) - goto err_unlock; + ret = fimc_md_register_sensor_entities(fmd); + if (ret) { + mutex_unlock(&fmd->media_dev.graph_mutex); + goto err_m_ent; + } + + mutex_unlock(&fmd->media_dev.graph_mutex); ret = device_create_file(&pdev->dev, &dev_attr_subdev_conf_mode); if (ret) - goto err_unlock; + goto err_m_ent; + /* + * FIMC platform devices need to be registered before the sclk_cam + * clocks provider, as one of these devices needs to be activated + * to enable the clock. + */ + ret = fimc_md_register_clk_provider(fmd); + if (ret < 0) { + v4l2_err(v4l2_dev, "clock provider registration failed\n"); + goto err_attr; + } + + if (fmd->num_sensors > 0) { + fmd->subdev_notifier.subdevs = fmd->async_subdevs; + fmd->subdev_notifier.num_subdevs = fmd->num_sensors; + fmd->subdev_notifier.bound = subdev_notifier_bound; + fmd->subdev_notifier.complete = subdev_notifier_complete; + fmd->num_sensors = 0; + + ret = v4l2_async_notifier_register(&fmd->v4l2_dev, + &fmd->subdev_notifier); + if (ret) + goto err_clk_p; + } - platform_set_drvdata(pdev, fmd); - mutex_unlock(&fmd->media_dev.graph_mutex); return 0; -err_unlock: - mutex_unlock(&fmd->media_dev.graph_mutex); +err_clk_p: + fimc_md_unregister_clk_provider(fmd); +err_attr: + device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode); err_clk: - media_device_unregister(&fmd->media_dev); fimc_md_put_clocks(fmd); +err_m_ent: fimc_md_unregister_entities(fmd); err_md: + media_device_unregister(&fmd->media_dev); +err_v4l2_dev: v4l2_device_unregister(&fmd->v4l2_dev); return ret; } @@ -1544,11 +1435,17 @@ static int fimc_md_remove(struct platform_device *pdev) if (!fmd) return 0; + + fimc_md_unregister_clk_provider(fmd); + v4l2_async_notifier_unregister(&fmd->subdev_notifier); + + v4l2_device_unregister(&fmd->v4l2_dev); device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode); fimc_md_unregister_entities(fmd); fimc_md_pipelines_free(fmd); media_device_unregister(&fmd->media_dev); fimc_md_put_clocks(fmd); + return 0; } diff --git a/drivers/media/platform/exynos4-is/media-dev.h b/drivers/media/platform/exynos4-is/media-dev.h index 62599fd7756..03214541f14 100644 --- a/drivers/media/platform/exynos4-is/media-dev.h +++ b/drivers/media/platform/exynos4-is/media-dev.h @@ -10,6 +10,7 @@ #define FIMC_MDEVICE_H_ #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/platform_device.h> #include <linux/mutex.h> #include <linux/of.h> @@ -18,7 +19,7 @@ #include <media/media-entity.h> #include <media/v4l2-device.h> #include <media/v4l2-subdev.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> #include "fimc-core.h" #include "fimc-lite.h" @@ -31,8 +32,9 @@ #define PINCTRL_STATE_IDLE "idle" -#define FIMC_MAX_SENSORS 8 +#define FIMC_MAX_SENSORS 4 #define FIMC_MAX_CAMCLKS 2 +#define DEFAULT_SENSOR_CLK_FREQ 24000000U /* LCD/ISP Writeback clocks (PIXELASYNCMx) */ enum { @@ -78,6 +80,7 @@ struct fimc_camclk_info { /** * struct fimc_sensor_info - image data source subdev information * @pdata: sensor's atrributes passed as media device's platform data + * @asd: asynchronous subdev registration data structure * @subdev: image sensor v4l2 subdev * @host: fimc device the sensor is currently linked to * @@ -85,10 +88,17 @@ struct fimc_camclk_info { */ struct fimc_sensor_info { struct fimc_source_info pdata; + struct v4l2_async_subdev asd; struct v4l2_subdev *subdev; struct fimc_dev *host; }; +struct cam_clk { + struct clk_hw hw; + struct fimc_md *fmd; +}; +#define to_cam_clk(_hw) container_of(_hw, struct cam_clk, hw) + /** * struct fimc_md - fimc media device information * @csis: MIPI CSIS subdevs data @@ -105,6 +115,7 @@ struct fimc_sensor_info { * @pinctrl: camera port pinctrl handle * @state_default: pinctrl default state handle * @state_idle: pinctrl idle state handle + * @cam_clk_provider: CAMCLK clock provider structure * @user_subdev_api: true if subdevs are not configured by the host driver * @slock: spinlock protecting @sensor array */ @@ -122,13 +133,25 @@ struct fimc_md { struct media_device media_dev; struct v4l2_device v4l2_dev; struct platform_device *pdev; + struct fimc_pinctrl { struct pinctrl *pinctrl; struct pinctrl_state *state_default; struct pinctrl_state *state_idle; } pinctl; - bool user_subdev_api; + struct cam_clk_provider { + struct clk *clks[FIMC_MAX_CAMCLKS]; + struct clk_onecell_data clk_data; + struct device_node *of_node; + struct cam_clk camclk[FIMC_MAX_CAMCLKS]; + int num_clocks; + } clk_provider; + + struct v4l2_async_notifier subdev_notifier; + struct v4l2_async_subdev *async_subdevs[FIMC_MAX_SENSORS]; + + bool user_subdev_api; spinlock_t slock; struct list_head pipelines; }; @@ -145,6 +168,11 @@ static inline struct fimc_md *entity_to_fimc_mdev(struct media_entity *me) container_of(me->parent, struct fimc_md, media_dev); } +static inline struct fimc_md *notifier_to_fimc_md(struct v4l2_async_notifier *n) +{ + return container_of(n, struct fimc_md, subdev_notifier); +} + static inline void fimc_md_graph_lock(struct exynos_video_entity *ve) { mutex_lock(&ve->vdev.entity.parent->graph_mutex); diff --git a/drivers/media/platform/exynos4-is/mipi-csis.c b/drivers/media/platform/exynos4-is/mipi-csis.c index 0914230b42d..ae54ef5f535 100644 --- a/drivers/media/platform/exynos4-is/mipi-csis.c +++ b/drivers/media/platform/exynos4-is/mipi-csis.c @@ -20,14 +20,15 @@ #include <linux/memory.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/platform_data/mipi-csis.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/videodev2.h> -#include <media/s5p_fimc.h> +#include <media/exynos-fimc.h> #include <media/v4l2-of.h> #include <media/v4l2-subdev.h> @@ -90,7 +91,7 @@ MODULE_PARM_DESC(debug, "Debug level (0-2)"); #define S5PCSIS_INTSRC_ODD_BEFORE (1 << 29) #define S5PCSIS_INTSRC_ODD_AFTER (1 << 28) #define S5PCSIS_INTSRC_ODD (0x3 << 28) -#define S5PCSIS_INTSRC_NON_IMAGE_DATA (0xff << 28) +#define S5PCSIS_INTSRC_NON_IMAGE_DATA (0xf << 28) #define S5PCSIS_INTSRC_FRAME_START (1 << 27) #define S5PCSIS_INTSRC_FRAME_END (1 << 26) #define S5PCSIS_INTSRC_ERR_SOT_HS (0xf << 12) @@ -180,6 +181,7 @@ struct csis_drvdata { * @sd: v4l2_subdev associated with CSIS device instance * @index: the hardware instance index * @pdev: CSIS platform device + * @phy: pointer to the CSIS generic PHY * @regs: mmaped I/O registers memory * @supplies: CSIS regulator supplies * @clock: CSIS clocks @@ -203,6 +205,7 @@ struct csis_state { struct v4l2_subdev sd; u8 index; struct platform_device *pdev; + struct phy *phy; void __iomem *regs; struct regulator_bulk_data supplies[CSIS_NUM_SUPPLIES]; struct clk *clock[NUM_CSIS_CLOCKS]; @@ -726,26 +729,6 @@ static irqreturn_t s5pcsis_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } -static int s5pcsis_get_platform_data(struct platform_device *pdev, - struct csis_state *state) -{ - struct s5p_platform_mipi_csis *pdata = pdev->dev.platform_data; - - if (pdata == NULL) { - dev_err(&pdev->dev, "Platform data not specified\n"); - return -EINVAL; - } - - state->clk_frequency = pdata->clk_rate; - state->num_lanes = pdata->lanes; - state->hs_settle = pdata->hs_settle; - state->index = max(0, pdev->id); - state->max_num_lanes = state->index ? CSIS1_MAX_LANES : - CSIS0_MAX_LANES; - return 0; -} - -#ifdef CONFIG_OF static int s5pcsis_parse_dt(struct platform_device *pdev, struct csis_state *state) { @@ -759,7 +742,7 @@ static int s5pcsis_parse_dt(struct platform_device *pdev, &state->max_num_lanes)) return -EINVAL; - node = v4l2_of_get_next_endpoint(node, NULL); + node = of_graph_get_next_endpoint(node, NULL); if (!node) { dev_err(&pdev->dev, "No port node at %s\n", pdev->dev.of_node->full_name); @@ -768,7 +751,7 @@ static int s5pcsis_parse_dt(struct platform_device *pdev, /* Get port node and validate MIPI-CSI channel id. */ v4l2_of_parse_endpoint(node, &endpoint); - state->index = endpoint.port - FIMC_INPUT_MIPI_CSI2_0; + state->index = endpoint.base.port - FIMC_INPUT_MIPI_CSI2_0; if (state->index < 0 || state->index >= CSIS_MAX_ENTITIES) return -ENXIO; @@ -779,14 +762,12 @@ static int s5pcsis_parse_dt(struct platform_device *pdev, "samsung,csis-wclk"); state->num_lanes = endpoint.bus.mipi_csi2.num_data_lanes; - of_node_put(node); + return 0; } -#else -#define s5pcsis_parse_dt(pdev, state) (-ENOSYS) -#endif +static int s5pcsis_pm_resume(struct device *dev, bool runtime); static const struct of_device_id s5pcsis_of_match[]; static int s5pcsis_probe(struct platform_device *pdev) @@ -807,19 +788,14 @@ static int s5pcsis_probe(struct platform_device *pdev) spin_lock_init(&state->slock); state->pdev = pdev; - if (dev->of_node) { - of_id = of_match_node(s5pcsis_of_match, dev->of_node); - if (WARN_ON(of_id == NULL)) - return -EINVAL; - - drv_data = of_id->data; - state->interrupt_mask = drv_data->interrupt_mask; + of_id = of_match_node(s5pcsis_of_match, dev->of_node); + if (WARN_ON(of_id == NULL)) + return -EINVAL; - ret = s5pcsis_parse_dt(pdev, state); - } else { - ret = s5pcsis_get_platform_data(pdev, state); - } + drv_data = of_id->data; + state->interrupt_mask = drv_data->interrupt_mask; + ret = s5pcsis_parse_dt(pdev, state); if (ret < 0) return ret; @@ -829,6 +805,10 @@ static int s5pcsis_probe(struct platform_device *pdev) return -EINVAL; } + state->phy = devm_phy_get(dev, "csis"); + if (IS_ERR(state->phy)) + return PTR_ERR(state->phy); + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); state->regs = devm_ioremap_resource(dev, mem_res); if (IS_ERR(state->regs)) @@ -895,13 +875,21 @@ static int s5pcsis_probe(struct platform_device *pdev) /* .. and a pointer to the subdev. */ platform_set_drvdata(pdev, &state->sd); memcpy(state->events, s5pcsis_events, sizeof(state->events)); + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = s5pcsis_pm_resume(dev, true); + if (ret < 0) + goto e_m_ent; + } dev_info(&pdev->dev, "lanes: %d, hs_settle: %d, wclk: %d, freq: %u\n", state->num_lanes, state->hs_settle, state->wclk_ext, state->clk_frequency); return 0; +e_m_ent: + media_entity_cleanup(&state->sd.entity); e_clkdis: clk_disable(state->clock[CSIS_CLK_MUX]); e_clkput: @@ -922,7 +910,7 @@ static int s5pcsis_pm_suspend(struct device *dev, bool runtime) mutex_lock(&state->lock); if (state->flags & ST_POWERED) { s5pcsis_stop_stream(state); - ret = s5p_csis_phy_enable(state->index, false); + ret = phy_power_off(state->phy); if (ret) goto unlock; ret = regulator_bulk_disable(CSIS_NUM_SUPPLIES, @@ -958,7 +946,7 @@ static int s5pcsis_pm_resume(struct device *dev, bool runtime) state->supplies); if (ret) goto unlock; - ret = s5p_csis_phy_enable(state->index, true); + ret = phy_power_on(state->phy); if (!ret) { state->flags |= ST_POWERED; } else { @@ -1007,7 +995,7 @@ static int s5pcsis_remove(struct platform_device *pdev) struct csis_state *state = sd_to_csis_state(sd); pm_runtime_disable(&pdev->dev); - s5pcsis_pm_suspend(&pdev->dev, false); + s5pcsis_pm_suspend(&pdev->dev, true); clk_disable(state->clock[CSIS_CLK_MUX]); pm_runtime_set_suspended(&pdev->dev); s5pcsis_clk_put(state); |
