mirror of
https://github.com/torvalds/linux.git
synced 2024-10-31 01:01:52 +00:00
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:
commit
463b21fb27
@ -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 {
|
||||
|
97
Documentation/devicetree/bindings/media/samsung-s5c73m3.txt
Normal file
97
Documentation/devicetree/bindings/media/samsung-s5c73m3.txt
Normal 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>;
|
||||
...
|
||||
};
|
||||
};
|
33
Documentation/devicetree/bindings/media/samsung-s5k6a3.txt
Normal file
33
Documentation/devicetree/bindings/media/samsung-s5k6a3.txt
Normal 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.
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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/
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
389
drivers/media/i2c/s5k6a3.c
Normal 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");
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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_ */
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
660
drivers/media/platform/exynos4-is/fimc-isp-video.c
Normal file
660
drivers/media/platform/exynos4-is/fimc-isp-video.c
Normal 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);
|
||||
}
|
44
drivers/media/platform/exynos4-is/fimc-isp-video.h
Normal file
44
drivers/media/platform/exynos4-is/fimc-isp-video.h
Normal 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__ */
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user