Merge branch 'topic/exynos' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media

Pull exynos media updates from Mauro Carvalho Chehab:
 "These are the remaining patches I have for the merge windows.  It
  basically adds a new sensor and adds the needed DT bits for it to
  work"

* 'topic/exynos' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media:
  [media] s5p-fimc: Remove reference to outdated macro
  [media] s5p-jpeg: Fix broken indentation in jpeg-regs.h
  [media] exynos4-is: Add the FIMC-IS ISP capture DMA driver
  [media] exynos4-is: Add support for asynchronous subdevices registration
  [media] exynos4-is: Add clock provider for the SCLK_CAM clock outputs
  [media] exynos4-is: Use external s5k6a3 sensor driver
  [media] V4L: s5c73m3: Add device tree support
  [media] V4L: Add driver for s5k6a3 image sensor
  [media] Documentation: devicetree: Update Samsung FIMC DT binding
  [media] Documentation: dt: Add binding documentation for S5C73M3 camera
  [media] Documentation: dt: Add binding documentation for S5K6A3 image sensor
This commit is contained in:
Linus Torvalds 2014-04-05 13:10:00 -07:00
commit 463b21fb27
27 changed files with 1888 additions and 567 deletions

View File

@ -15,11 +15,21 @@ Common 'camera' node
Required properties:
- compatible : must be "samsung,fimc", "simple-bus"
- clocks : list of clock specifiers, corresponding to entries in
the clock-names property;
- clock-names : must contain "sclk_cam0", "sclk_cam1", "pxl_async0",
"pxl_async1" entries, matching entries in the clocks property.
- compatible: must be "samsung,fimc", "simple-bus"
- clocks: list of clock specifiers, corresponding to entries in
the clock-names property;
- clock-names : must contain "sclk_cam0", "sclk_cam1", "pxl_async0",
"pxl_async1" entries, matching entries in the clocks property.
- #clock-cells: from the common clock bindings (../clock/clock-bindings.txt),
must be 1. A clock provider is associated with the 'camera' node and it should
be referenced by external sensors that use clocks provided by the SoC on
CAM_*_CLKOUT pins. The clock specifier cell stores an index of a clock.
The indices are 0, 1 for CAM_A_CLKOUT, CAM_B_CLKOUT clocks respectively.
- clock-output-names: from the common clock bindings, should contain names of
clocks registered by the camera subsystem corresponding to CAM_A_CLKOUT,
CAM_B_CLKOUT output clocks respectively.
The pinctrl bindings defined in ../pinctrl/pinctrl-bindings.txt must be used
to define a required pinctrl state named "default" and optional pinctrl states:
@ -32,6 +42,7 @@ way around.
The 'camera' node must include at least one 'fimc' child node.
'fimc' device nodes
-------------------
@ -88,8 +99,8 @@ port nodes specifies data input - 0, 1 indicates input A, B respectively.
Optional properties
- samsung,camclk-out : specifies clock output for remote sensor,
0 - CAM_A_CLKOUT, 1 - CAM_B_CLKOUT;
- samsung,camclk-out (deprecated) : specifies clock output for remote sensor,
0 - CAM_A_CLKOUT, 1 - CAM_B_CLKOUT;
Image sensor nodes
------------------
@ -97,8 +108,6 @@ Image sensor nodes
The sensor device nodes should be added to their control bus controller (e.g.
I2C0) nodes and linked to a port node in the csis or the parallel-ports node,
using the common video interfaces bindings, defined in video-interfaces.txt.
The implementation of this bindings requires clock-frequency property to be
present in the sensor device nodes.
Example:
@ -114,7 +123,7 @@ Example:
vddio-supply = <...>;
clock-frequency = <24000000>;
clocks = <...>;
clocks = <&camera 1>;
clock-names = "mclk";
port {
@ -135,7 +144,7 @@ Example:
vddio-supply = <...>;
clock-frequency = <24000000>;
clocks = <...>;
clocks = <&camera 0>;
clock-names = "mclk";
port {
@ -149,12 +158,17 @@ Example:
camera {
compatible = "samsung,fimc", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
status = "okay";
clocks = <&clock 132>, <&clock 133>, <&clock 351>,
<&clock 352>;
clock-names = "sclk_cam0", "sclk_cam1", "pxl_async0",
"pxl_async1";
#clock-cells = <1>;
clock-output-names = "cam_a_clkout", "cam_b_clkout";
pinctrl-names = "default";
pinctrl-0 = <&cam_port_a_clk_active>;
status = "okay";
#address-cells = <1>;
#size-cells = <1>;
/* parallel camera ports */
parallel-ports {

View File

@ -0,0 +1,97 @@
Samsung S5C73M3 8Mp camera ISP
------------------------------
The S5C73M3 camera ISP supports MIPI CSI-2 and parallel (ITU-R BT.656) video
data busses. The I2C bus is the main control bus and additionally the SPI bus
is used, mostly for transferring the firmware to and from the device. Two
slave device nodes corresponding to these control bus interfaces are required
and should be placed under respective bus controller nodes.
I2C slave device node
---------------------
Required properties:
- compatible : "samsung,s5c73m3";
- reg : I2C slave address of the sensor;
- vdd-int-supply : digital power supply (1.2V);
- vdda-supply : analog power supply (1.2V);
- vdd-reg-supply : regulator input power supply (2.8V);
- vddio-host-supply : host I/O power supply (1.8V to 2.8V);
- vddio-cis-supply : CIS I/O power supply (1.2V to 1.8V);
- vdd-af-supply : lens power supply (2.8V);
- xshutdown-gpios : specifier of GPIO connected to the XSHUTDOWN pin;
- standby-gpios : specifier of GPIO connected to the STANDBY pin;
- clocks : should contain list of phandle and clock specifier pairs
according to common clock bindings for the clocks described
in the clock-names property;
- clock-names : should contain "cis_extclk" entry for the CIS_EXTCLK clock;
Optional properties:
- clock-frequency : the frequency at which the "cis_extclk" clock should be
configured to operate, in Hz; if this property is not
specified default 24 MHz value will be used.
The common video interfaces bindings (see video-interfaces.txt) should be used
to specify link from the S5C73M3 to an external image data receiver. The S5C73M3
device node should contain one 'port' child node with an 'endpoint' subnode for
this purpose. The data link from a raw image sensor to the S5C73M3 can be
similarly specified, but it is optional since the S5C73M3 ISP and a raw image
sensor are usually inseparable and form a hybrid module.
Following properties are valid for the endpoint node(s):
endpoint subnode
----------------
- data-lanes : (optional) specifies MIPI CSI-2 data lanes as covered in
video-interfaces.txt. This sensor doesn't support data lane remapping
and physical lane indexes in subsequent elements of the array should
be only consecutive ascending values.
SPI device node
---------------
Required properties:
- compatible : "samsung,s5c73m3";
For more details see description of the SPI busses bindings
(../spi/spi-bus.txt) and bindings of a specific bus controller.
Example:
i2c@138A000000 {
...
s5c73m3@3c {
compatible = "samsung,s5c73m3";
reg = <0x3c>;
vdd-int-supply = <&buck9_reg>;
vdda-supply = <&ldo17_reg>;
vdd-reg-supply = <&cam_io_reg>;
vddio-host-supply = <&ldo18_reg>;
vddio-cis-supply = <&ldo9_reg>;
vdd-af-supply = <&cam_af_reg>;
clock-frequency = <24000000>;
clocks = <&clk 0>;
clock-names = "cis_extclk";
reset-gpios = <&gpf1 3 1>;
standby-gpios = <&gpm0 1 1>;
port {
s5c73m3_ep: endpoint {
remote-endpoint = <&csis0_ep>;
data-lanes = <1 2 3 4>;
};
};
};
};
spi@1392000 {
...
s5c73m3_spi: s5c73m3@0 {
compatible = "samsung,s5c73m3";
reg = <0>;
...
};
};

View File

@ -0,0 +1,33 @@
Samsung S5K6A3(YX) raw image sensor
---------------------------------
S5K6A3(YX) is a raw image sensor with MIPI CSI-2 and CCP2 image data interfaces
and CCI (I2C compatible) control bus.
Required properties:
- compatible : "samsung,s5k6a3";
- reg : I2C slave address of the sensor;
- svdda-supply : core voltage supply;
- svddio-supply : I/O voltage supply;
- afvdd-supply : AF (actuator) voltage supply;
- gpios : specifier of a GPIO connected to the RESET pin;
- clocks : should contain list of phandle and clock specifier pairs
according to common clock bindings for the clocks described
in the clock-names property;
- clock-names : should contain "extclk" entry for the sensor's EXTCLK clock;
Optional properties:
- clock-frequency : the frequency at which the "extclk" clock should be
configured to operate, in Hz; if this property is not
specified default 24 MHz value will be used.
The common video interfaces bindings (see video-interfaces.txt) should be
used to specify link to the image data receiver. The S5K6A3(YX) device
node should contain one 'port' child node with an 'endpoint' subnode.
Following properties are valid for the endpoint node:
- data-lanes : (optional) specifies MIPI CSI-2 data lanes as covered in
video-interfaces.txt. The sensor supports only one data lane.

View File

@ -151,9 +151,8 @@ CONFIG_S5P_DEV_FIMC1 \
CONFIG_S5P_DEV_FIMC2 | optional
CONFIG_S5P_DEV_FIMC3 |
CONFIG_S5P_SETUP_FIMC /
CONFIG_S5P_SETUP_MIPIPHY \
CONFIG_S5P_DEV_CSIS0 | optional for MIPI-CSI interface
CONFIG_S5P_DEV_CSIS1 /
CONFIG_S5P_DEV_CSIS0 \ optional for MIPI-CSI interface
CONFIG_S5P_DEV_CSIS1 /
Except that, relevant s5p_device_fimc? should be registered in the machine code
in addition to a "s5p-fimc-md" platform device to which the media device driver

View File

@ -579,6 +579,14 @@ config VIDEO_S5K6AA
This is a V4L2 sensor-level driver for Samsung S5K6AA(FX) 1.3M
camera sensor with an embedded SoC image signal processor.
config VIDEO_S5K6A3
tristate "Samsung S5K6A3 sensor support"
depends on MEDIA_CAMERA_SUPPORT
depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
---help---
This is a V4L2 sensor-level driver for Samsung S5K6A3 raw
camera sensor.
config VIDEO_S5K4ECGX
tristate "Samsung S5K4ECGX sensor support"
depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API

View File

@ -66,6 +66,7 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o
obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o
obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o
obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o
obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o
obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o
obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/

View File

@ -15,7 +15,7 @@
* GNU General Public License for more details.
*/
#include <linux/sizes.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/gpio.h>
@ -23,7 +23,9 @@
#include <linux/init.h>
#include <linux/media.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/videodev2.h>
@ -33,6 +35,7 @@
#include <media/v4l2-subdev.h>
#include <media/v4l2-mediabus.h>
#include <media/s5c73m3.h>
#include <media/v4l2-of.h>
#include "s5c73m3.h"
@ -46,6 +49,8 @@ static int update_fw;
module_param(update_fw, int, 0644);
#define S5C73M3_EMBEDDED_DATA_MAXLEN SZ_4K
#define S5C73M3_MIPI_DATA_LANES 4
#define S5C73M3_CLK_NAME "cis_extclk"
static const char * const s5c73m3_supply_names[S5C73M3_MAX_SUPPLIES] = {
"vdd-int", /* Digital Core supply (1.2V), CAM_ISP_CORE_1.2V */
@ -1355,9 +1360,20 @@ static int __s5c73m3_power_on(struct s5c73m3 *state)
for (i = 0; i < S5C73M3_MAX_SUPPLIES; i++) {
ret = regulator_enable(state->supplies[i].consumer);
if (ret)
goto err;
goto err_reg_dis;
}
ret = clk_set_rate(state->clock, state->mclk_frequency);
if (ret < 0)
goto err_reg_dis;
ret = clk_prepare_enable(state->clock);
if (ret < 0)
goto err_reg_dis;
v4l2_dbg(1, s5c73m3_dbg, &state->oif_sd, "clock frequency: %ld\n",
clk_get_rate(state->clock));
s5c73m3_gpio_deassert(state, STBY);
usleep_range(100, 200);
@ -1365,7 +1381,8 @@ static int __s5c73m3_power_on(struct s5c73m3 *state)
usleep_range(50, 100);
return 0;
err:
err_reg_dis:
for (--i; i >= 0; i--)
regulator_disable(state->supplies[i].consumer);
return ret;
@ -1380,6 +1397,9 @@ static int __s5c73m3_power_off(struct s5c73m3 *state)
if (s5c73m3_gpio_assert(state, STBY))
usleep_range(100, 200);
clk_disable_unprepare(state->clock);
state->streaming = 0;
state->isp_ready = 0;
@ -1388,6 +1408,7 @@ static int __s5c73m3_power_off(struct s5c73m3 *state)
if (ret)
goto err;
}
return 0;
err:
for (++i; i < S5C73M3_MAX_SUPPLIES; i++) {
@ -1396,6 +1417,8 @@ err:
v4l2_err(&state->oif_sd, "Failed to reenable %s: %d\n",
state->supplies[i].supply, r);
}
clk_prepare_enable(state->clock);
return ret;
}
@ -1451,17 +1474,6 @@ static int s5c73m3_oif_registered(struct v4l2_subdev *sd)
S5C73M3_JPEG_PAD, &state->oif_sd.entity, OIF_JPEG_PAD,
MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
mutex_lock(&state->lock);
ret = __s5c73m3_power_on(state);
if (ret == 0)
s5c73m3_get_fw_version(state);
__s5c73m3_power_off(state);
mutex_unlock(&state->lock);
v4l2_dbg(1, s5c73m3_dbg, sd, "%s: Booting %s (%d)\n",
__func__, ret ? "failed" : "succeeded", ret);
return ret;
}
@ -1519,41 +1531,112 @@ static const struct v4l2_subdev_ops oif_subdev_ops = {
.video = &s5c73m3_oif_video_ops,
};
static int s5c73m3_configure_gpios(struct s5c73m3 *state,
const struct s5c73m3_platform_data *pdata)
static int s5c73m3_configure_gpios(struct s5c73m3 *state)
{
static const char * const gpio_names[] = {
"S5C73M3_STBY", "S5C73M3_RST"
};
struct i2c_client *c = state->i2c_client;
struct s5c73m3_gpio *g = state->gpio;
int ret, i;
for (i = 0; i < GPIO_NUM; ++i) {
unsigned int flags = GPIOF_DIR_OUT;
if (g[i].level)
flags |= GPIOF_INIT_HIGH;
ret = devm_gpio_request_one(&c->dev, g[i].gpio, flags,
gpio_names[i]);
if (ret) {
v4l2_err(c, "failed to request gpio %s\n",
gpio_names[i]);
return ret;
}
}
return 0;
}
static int s5c73m3_parse_gpios(struct s5c73m3 *state)
{
static const char * const prop_names[] = {
"standby-gpios", "xshutdown-gpios",
};
struct device *dev = &state->i2c_client->dev;
struct device_node *node = dev->of_node;
int ret, i;
for (i = 0; i < GPIO_NUM; ++i) {
enum of_gpio_flags of_flags;
ret = of_get_named_gpio_flags(node, prop_names[i],
0, &of_flags);
if (ret < 0) {
dev_err(dev, "failed to parse %s DT property\n",
prop_names[i]);
return -EINVAL;
}
state->gpio[i].gpio = ret;
state->gpio[i].level = !(of_flags & OF_GPIO_ACTIVE_LOW);
}
return 0;
}
static int s5c73m3_get_platform_data(struct s5c73m3 *state)
{
struct device *dev = &state->i2c_client->dev;
const struct s5c73m3_gpio *gpio;
unsigned long flags;
const struct s5c73m3_platform_data *pdata = dev->platform_data;
struct device_node *node = dev->of_node;
struct device_node *node_ep;
struct v4l2_of_endpoint ep;
int ret;
state->gpio[STBY].gpio = -EINVAL;
state->gpio[RST].gpio = -EINVAL;
if (!node) {
if (!pdata) {
dev_err(dev, "Platform data not specified\n");
return -EINVAL;
}
gpio = &pdata->gpio_stby;
if (gpio_is_valid(gpio->gpio)) {
flags = (gpio->level ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW)
| GPIOF_EXPORT;
ret = devm_gpio_request_one(dev, gpio->gpio, flags,
"S5C73M3_STBY");
if (ret < 0)
return ret;
state->gpio[STBY] = *gpio;
state->mclk_frequency = pdata->mclk_frequency;
state->gpio[STBY] = pdata->gpio_stby;
state->gpio[RST] = pdata->gpio_reset;
return 0;
}
gpio = &pdata->gpio_reset;
if (gpio_is_valid(gpio->gpio)) {
flags = (gpio->level ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW)
| GPIOF_EXPORT;
ret = devm_gpio_request_one(dev, gpio->gpio, flags,
"S5C73M3_RST");
if (ret < 0)
return ret;
state->clock = devm_clk_get(dev, S5C73M3_CLK_NAME);
if (IS_ERR(state->clock))
return PTR_ERR(state->clock);
state->gpio[RST] = *gpio;
if (of_property_read_u32(node, "clock-frequency",
&state->mclk_frequency)) {
state->mclk_frequency = S5C73M3_DEFAULT_MCLK_FREQ;
dev_info(dev, "using default %u Hz clock frequency\n",
state->mclk_frequency);
}
ret = s5c73m3_parse_gpios(state);
if (ret < 0)
return -EINVAL;
node_ep = v4l2_of_get_next_endpoint(node, NULL);
if (!node_ep) {
dev_warn(dev, "no endpoint defined for node: %s\n",
node->full_name);
return 0;
}
v4l2_of_parse_endpoint(node_ep, &ep);
of_node_put(node_ep);
if (ep.bus_type != V4L2_MBUS_CSI2) {
dev_err(dev, "unsupported bus type\n");
return -EINVAL;
}
/*
* Number of MIPI CSI-2 data lanes is currently not configurable,
* always a default value of 4 lanes is used.
*/
if (ep.bus.mipi_csi2.num_data_lanes != S5C73M3_MIPI_DATA_LANES)
dev_info(dev, "falling back to 4 MIPI CSI-2 data lanes\n");
return 0;
}
@ -1561,21 +1644,20 @@ static int s5c73m3_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
const struct s5c73m3_platform_data *pdata = client->dev.platform_data;
struct v4l2_subdev *sd;
struct v4l2_subdev *oif_sd;
struct s5c73m3 *state;
int ret, i;
if (pdata == NULL) {
dev_err(&client->dev, "Platform data not specified\n");
return -EINVAL;
}
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
state->i2c_client = client;
ret = s5c73m3_get_platform_data(state);
if (ret < 0)
return ret;
mutex_init(&state->lock);
sd = &state->sensor_sd;
oif_sd = &state->oif_sd;
@ -1613,11 +1695,7 @@ static int s5c73m3_probe(struct i2c_client *client,
if (ret < 0)
return ret;
state->mclk_frequency = pdata->mclk_frequency;
state->bus_type = pdata->bus_type;
state->i2c_client = client;
ret = s5c73m3_configure_gpios(state, pdata);
ret = s5c73m3_configure_gpios(state);
if (ret)
goto out_err;
@ -1651,9 +1729,29 @@ static int s5c73m3_probe(struct i2c_client *client,
if (ret < 0)
goto out_err;
oif_sd->dev = dev;
ret = __s5c73m3_power_on(state);
if (ret < 0)
goto out_err1;
ret = s5c73m3_get_fw_version(state);
__s5c73m3_power_off(state);
if (ret < 0) {
dev_err(dev, "Device detection failed: %d\n", ret);
goto out_err1;
}
ret = v4l2_async_register_subdev(oif_sd);
if (ret < 0)
goto out_err1;
v4l2_info(sd, "%s: completed successfully\n", __func__);
return 0;
out_err1:
s5c73m3_unregister_spi_driver(state);
out_err:
media_entity_cleanup(&sd->entity);
return ret;
@ -1665,7 +1763,7 @@ static int s5c73m3_remove(struct i2c_client *client)
struct s5c73m3 *state = oif_sd_to_s5c73m3(oif_sd);
struct v4l2_subdev *sensor_sd = &state->sensor_sd;
v4l2_device_unregister_subdev(oif_sd);
v4l2_async_unregister_subdev(oif_sd);
v4l2_ctrl_handler_free(oif_sd->ctrl_handler);
media_entity_cleanup(&oif_sd->entity);
@ -1684,8 +1782,17 @@ static const struct i2c_device_id s5c73m3_id[] = {
};
MODULE_DEVICE_TABLE(i2c, s5c73m3_id);
#ifdef CONFIG_OF
static const struct of_device_id s5c73m3_of_match[] = {
{ .compatible = "samsung,s5c73m3" },
{ }
};
MODULE_DEVICE_TABLE(of, s5c73m3_of_match);
#endif
static struct i2c_driver s5c73m3_i2c_driver = {
.driver = {
.of_match_table = of_match_ptr(s5c73m3_of_match),
.name = DRIVER_NAME,
},
.probe = s5c73m3_probe,

View File

@ -27,6 +27,11 @@
#define S5C73M3_SPI_DRV_NAME "S5C73M3-SPI"
static const struct of_device_id s5c73m3_spi_ids[] = {
{ .compatible = "samsung,s5c73m3" },
{ }
};
enum spi_direction {
SPI_DIR_RX,
SPI_DIR_TX
@ -146,6 +151,7 @@ int s5c73m3_register_spi_driver(struct s5c73m3 *state)
spidrv->driver.name = S5C73M3_SPI_DRV_NAME;
spidrv->driver.bus = &spi_bus_type;
spidrv->driver.owner = THIS_MODULE;
spidrv->driver.of_match_table = s5c73m3_spi_ids;
return spi_register_driver(spidrv);
}

View File

@ -17,6 +17,7 @@
#ifndef S5C73M3_H_
#define S5C73M3_H_
#include <linux/clk.h>
#include <linux/kernel.h>
#include <linux/regulator/consumer.h>
#include <media/v4l2-common.h>
@ -321,6 +322,7 @@ enum s5c73m3_oif_pads {
#define S5C73M3_MAX_SUPPLIES 6
#define S5C73M3_DEFAULT_MCLK_FREQ 24000000U
struct s5c73m3_ctrls {
struct v4l2_ctrl_handler handler;
@ -391,6 +393,8 @@ struct s5c73m3 {
struct regulator_bulk_data supplies[S5C73M3_MAX_SUPPLIES];
struct s5c73m3_gpio gpio[GPIO_NUM];
struct clk *clock;
/* External master clock frequency */
u32 mclk_frequency;
/* Video bus type - MIPI-CSI2/parallel */

389
drivers/media/i2c/s5k6a3.c Normal file
View File

@ -0,0 +1,389 @@
/*
* Samsung S5K6A3 image sensor 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/clk.h>
#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 <linux/videodev2.h>
#include <media/v4l2-async.h>
#include <media/v4l2-subdev.h>
#define S5K6A3_SENSOR_MAX_WIDTH 1412
#define S5K6A3_SENSOR_MAX_HEIGHT 1412
#define S5K6A3_SENSOR_MIN_WIDTH 32
#define S5K6A3_SENSOR_MIN_HEIGHT 32
#define S5K6A3_DEFAULT_WIDTH 1296
#define S5K6A3_DEFAULT_HEIGHT 732
#define S5K6A3_DRV_NAME "S5K6A3"
#define S5K6A3_CLK_NAME "extclk"
#define S5K6A3_DEFAULT_CLK_FREQ 24000000U
enum {
S5K6A3_SUPP_VDDA,
S5K6A3_SUPP_VDDIO,
S5K6A3_SUPP_AFVDD,
S5K6A3_NUM_SUPPLIES,
};
/**
* struct s5k6a3 - 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
* @lock: mutex protecting the structure's members below
* @format: media bus format at the sensor's source pad
*/
struct s5k6a3 {
struct device *dev;
struct v4l2_subdev subdev;
struct media_pad pad;
struct regulator_bulk_data supplies[S5K6A3_NUM_SUPPLIES];
int gpio_reset;
struct mutex lock;
struct v4l2_mbus_framefmt format;
struct clk *clock;
u32 clock_frequency;
int power_count;
};
static const char * const s5k6a3_supply_names[] = {
[S5K6A3_SUPP_VDDA] = "svdda",
[S5K6A3_SUPP_VDDIO] = "svddio",
[S5K6A3_SUPP_AFVDD] = "afvdd",
};
static inline struct s5k6a3 *sd_to_s5k6a3(struct v4l2_subdev *sd)
{
return container_of(sd, struct s5k6a3, subdev);
}
static const struct v4l2_mbus_framefmt s5k6a3_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(s5k6a3_formats); i++)
if (mf->code == s5k6a3_formats[i].code)
return &s5k6a3_formats[i];
return &s5k6a3_formats[0];
}
static int s5k6a3_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_mbus_code_enum *code)
{
if (code->index >= ARRAY_SIZE(s5k6a3_formats))
return -EINVAL;
code->code = s5k6a3_formats[code->index].code;
return 0;
}
static void s5k6a3_try_format(struct v4l2_mbus_framefmt *mf)
{
const struct v4l2_mbus_framefmt *fmt;
fmt = find_sensor_format(mf);
mf->code = fmt->code;
v4l_bound_align_image(&mf->width, S5K6A3_SENSOR_MIN_WIDTH,
S5K6A3_SENSOR_MAX_WIDTH, 0,
&mf->height, S5K6A3_SENSOR_MIN_HEIGHT,
S5K6A3_SENSOR_MAX_HEIGHT, 0, 0);
}
static struct v4l2_mbus_framefmt *__s5k6a3_get_format(
struct s5k6a3 *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 s5k6a3_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
struct s5k6a3 *sensor = sd_to_s5k6a3(sd);
struct v4l2_mbus_framefmt *mf;
s5k6a3_try_format(&fmt->format);
mf = __s5k6a3_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 s5k6a3_get_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
struct s5k6a3 *sensor = sd_to_s5k6a3(sd);
struct v4l2_mbus_framefmt *mf;
mf = __s5k6a3_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 s5k6a3_pad_ops = {
.enum_mbus_code = s5k6a3_enum_mbus_code,
.get_fmt = s5k6a3_get_fmt,
.set_fmt = s5k6a3_set_fmt,
};
static int s5k6a3_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(fh, 0);
*format = s5k6a3_formats[0];
format->width = S5K6A3_DEFAULT_WIDTH;
format->height = S5K6A3_DEFAULT_HEIGHT;
return 0;
}
static const struct v4l2_subdev_internal_ops s5k6a3_sd_internal_ops = {
.open = s5k6a3_open,
};
static int __s5k6a3_power_on(struct s5k6a3 *sensor)
{
int i = S5K6A3_SUPP_VDDA;
int ret;
ret = clk_set_rate(sensor->clock, sensor->clock_frequency);
if (ret < 0)
return ret;
ret = pm_runtime_get(sensor->dev);
if (ret < 0)
return ret;
ret = regulator_enable(sensor->supplies[i].consumer);
if (ret < 0)
goto error_rpm_put;
ret = clk_prepare_enable(sensor->clock);
if (ret < 0)
goto error_reg_dis;
for (i++; i < S5K6A3_NUM_SUPPLIES; i++) {
ret = regulator_enable(sensor->supplies[i].consumer);
if (ret < 0)
goto error_reg_dis;
}
gpio_set_value(sensor->gpio_reset, 1);
usleep_range(600, 800);
gpio_set_value(sensor->gpio_reset, 0);
usleep_range(600, 800);
gpio_set_value(sensor->gpio_reset, 1);
/* Delay needed for the sensor initialization */
msleep(20);
return 0;
error_reg_dis:
for (--i; i >= 0; --i)
regulator_disable(sensor->supplies[i].consumer);
error_rpm_put:
pm_runtime_put(sensor->dev);
return ret;
}
static int __s5k6a3_power_off(struct s5k6a3 *sensor)
{
int i;
gpio_set_value(sensor->gpio_reset, 0);
for (i = S5K6A3_NUM_SUPPLIES - 1; i >= 0; i--)
regulator_disable(sensor->supplies[i].consumer);
clk_disable_unprepare(sensor->clock);
pm_runtime_put(sensor->dev);
return 0;
}
static int s5k6a3_s_power(struct v4l2_subdev *sd, int on)
{
struct s5k6a3 *sensor = sd_to_s5k6a3(sd);
int ret = 0;
mutex_lock(&sensor->lock);
if (sensor->power_count == !on) {
if (on)
ret = __s5k6a3_power_on(sensor);
else
ret = __s5k6a3_power_off(sensor);
if (ret == 0)
sensor->power_count += on ? 1 : -1;
}
mutex_unlock(&sensor->lock);
return ret;
}
static struct v4l2_subdev_core_ops s5k6a3_core_ops = {
.s_power = s5k6a3_s_power,
};
static struct v4l2_subdev_ops s5k6a3_subdev_ops = {
.core = &s5k6a3_core_ops,
.pad = &s5k6a3_pad_ops,
};
static int s5k6a3_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct s5k6a3 *sensor;
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;
sensor->clock = ERR_PTR(-EINVAL);
sensor->dev = dev;
sensor->clock = devm_clk_get(sensor->dev, S5K6A3_CLK_NAME);
if (IS_ERR(sensor->clock))
return PTR_ERR(sensor->clock);
gpio = of_get_gpio_flags(dev->of_node, 0, NULL);
if (!gpio_is_valid(gpio))
return gpio;
ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_LOW,
S5K6A3_DRV_NAME);
if (ret < 0)
return ret;
sensor->gpio_reset = gpio;
if (of_property_read_u32(dev->of_node, "clock-frequency",
&sensor->clock_frequency)) {
sensor->clock_frequency = S5K6A3_DEFAULT_CLK_FREQ;
dev_info(dev, "using default %u Hz clock frequency\n",
sensor->clock_frequency);
}
for (i = 0; i < S5K6A3_NUM_SUPPLIES; i++)
sensor->supplies[i].supply = s5k6a3_supply_names[i];
ret = devm_regulator_bulk_get(&client->dev, S5K6A3_NUM_SUPPLIES,
sensor->supplies);
if (ret < 0)
return ret;
sd = &sensor->subdev;
v4l2_i2c_subdev_init(sd, client, &s5k6a3_subdev_ops);
sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
sd->internal_ops = &s5k6a3_sd_internal_ops;
sensor->format.code = s5k6a3_formats[0].code;
sensor->format.width = S5K6A3_DEFAULT_WIDTH;
sensor->format.height = S5K6A3_DEFAULT_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);
ret = v4l2_async_register_subdev(sd);
if (ret < 0) {
pm_runtime_disable(&client->dev);
media_entity_cleanup(&sd->entity);
}
return ret;
}
static int s5k6a3_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
pm_runtime_disable(&client->dev);
v4l2_async_unregister_subdev(sd);
media_entity_cleanup(&sd->entity);
return 0;
}
static const struct i2c_device_id s5k6a3_ids[] = {
{ }
};
#ifdef CONFIG_OF
static const struct of_device_id s5k6a3_of_match[] = {
{ .compatible = "samsung,s5k6a3" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, s5k6a3_of_match);
#endif
static struct i2c_driver s5k6a3_driver = {
.driver = {
.of_match_table = of_match_ptr(s5k6a3_of_match),
.name = S5K6A3_DRV_NAME,
.owner = THIS_MODULE,
},
.probe = s5k6a3_probe,
.remove = s5k6a3_remove,
.id_table = s5k6a3_ids,
};
module_i2c_driver(s5k6a3_driver);
MODULE_DESCRIPTION("S5K6A3 image sensor subdev driver");
MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
MODULE_LICENSE("GPL v2");

View File

@ -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

View File

@ -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

View File

@ -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];

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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");

View File

@ -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_ */

View File

@ -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;
}
/* Initialize memory allocator context for the ISP DMA. */
is->isp.alloc_ctx = is->alloc_ctx;
for_each_available_child_of_node(adapter, child) {
struct i2c_client *client;
struct v4l2_subdev *sd;
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);
client = of_find_i2c_device_by_node(child);
if (!client)
goto e_retry;
sd = i2c_get_clientdata(client);
if (!sd)
goto e_retry;
/* FIXME: Add support for multiple sensors. */
if (WARN_ON(is->sensor))
continue;
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;
ret = platform_driver_register(&fimc_is_driver);
if (!ret)
return ret;
fimc_is_unregister_i2c_driver();
err_sens:
fimc_is_unregister_sensor_driver();
ret = platform_driver_register(&fimc_is_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);
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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__ */

View File

@ -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,
};

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -210,19 +210,19 @@
/* JPEG CNTL Register bit */
#define EXYNOS4_ENC_DEC_MODE_MASK (0xfffffffc << 0)
#define EXYNOS4_DEC_MODE (1 << 0)
#define EXYNOS4_ENC_MODE (1 << 1)
#define EXYNOS4_DEC_MODE (1 << 0)
#define EXYNOS4_ENC_MODE (1 << 1)
#define EXYNOS4_AUTO_RST_MARKER (1 << 2)
#define EXYNOS4_RST_INTERVAL_SHIFT 3
#define EXYNOS4_RST_INTERVAL(x) (((x) & 0xffff) \
<< EXYNOS4_RST_INTERVAL_SHIFT)
#define EXYNOS4_HUF_TBL_EN (1 << 19)
#define EXYNOS4_HOR_SCALING_SHIFT 20
#define EXYNOS4_HOR_SCALING_MASK (3 << EXYNOS4_HOR_SCALING_SHIFT)
#define EXYNOS4_HOR_SCALING_MASK (3 << EXYNOS4_HOR_SCALING_SHIFT)
#define EXYNOS4_HOR_SCALING(x) (((x) & 0x3) \
<< EXYNOS4_HOR_SCALING_SHIFT)
#define EXYNOS4_VER_SCALING_SHIFT 22
#define EXYNOS4_VER_SCALING_MASK (3 << EXYNOS4_VER_SCALING_SHIFT)
#define EXYNOS4_VER_SCALING_MASK (3 << EXYNOS4_VER_SCALING_SHIFT)
#define EXYNOS4_VER_SCALING(x) (((x) & 0x3) \
<< EXYNOS4_VER_SCALING_SHIFT)
#define EXYNOS4_PADDING (1 << 27)
@ -238,8 +238,8 @@
#define EXYNOS4_FRAME_ERR_EN (1 << 4)
#define EXYNOS4_INT_EN_ALL (0x1f << 0)
#define EXYNOS4_MOD_REG_PROC_ENC (0 << 3)
#define EXYNOS4_MOD_REG_PROC_DEC (1 << 3)
#define EXYNOS4_MOD_REG_PROC_ENC (0 << 3)
#define EXYNOS4_MOD_REG_PROC_DEC (1 << 3)
#define EXYNOS4_MOD_REG_SUBSAMPLE_444 (0 << 0)
#define EXYNOS4_MOD_REG_SUBSAMPLE_422 (1 << 0)
@ -270,7 +270,7 @@
#define EXYNOS4_DEC_YUV_420_IMG (4 << 0)
#define EXYNOS4_GRAY_IMG_IP_SHIFT 3
#define EXYNOS4_GRAY_IMG_IP_MASK (7 << EXYNOS4_GRAY_IMG_IP_SHIFT)
#define EXYNOS4_GRAY_IMG_IP_MASK (7 << EXYNOS4_GRAY_IMG_IP_SHIFT)
#define EXYNOS4_GRAY_IMG_IP (4 << EXYNOS4_GRAY_IMG_IP_SHIFT)
#define EXYNOS4_RGB_IP_SHIFT 6
@ -278,18 +278,18 @@
#define EXYNOS4_RGB_IP_RGB_16BIT_IMG (4 << EXYNOS4_RGB_IP_SHIFT)
#define EXYNOS4_RGB_IP_RGB_32BIT_IMG (5 << EXYNOS4_RGB_IP_SHIFT)
#define EXYNOS4_YUV_444_IP_SHIFT 9
#define EXYNOS4_YUV_444_IP_SHIFT 9
#define EXYNOS4_YUV_444_IP_MASK (7 << EXYNOS4_YUV_444_IP_SHIFT)
#define EXYNOS4_YUV_444_IP_YUV_444_2P_IMG (4 << EXYNOS4_YUV_444_IP_SHIFT)
#define EXYNOS4_YUV_444_IP_YUV_444_3P_IMG (5 << EXYNOS4_YUV_444_IP_SHIFT)
#define EXYNOS4_YUV_422_IP_SHIFT 12
#define EXYNOS4_YUV_422_IP_SHIFT 12
#define EXYNOS4_YUV_422_IP_MASK (7 << EXYNOS4_YUV_422_IP_SHIFT)
#define EXYNOS4_YUV_422_IP_YUV_422_1P_IMG (4 << EXYNOS4_YUV_422_IP_SHIFT)
#define EXYNOS4_YUV_422_IP_YUV_422_2P_IMG (5 << EXYNOS4_YUV_422_IP_SHIFT)
#define EXYNOS4_YUV_422_IP_YUV_422_3P_IMG (6 << EXYNOS4_YUV_422_IP_SHIFT)
#define EXYNOS4_YUV_420_IP_SHIFT 15
#define EXYNOS4_YUV_420_IP_SHIFT 15
#define EXYNOS4_YUV_420_IP_MASK (7 << EXYNOS4_YUV_420_IP_SHIFT)
#define EXYNOS4_YUV_420_IP_YUV_420_2P_IMG (4 << EXYNOS4_YUV_420_IP_SHIFT)
#define EXYNOS4_YUV_420_IP_YUV_420_3P_IMG (5 << EXYNOS4_YUV_420_IP_SHIFT)
@ -303,8 +303,8 @@
#define EXYNOS4_JPEG_DECODED_IMG_FMT_MASK 0x03
#define EXYNOS4_SWAP_CHROMA_CRCB (1 << 26)
#define EXYNOS4_SWAP_CHROMA_CBCR (0 << 26)
#define EXYNOS4_SWAP_CHROMA_CRCB (1 << 26)
#define EXYNOS4_SWAP_CHROMA_CBCR (0 << 26)
/* JPEG HUFF count Register bit */
#define EXYNOS4_HUFF_COUNT_MASK 0xffff