diff options
Diffstat (limited to 'drivers/media/platform/exynos4-is')
19 files changed, 1155 insertions, 489 deletions
diff --git a/drivers/media/platform/exynos4-is/Kconfig b/drivers/media/platform/exynos4-is/Kconfig index 01ed1ecdff7e..e1b2ceba00c1 100644 --- a/drivers/media/platform/exynos4-is/Kconfig +++ b/drivers/media/platform/exynos4-is/Kconfig @@ -64,4 +64,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 c2ff29ba6856..eed1b185d813 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/fimc-capture.c b/drivers/media/platform/exynos4-is/fimc-capture.c index 8a712ca91d11..92ae812abce2 100644 --- a/drivers/media/platform/exynos4-is/fimc-capture.c +++ b/drivers/media/platform/exynos4-is/fimc-capture.c @@ -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-is-param.c b/drivers/media/platform/exynos4-is/fimc-is-param.c index 9bf3ddd9e028..bf1465d1bf6d 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-param.c +++ b/drivers/media/platform/exynos4-is/fimc-is-param.c @@ -56,7 +56,7 @@ static void __fimc_is_hw_update_param_sensor_framerate(struct fimc_is *is) __hw_param_copy(dst, src); } -static int __fimc_is_hw_update_param(struct fimc_is *is, u32 offset) +int __fimc_is_hw_update_param(struct fimc_is *is, u32 offset) { struct is_param_region *par = &is->is_p_region->parameter; struct chain_config *cfg = &is->config[is->config_index]; diff --git a/drivers/media/platform/exynos4-is/fimc-is-param.h b/drivers/media/platform/exynos4-is/fimc-is-param.h index f9358c27ae2d..8e31f7642776 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 2628733c4e10..cfe4406a83ff 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-regs.c +++ b/drivers/media/platform/exynos4-is/fimc-is-regs.c @@ -105,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); @@ -112,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) diff --git a/drivers/media/platform/exynos4-is/fimc-is-regs.h b/drivers/media/platform/exynos4-is/fimc-is-regs.h index 1d9d4ffc6ad5..141e5ddadbeb 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-regs.h +++ b/drivers/media/platform/exynos4-is/fimc-is-regs.h @@ -147,6 +147,7 @@ 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_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 6647421e5d3a..10e82e21b5d1 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 6036d49a6c68..173ccffa4bcd 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 9bdfa4599bc3..128b73b6cce2 100644 --- a/drivers/media/platform/exynos4-is/fimc-is.c +++ b/drivers/media/platform/exynos4-is/fimc-is.c @@ -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 = of_graph_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 = of_graph_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; } @@ -647,7 +638,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 +652,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; @@ -977,27 +968,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); } diff --git a/drivers/media/platform/exynos4-is/fimc-is.h b/drivers/media/platform/exynos4-is/fimc-is.h index 61bb0127e19d..e0be691af2d3 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 000000000000..e92b4e115adb --- /dev/null +++ b/drivers/media/platform/exynos4-is/fimc-isp-video.c @@ -0,0 +1,660 @@ +/* + * 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/s5p_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 int 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 ret; + + 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; + return 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 000000000000..98c662654bb6 --- /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 f3c6136aa5b4..be62d6b9ac48 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, }; diff --git a/drivers/media/platform/exynos4-is/fimc-isp.h b/drivers/media/platform/exynos4-is/fimc-isp.h index 03bf95ab017b..4dc55a18d978 100644 --- a/drivers/media/platform/exynos4-is/fimc-isp.h +++ b/drivers/media/platform/exynos4-is/fimc-isp.h @@ -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.c b/drivers/media/platform/exynos4-is/fimc-lite.c index 779ec3cd259d..3ad660b55b6b 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite.c +++ b/drivers/media/platform/exynos4-is/fimc-lite.c @@ -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); diff --git a/drivers/media/platform/exynos4-is/fimc-m2m.c b/drivers/media/platform/exynos4-is/fimc-m2m.c index 9da95bd14820..36971d915b53 100644 --- a/drivers/media/platform/exynos4-is/fimc-m2m.c +++ b/drivers/media/platform/exynos4-is/fimc-m2m.c @@ -134,6 +134,9 @@ static void fimc_device_run(void *priv) 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) { @@ -557,7 +560,7 @@ 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); @@ -570,7 +573,7 @@ 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); diff --git a/drivers/media/platform/exynos4-is/media-dev.c b/drivers/media/platform/exynos4-is/media-dev.c index 04d6ecdd314c..e62211a80f0e 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> @@ -25,6 +27,7 @@ #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> @@ -219,6 +222,7 @@ 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; @@ -379,77 +383,18 @@ static void fimc_md_unregister_sensor(struct v4l2_subdev *sd) struct i2c_client *client = v4l2_get_subdevdata(sd); struct i2c_adapter *adapter; - if (!client) + if (!client || client->dev.of_node) return; v4l2_device_unregister_subdev(sd); - if (!client->dev.of_node) { - adapter = client->adapter; - i2c_unregister_device(client); - if (adapter) - i2c_put_adapter(adapter); - } + 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->dev.driver || - !try_module_get(client->dev.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->dev.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, @@ -458,7 +403,6 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, 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; @@ -486,6 +430,8 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, if (!of_property_read_u32(rem, "clock-frequency", &val)) pd->clk_frequency = val; + else + pd->clk_frequency = DEFAULT_SENSOR_CLK_FREQ; if (pd->clk_frequency == 0) { v4l2_err(&fmd->v4l2_dev, "Wrong clock frequency at node %s\n", @@ -525,10 +471,17 @@ 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 */ @@ -732,8 +685,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, @@ -884,11 +845,13 @@ 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->pdev->dev.of_node == 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) @@ -1005,16 +968,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 +986,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); } /** @@ -1223,6 +1195,14 @@ static int __fimc_md_set_camclk(struct fimc_md *fmd, struct fimc_camclk_info *camclk; int ret = 0; + /* + * When device tree is used the sensor drivers are supposed to + * control the clock themselves. This whole function will be + * removed once S5PV210 platform is converted to the device tree. + */ + if (fmd->pdev->dev.of_node) + return 0; + if (WARN_ON(si->clk_id >= FIMC_MAX_CAMCLKS) || !fmd || !fmd->pmf) return -EINVAL; @@ -1277,6 +1257,14 @@ 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); + /* + * If there is a clock provider registered the sensors will + * handle their clock themselves, no need to control it on + * the host interface side. + */ + if (fmd->clk_provider.num_clocks > 0) + return 0; + return __fimc_md_set_camclk(fmd, si, on); } @@ -1438,6 +1426,153 @@ static int fimc_md_get_pinctrl(struct fimc_md *fmd) return 0; } +#ifdef CONFIG_OF +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; +} +#else +#define fimc_md_register_clk_provider(fmd) (0) +#define fimc_md_unregister_clk_provider(fmd) (0) +#endif + +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; @@ -1470,63 +1605,91 @@ static int fimc_md_probe(struct platform_device *pdev) 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; + goto err_md; fmd->user_subdev_api = (dev->of_node != NULL); - /* Protect the media graph while we're registering entities */ - mutex_lock(&fmd->media_dev.graph_mutex); - 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; } + platform_set_drvdata(pdev, fmd); + + /* Protect the media graph while we're registering entities */ + mutex_lock(&fmd->media_dev.graph_mutex); + 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; + if (ret) { + mutex_unlock(&fmd->media_dev.graph_mutex); + goto err_clk; + } if (dev->platform_data || dev->of_node) { ret = fimc_md_register_sensor_entities(fmd); - if (ret) - goto err_unlock; + if (ret) { + mutex_unlock(&fmd->media_dev.graph_mutex); + goto err_m_ent; + } } - 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; + 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: fimc_md_put_clocks(fmd); +err_m_ent: fimc_md_unregister_entities(fmd); - media_device_unregister(&fmd->media_dev); err_md: + media_device_unregister(&fmd->media_dev); +err_v4l2_dev: v4l2_device_unregister(&fmd->v4l2_dev); return ret; } @@ -1538,12 +1701,16 @@ 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 62599fd7756f..ee1e2519f728 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> @@ -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); |