mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-09 14:43:16 +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:
|
Required properties:
|
||||||
|
|
||||||
- compatible : must be "samsung,fimc", "simple-bus"
|
- compatible: must be "samsung,fimc", "simple-bus"
|
||||||
- clocks : list of clock specifiers, corresponding to entries in
|
- clocks: list of clock specifiers, corresponding to entries in
|
||||||
the clock-names property;
|
the clock-names property;
|
||||||
- clock-names : must contain "sclk_cam0", "sclk_cam1", "pxl_async0",
|
- clock-names : must contain "sclk_cam0", "sclk_cam1", "pxl_async0",
|
||||||
"pxl_async1" entries, matching entries in the clocks property.
|
"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
|
The pinctrl bindings defined in ../pinctrl/pinctrl-bindings.txt must be used
|
||||||
to define a required pinctrl state named "default" and optional pinctrl states:
|
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.
|
The 'camera' node must include at least one 'fimc' child node.
|
||||||
|
|
||||||
|
|
||||||
'fimc' device nodes
|
'fimc' device nodes
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
@ -88,8 +99,8 @@ port nodes specifies data input - 0, 1 indicates input A, B respectively.
|
|||||||
|
|
||||||
Optional properties
|
Optional properties
|
||||||
|
|
||||||
- samsung,camclk-out : specifies clock output for remote sensor,
|
- samsung,camclk-out (deprecated) : specifies clock output for remote sensor,
|
||||||
0 - CAM_A_CLKOUT, 1 - CAM_B_CLKOUT;
|
0 - CAM_A_CLKOUT, 1 - CAM_B_CLKOUT;
|
||||||
|
|
||||||
Image sensor nodes
|
Image sensor nodes
|
||||||
------------------
|
------------------
|
||||||
@ -97,8 +108,6 @@ Image sensor nodes
|
|||||||
The sensor device nodes should be added to their control bus controller (e.g.
|
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,
|
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.
|
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:
|
Example:
|
||||||
|
|
||||||
@ -114,7 +123,7 @@ Example:
|
|||||||
vddio-supply = <...>;
|
vddio-supply = <...>;
|
||||||
|
|
||||||
clock-frequency = <24000000>;
|
clock-frequency = <24000000>;
|
||||||
clocks = <...>;
|
clocks = <&camera 1>;
|
||||||
clock-names = "mclk";
|
clock-names = "mclk";
|
||||||
|
|
||||||
port {
|
port {
|
||||||
@ -135,7 +144,7 @@ Example:
|
|||||||
vddio-supply = <...>;
|
vddio-supply = <...>;
|
||||||
|
|
||||||
clock-frequency = <24000000>;
|
clock-frequency = <24000000>;
|
||||||
clocks = <...>;
|
clocks = <&camera 0>;
|
||||||
clock-names = "mclk";
|
clock-names = "mclk";
|
||||||
|
|
||||||
port {
|
port {
|
||||||
@ -149,12 +158,17 @@ Example:
|
|||||||
|
|
||||||
camera {
|
camera {
|
||||||
compatible = "samsung,fimc", "simple-bus";
|
compatible = "samsung,fimc", "simple-bus";
|
||||||
#address-cells = <1>;
|
clocks = <&clock 132>, <&clock 133>, <&clock 351>,
|
||||||
#size-cells = <1>;
|
<&clock 352>;
|
||||||
status = "okay";
|
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-names = "default";
|
||||||
pinctrl-0 = <&cam_port_a_clk_active>;
|
pinctrl-0 = <&cam_port_a_clk_active>;
|
||||||
|
status = "okay";
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <1>;
|
||||||
|
|
||||||
/* parallel camera ports */
|
/* parallel camera ports */
|
||||||
parallel-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_FIMC2 | optional
|
||||||
CONFIG_S5P_DEV_FIMC3 |
|
CONFIG_S5P_DEV_FIMC3 |
|
||||||
CONFIG_S5P_SETUP_FIMC /
|
CONFIG_S5P_SETUP_FIMC /
|
||||||
CONFIG_S5P_SETUP_MIPIPHY \
|
CONFIG_S5P_DEV_CSIS0 \ optional for MIPI-CSI interface
|
||||||
CONFIG_S5P_DEV_CSIS0 | optional for MIPI-CSI interface
|
CONFIG_S5P_DEV_CSIS1 /
|
||||||
CONFIG_S5P_DEV_CSIS1 /
|
|
||||||
|
|
||||||
Except that, relevant s5p_device_fimc? should be registered in the machine code
|
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
|
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
|
This is a V4L2 sensor-level driver for Samsung S5K6AA(FX) 1.3M
|
||||||
camera sensor with an embedded SoC image signal processor.
|
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
|
config VIDEO_S5K4ECGX
|
||||||
tristate "Samsung S5K4ECGX sensor support"
|
tristate "Samsung S5K4ECGX sensor support"
|
||||||
depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
|
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_SR030PC30) += sr030pc30.o
|
||||||
obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o
|
obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o
|
||||||
obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o
|
obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o
|
||||||
|
obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o
|
||||||
obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o
|
obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o
|
||||||
obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o
|
obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o
|
||||||
obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/
|
obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/sizes.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/firmware.h>
|
#include <linux/firmware.h>
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
@ -23,7 +23,9 @@
|
|||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/media.h>
|
#include <linux/media.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/of_gpio.h>
|
||||||
#include <linux/regulator/consumer.h>
|
#include <linux/regulator/consumer.h>
|
||||||
|
#include <linux/sizes.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/spi/spi.h>
|
#include <linux/spi/spi.h>
|
||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
@ -33,6 +35,7 @@
|
|||||||
#include <media/v4l2-subdev.h>
|
#include <media/v4l2-subdev.h>
|
||||||
#include <media/v4l2-mediabus.h>
|
#include <media/v4l2-mediabus.h>
|
||||||
#include <media/s5c73m3.h>
|
#include <media/s5c73m3.h>
|
||||||
|
#include <media/v4l2-of.h>
|
||||||
|
|
||||||
#include "s5c73m3.h"
|
#include "s5c73m3.h"
|
||||||
|
|
||||||
@ -46,6 +49,8 @@ static int update_fw;
|
|||||||
module_param(update_fw, int, 0644);
|
module_param(update_fw, int, 0644);
|
||||||
|
|
||||||
#define S5C73M3_EMBEDDED_DATA_MAXLEN SZ_4K
|
#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] = {
|
static const char * const s5c73m3_supply_names[S5C73M3_MAX_SUPPLIES] = {
|
||||||
"vdd-int", /* Digital Core supply (1.2V), CAM_ISP_CORE_1.2V */
|
"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++) {
|
for (i = 0; i < S5C73M3_MAX_SUPPLIES; i++) {
|
||||||
ret = regulator_enable(state->supplies[i].consumer);
|
ret = regulator_enable(state->supplies[i].consumer);
|
||||||
if (ret)
|
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);
|
s5c73m3_gpio_deassert(state, STBY);
|
||||||
usleep_range(100, 200);
|
usleep_range(100, 200);
|
||||||
|
|
||||||
@ -1365,7 +1381,8 @@ static int __s5c73m3_power_on(struct s5c73m3 *state)
|
|||||||
usleep_range(50, 100);
|
usleep_range(50, 100);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
err:
|
|
||||||
|
err_reg_dis:
|
||||||
for (--i; i >= 0; i--)
|
for (--i; i >= 0; i--)
|
||||||
regulator_disable(state->supplies[i].consumer);
|
regulator_disable(state->supplies[i].consumer);
|
||||||
return ret;
|
return ret;
|
||||||
@ -1380,6 +1397,9 @@ static int __s5c73m3_power_off(struct s5c73m3 *state)
|
|||||||
|
|
||||||
if (s5c73m3_gpio_assert(state, STBY))
|
if (s5c73m3_gpio_assert(state, STBY))
|
||||||
usleep_range(100, 200);
|
usleep_range(100, 200);
|
||||||
|
|
||||||
|
clk_disable_unprepare(state->clock);
|
||||||
|
|
||||||
state->streaming = 0;
|
state->streaming = 0;
|
||||||
state->isp_ready = 0;
|
state->isp_ready = 0;
|
||||||
|
|
||||||
@ -1388,6 +1408,7 @@ static int __s5c73m3_power_off(struct s5c73m3 *state)
|
|||||||
if (ret)
|
if (ret)
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
err:
|
err:
|
||||||
for (++i; i < S5C73M3_MAX_SUPPLIES; i++) {
|
for (++i; i < S5C73M3_MAX_SUPPLIES; i++) {
|
||||||
@ -1396,6 +1417,8 @@ err:
|
|||||||
v4l2_err(&state->oif_sd, "Failed to reenable %s: %d\n",
|
v4l2_err(&state->oif_sd, "Failed to reenable %s: %d\n",
|
||||||
state->supplies[i].supply, r);
|
state->supplies[i].supply, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clk_prepare_enable(state->clock);
|
||||||
return ret;
|
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,
|
S5C73M3_JPEG_PAD, &state->oif_sd.entity, OIF_JPEG_PAD,
|
||||||
MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1519,41 +1531,112 @@ static const struct v4l2_subdev_ops oif_subdev_ops = {
|
|||||||
.video = &s5c73m3_oif_video_ops,
|
.video = &s5c73m3_oif_video_ops,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int s5c73m3_configure_gpios(struct s5c73m3 *state,
|
static int s5c73m3_configure_gpios(struct s5c73m3 *state)
|
||||||
const struct s5c73m3_platform_data *pdata)
|
{
|
||||||
|
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;
|
struct device *dev = &state->i2c_client->dev;
|
||||||
const struct s5c73m3_gpio *gpio;
|
const struct s5c73m3_platform_data *pdata = dev->platform_data;
|
||||||
unsigned long flags;
|
struct device_node *node = dev->of_node;
|
||||||
|
struct device_node *node_ep;
|
||||||
|
struct v4l2_of_endpoint ep;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
state->gpio[STBY].gpio = -EINVAL;
|
if (!node) {
|
||||||
state->gpio[RST].gpio = -EINVAL;
|
if (!pdata) {
|
||||||
|
dev_err(dev, "Platform data not specified\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
gpio = &pdata->gpio_stby;
|
state->mclk_frequency = pdata->mclk_frequency;
|
||||||
if (gpio_is_valid(gpio->gpio)) {
|
state->gpio[STBY] = pdata->gpio_stby;
|
||||||
flags = (gpio->level ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW)
|
state->gpio[RST] = pdata->gpio_reset;
|
||||||
| GPIOF_EXPORT;
|
return 0;
|
||||||
ret = devm_gpio_request_one(dev, gpio->gpio, flags,
|
|
||||||
"S5C73M3_STBY");
|
|
||||||
if (ret < 0)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
state->gpio[STBY] = *gpio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gpio = &pdata->gpio_reset;
|
state->clock = devm_clk_get(dev, S5C73M3_CLK_NAME);
|
||||||
if (gpio_is_valid(gpio->gpio)) {
|
if (IS_ERR(state->clock))
|
||||||
flags = (gpio->level ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW)
|
return PTR_ERR(state->clock);
|
||||||
| GPIOF_EXPORT;
|
|
||||||
ret = devm_gpio_request_one(dev, gpio->gpio, flags,
|
|
||||||
"S5C73M3_RST");
|
|
||||||
if (ret < 0)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1561,21 +1644,20 @@ static int s5c73m3_probe(struct i2c_client *client,
|
|||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
const struct s5c73m3_platform_data *pdata = client->dev.platform_data;
|
|
||||||
struct v4l2_subdev *sd;
|
struct v4l2_subdev *sd;
|
||||||
struct v4l2_subdev *oif_sd;
|
struct v4l2_subdev *oif_sd;
|
||||||
struct s5c73m3 *state;
|
struct s5c73m3 *state;
|
||||||
int ret, i;
|
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);
|
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
|
||||||
if (!state)
|
if (!state)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
state->i2c_client = client;
|
||||||
|
ret = s5c73m3_get_platform_data(state);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
mutex_init(&state->lock);
|
mutex_init(&state->lock);
|
||||||
sd = &state->sensor_sd;
|
sd = &state->sensor_sd;
|
||||||
oif_sd = &state->oif_sd;
|
oif_sd = &state->oif_sd;
|
||||||
@ -1613,11 +1695,7 @@ static int s5c73m3_probe(struct i2c_client *client,
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
state->mclk_frequency = pdata->mclk_frequency;
|
ret = s5c73m3_configure_gpios(state);
|
||||||
state->bus_type = pdata->bus_type;
|
|
||||||
state->i2c_client = client;
|
|
||||||
|
|
||||||
ret = s5c73m3_configure_gpios(state, pdata);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out_err;
|
goto out_err;
|
||||||
|
|
||||||
@ -1651,9 +1729,29 @@ static int s5c73m3_probe(struct i2c_client *client,
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto out_err;
|
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__);
|
v4l2_info(sd, "%s: completed successfully\n", __func__);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
out_err1:
|
||||||
|
s5c73m3_unregister_spi_driver(state);
|
||||||
out_err:
|
out_err:
|
||||||
media_entity_cleanup(&sd->entity);
|
media_entity_cleanup(&sd->entity);
|
||||||
return ret;
|
return ret;
|
||||||
@ -1665,7 +1763,7 @@ static int s5c73m3_remove(struct i2c_client *client)
|
|||||||
struct s5c73m3 *state = oif_sd_to_s5c73m3(oif_sd);
|
struct s5c73m3 *state = oif_sd_to_s5c73m3(oif_sd);
|
||||||
struct v4l2_subdev *sensor_sd = &state->sensor_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);
|
v4l2_ctrl_handler_free(oif_sd->ctrl_handler);
|
||||||
media_entity_cleanup(&oif_sd->entity);
|
media_entity_cleanup(&oif_sd->entity);
|
||||||
@ -1684,8 +1782,17 @@ static const struct i2c_device_id s5c73m3_id[] = {
|
|||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(i2c, 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 = {
|
static struct i2c_driver s5c73m3_i2c_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
|
.of_match_table = of_match_ptr(s5c73m3_of_match),
|
||||||
.name = DRIVER_NAME,
|
.name = DRIVER_NAME,
|
||||||
},
|
},
|
||||||
.probe = s5c73m3_probe,
|
.probe = s5c73m3_probe,
|
||||||
|
@ -27,6 +27,11 @@
|
|||||||
|
|
||||||
#define S5C73M3_SPI_DRV_NAME "S5C73M3-SPI"
|
#define S5C73M3_SPI_DRV_NAME "S5C73M3-SPI"
|
||||||
|
|
||||||
|
static const struct of_device_id s5c73m3_spi_ids[] = {
|
||||||
|
{ .compatible = "samsung,s5c73m3" },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
enum spi_direction {
|
enum spi_direction {
|
||||||
SPI_DIR_RX,
|
SPI_DIR_RX,
|
||||||
SPI_DIR_TX
|
SPI_DIR_TX
|
||||||
@ -146,6 +151,7 @@ int s5c73m3_register_spi_driver(struct s5c73m3 *state)
|
|||||||
spidrv->driver.name = S5C73M3_SPI_DRV_NAME;
|
spidrv->driver.name = S5C73M3_SPI_DRV_NAME;
|
||||||
spidrv->driver.bus = &spi_bus_type;
|
spidrv->driver.bus = &spi_bus_type;
|
||||||
spidrv->driver.owner = THIS_MODULE;
|
spidrv->driver.owner = THIS_MODULE;
|
||||||
|
spidrv->driver.of_match_table = s5c73m3_spi_ids;
|
||||||
|
|
||||||
return spi_register_driver(spidrv);
|
return spi_register_driver(spidrv);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#ifndef S5C73M3_H_
|
#ifndef S5C73M3_H_
|
||||||
#define S5C73M3_H_
|
#define S5C73M3_H_
|
||||||
|
|
||||||
|
#include <linux/clk.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/regulator/consumer.h>
|
#include <linux/regulator/consumer.h>
|
||||||
#include <media/v4l2-common.h>
|
#include <media/v4l2-common.h>
|
||||||
@ -321,6 +322,7 @@ enum s5c73m3_oif_pads {
|
|||||||
|
|
||||||
|
|
||||||
#define S5C73M3_MAX_SUPPLIES 6
|
#define S5C73M3_MAX_SUPPLIES 6
|
||||||
|
#define S5C73M3_DEFAULT_MCLK_FREQ 24000000U
|
||||||
|
|
||||||
struct s5c73m3_ctrls {
|
struct s5c73m3_ctrls {
|
||||||
struct v4l2_ctrl_handler handler;
|
struct v4l2_ctrl_handler handler;
|
||||||
@ -391,6 +393,8 @@ struct s5c73m3 {
|
|||||||
struct regulator_bulk_data supplies[S5C73M3_MAX_SUPPLIES];
|
struct regulator_bulk_data supplies[S5C73M3_MAX_SUPPLIES];
|
||||||
struct s5c73m3_gpio gpio[GPIO_NUM];
|
struct s5c73m3_gpio gpio[GPIO_NUM];
|
||||||
|
|
||||||
|
struct clk *clock;
|
||||||
|
|
||||||
/* External master clock frequency */
|
/* External master clock frequency */
|
||||||
u32 mclk_frequency;
|
u32 mclk_frequency;
|
||||||
/* Video bus type - MIPI-CSI2/parallel */
|
/* 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
|
To compile this driver as a module, choose M here: the
|
||||||
module will be called exynos4-fimc-is.
|
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
|
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.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
|
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_S5P_MIPI_CSIS) += s5p-csis.o
|
||||||
obj-$(CONFIG_VIDEO_EXYNOS_FIMC_LITE) += exynos-fimc-lite.o
|
obj-$(CONFIG_VIDEO_EXYNOS_FIMC_LITE) += exynos-fimc-lite.o
|
||||||
obj-$(CONFIG_VIDEO_EXYNOS4_FIMC_IS) += exynos-fimc-is.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);
|
__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 is_param_region *par = &is->is_p_region->parameter;
|
||||||
struct chain_config *cfg = &is->config[is->config_index];
|
struct chain_config *cfg = &is->config[is->config_index];
|
||||||
|
@ -911,6 +911,10 @@ struct is_region {
|
|||||||
u32 shared[MAX_SHARED_COUNT];
|
u32 shared[MAX_SHARED_COUNT];
|
||||||
} __packed;
|
} __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 {
|
struct is_debug_frame_descriptor {
|
||||||
u32 sensor_frame_time;
|
u32 sensor_frame_time;
|
||||||
u32 sensor_exposure_time;
|
u32 sensor_exposure_time;
|
||||||
@ -988,6 +992,7 @@ struct sensor_open_extended {
|
|||||||
struct fimc_is;
|
struct fimc_is;
|
||||||
|
|
||||||
int fimc_is_hw_get_sensor_max_framerate(struct fimc_is *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);
|
void fimc_is_set_initial_params(struct fimc_is *is);
|
||||||
unsigned int __get_pending_param_count(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;
|
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)
|
void fimc_is_hw_set_sensor_num(struct fimc_is *is)
|
||||||
{
|
{
|
||||||
pr_debug("setting sensor index to: %d\n", is->sensor_index);
|
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(IH_REPLY_DONE, is, MCUCTL_REG_ISSR(0));
|
||||||
mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
|
mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
|
||||||
mcuctl_write(IHC_GET_SENSOR_NUM, is, MCUCTL_REG_ISSR(2));
|
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)
|
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);
|
void fimc_is_hw_set_intgr0_gd0(struct fimc_is *is);
|
||||||
int fimc_is_hw_wait_intmsr0_intmsd0(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_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_on(struct fimc_is *is);
|
||||||
void fimc_is_hw_stream_off(struct fimc_is *is);
|
void fimc_is_hw_stream_off(struct fimc_is *is);
|
||||||
int fimc_is_hw_set_param(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
|
* Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
|
||||||
*
|
*
|
||||||
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
||||||
*
|
|
||||||
* Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
|
* Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
* published by the Free Software Foundation.
|
* 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"
|
#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 = {
|
static const struct sensor_drv_data s5k6a3_drvdata = {
|
||||||
.id = FIMC_IS_SENSOR_ID_S5K6A3,
|
.id = FIMC_IS_SENSOR_ID_S5K6A3,
|
||||||
.subdev_name = "S5K6A3",
|
.open_timeout = S5K6A3_OPEN_TIMEOUT,
|
||||||
.width = S5K6A3_SENSOR_WIDTH,
|
|
||||||
.height = S5K6A3_SENSOR_HEIGHT,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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",
|
.compatible = "samsung,s5k6a3",
|
||||||
.data = &s5k6a3_drvdata,
|
.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 = {
|
const struct sensor_drv_data *fimc_is_sensor_get_drvdata(
|
||||||
.driver = {
|
struct device_node *node)
|
||||||
.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)
|
|
||||||
{
|
{
|
||||||
return i2c_add_driver(&fimc_is_sensor_driver);
|
const struct of_device_id *of_id;
|
||||||
}
|
|
||||||
|
|
||||||
void fimc_is_unregister_sensor_driver(void)
|
of_id = of_match_node(fimc_is_sensor_of_ids, node);
|
||||||
{
|
return of_id ? of_id->data : NULL;
|
||||||
i2c_del_driver(&fimc_is_sensor_driver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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_
|
#ifndef FIMC_IS_SENSOR_H_
|
||||||
#define FIMC_IS_SENSOR_H_
|
#define FIMC_IS_SENSOR_H_
|
||||||
|
|
||||||
#include <linux/clk.h>
|
#include <linux/of.h>
|
||||||
#include <linux/device.h>
|
#include <linux/types.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
|
|
||||||
|
|
||||||
|
#define S5K6A3_OPEN_TIMEOUT 2000 /* ms */
|
||||||
#define S5K6A3_SENSOR_WIDTH 1392
|
#define S5K6A3_SENSOR_WIDTH 1392
|
||||||
#define S5K6A3_SENSOR_HEIGHT 1392
|
#define S5K6A3_SENSOR_HEIGHT 1392
|
||||||
|
|
||||||
#define SENSOR_NUM_SUPPLIES 2
|
|
||||||
|
|
||||||
enum fimc_is_sensor_id {
|
enum fimc_is_sensor_id {
|
||||||
FIMC_IS_SENSOR_ID_S5K3H2 = 1,
|
FIMC_IS_SENSOR_ID_S5K3H2 = 1,
|
||||||
FIMC_IS_SENSOR_ID_S5K6A3,
|
FIMC_IS_SENSOR_ID_S5K6A3,
|
||||||
@ -45,45 +34,23 @@ enum fimc_is_sensor_id {
|
|||||||
|
|
||||||
struct sensor_drv_data {
|
struct sensor_drv_data {
|
||||||
enum fimc_is_sensor_id id;
|
enum fimc_is_sensor_id id;
|
||||||
const char * const subdev_name;
|
/* sensor open timeout in ms */
|
||||||
unsigned int width;
|
unsigned short open_timeout;
|
||||||
unsigned int height;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct fimc_is_sensor - fimc-is sensor data structure
|
* 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
|
* @drvdata: a pointer to the sensor's parameters data structure
|
||||||
* @i2c_bus: ISP I2C bus index (0...1)
|
* @i2c_bus: ISP I2C bus index (0...1)
|
||||||
* @test_pattern: true to enable video test pattern
|
* @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 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;
|
const struct sensor_drv_data *drvdata;
|
||||||
unsigned int i2c_bus;
|
unsigned int i2c_bus;
|
||||||
bool test_pattern;
|
u8 test_pattern;
|
||||||
|
|
||||||
struct mutex lock;
|
|
||||||
struct v4l2_mbus_framefmt format;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline
|
const struct sensor_drv_data *fimc_is_sensor_get_drvdata(
|
||||||
struct fimc_is_sensor *sd_to_fimc_is_sensor(struct v4l2_subdev *sd)
|
struct device_node *node);
|
||||||
{
|
|
||||||
return container_of(sd, struct fimc_is_sensor, subdev);
|
|
||||||
}
|
|
||||||
|
|
||||||
int fimc_is_register_sensor_driver(void);
|
|
||||||
void fimc_is_unregister_sensor_driver(void);
|
|
||||||
|
|
||||||
#endif /* FIMC_IS_SENSOR_H_ */
|
#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,
|
static int fimc_is_parse_sensor_config(struct fimc_is *is, unsigned int index,
|
||||||
struct device_node *np)
|
struct device_node *node)
|
||||||
{
|
{
|
||||||
|
struct fimc_is_sensor *sensor = &is->sensor[index];
|
||||||
u32 tmp = 0;
|
u32 tmp = 0;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
np = of_graph_get_next_endpoint(np, NULL);
|
sensor->drvdata = fimc_is_sensor_get_drvdata(node);
|
||||||
if (!np)
|
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;
|
return -ENXIO;
|
||||||
np = of_graph_get_remote_port(np);
|
|
||||||
if (!np)
|
node = of_graph_get_remote_port(node);
|
||||||
|
if (!node)
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
|
|
||||||
/* Use MIPI-CSIS channel id to determine the ISP I2C bus index. */
|
/* Use MIPI-CSIS channel id to determine the ISP I2C bus index. */
|
||||||
ret = of_property_read_u32(np, "reg", &tmp);
|
ret = of_property_read_u32(node, "reg", &tmp);
|
||||||
sensor->i2c_bus = tmp - FIMC_INPUT_MIPI_CSI2_0;
|
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)
|
static int fimc_is_register_subdevs(struct fimc_is *is)
|
||||||
{
|
{
|
||||||
struct device_node *adapter, *child;
|
struct device_node *i2c_bus, *child;
|
||||||
int ret;
|
int ret, index = 0;
|
||||||
|
|
||||||
ret = fimc_isp_subdev_create(&is->isp);
|
ret = fimc_isp_subdev_create(&is->isp);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
for_each_compatible_node(adapter, NULL, FIMC_IS_I2C_COMPATIBLE) {
|
/* Initialize memory allocator context for the ISP DMA. */
|
||||||
if (!of_find_device_by_node(adapter)) {
|
is->isp.alloc_ctx = is->alloc_ctx;
|
||||||
of_node_put(adapter);
|
|
||||||
return -EPROBE_DEFER;
|
|
||||||
}
|
|
||||||
|
|
||||||
for_each_available_child_of_node(adapter, child) {
|
for_each_compatible_node(i2c_bus, NULL, FIMC_IS_I2C_COMPATIBLE) {
|
||||||
struct i2c_client *client;
|
for_each_available_child_of_node(i2c_bus, child) {
|
||||||
struct v4l2_subdev *sd;
|
ret = fimc_is_parse_sensor_config(is, index, child);
|
||||||
|
|
||||||
client = of_find_i2c_device_by_node(child);
|
if (ret < 0 || index >= FIMC_IS_SENSORS_NUM) {
|
||||||
if (!client)
|
of_node_put(child);
|
||||||
goto e_retry;
|
return ret;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
pr_debug("%s(): registered subdev: %p\n",
|
index++;
|
||||||
__func__, sd->name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
e_retry:
|
|
||||||
of_node_put(child);
|
|
||||||
return -EPROBE_DEFER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fimc_is_unregister_subdevs(struct fimc_is *is)
|
static int fimc_is_unregister_subdevs(struct fimc_is *is)
|
||||||
{
|
{
|
||||||
fimc_isp_subdev_destroy(&is->isp);
|
fimc_isp_subdev_destroy(&is->isp);
|
||||||
is->sensor = NULL;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -647,7 +638,7 @@ static int fimc_is_hw_open_sensor(struct fimc_is *is,
|
|||||||
fimc_is_hw_set_intgr0_gd0(is);
|
fimc_is_hw_set_intgr0_gd0(is);
|
||||||
|
|
||||||
return fimc_is_wait_event(is, IS_ST_OPEN_SENSOR, 1,
|
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;
|
u32 prev_id;
|
||||||
int i, ret;
|
int i, ret;
|
||||||
|
|
||||||
/* Sensor initialization. */
|
/* Sensor initialization. Only one sensor is currently supported. */
|
||||||
ret = fimc_is_hw_open_sensor(is, is->sensor);
|
ret = fimc_is_hw_open_sensor(is, &is->sensor[0]);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -977,27 +968,20 @@ static int fimc_is_module_init(void)
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = fimc_is_register_sensor_driver();
|
|
||||||
if (ret < 0)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
ret = fimc_is_register_i2c_driver();
|
ret = fimc_is_register_i2c_driver();
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto err_sens;
|
|
||||||
|
|
||||||
ret = platform_driver_register(&fimc_is_driver);
|
|
||||||
if (!ret)
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
fimc_is_unregister_i2c_driver();
|
ret = platform_driver_register(&fimc_is_driver);
|
||||||
err_sens:
|
|
||||||
fimc_is_unregister_sensor_driver();
|
if (ret < 0)
|
||||||
|
fimc_is_unregister_i2c_driver();
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fimc_is_module_exit(void)
|
static void fimc_is_module_exit(void)
|
||||||
{
|
{
|
||||||
fimc_is_unregister_sensor_driver();
|
|
||||||
fimc_is_unregister_i2c_driver();
|
fimc_is_unregister_i2c_driver();
|
||||||
platform_driver_unregister(&fimc_is_driver);
|
platform_driver_unregister(&fimc_is_driver);
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
#define FIMC_IS_FW_LOAD_TIMEOUT 1000 /* ms */
|
#define FIMC_IS_FW_LOAD_TIMEOUT 1000 /* ms */
|
||||||
#define FIMC_IS_POWER_ON_TIMEOUT 1000 /* us */
|
#define FIMC_IS_POWER_ON_TIMEOUT 1000 /* us */
|
||||||
|
|
||||||
#define FIMC_IS_SENSOR_NUM 2
|
#define FIMC_IS_SENSORS_NUM 2
|
||||||
|
|
||||||
/* Memory definitions */
|
/* Memory definitions */
|
||||||
#define FIMC_IS_CPU_MEM_SIZE (0xa00000)
|
#define FIMC_IS_CPU_MEM_SIZE (0xa00000)
|
||||||
@ -253,7 +253,7 @@ struct fimc_is {
|
|||||||
struct firmware *f_w;
|
struct firmware *f_w;
|
||||||
|
|
||||||
struct fimc_isp isp;
|
struct fimc_isp isp;
|
||||||
struct fimc_is_sensor *sensor;
|
struct fimc_is_sensor sensor[FIMC_IS_SENSORS_NUM];
|
||||||
struct fimc_is_setfile setfile;
|
struct fimc_is_setfile setfile;
|
||||||
|
|
||||||
struct vb2_alloc_ctx *alloc_ctx;
|
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);
|
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)
|
static inline void fimc_is_mem_barrier(void)
|
||||||
{
|
{
|
||||||
mb();
|
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/v4l2-device.h>
|
||||||
|
|
||||||
#include "media-dev.h"
|
#include "media-dev.h"
|
||||||
|
#include "fimc-isp-video.h"
|
||||||
#include "fimc-is-command.h"
|
#include "fimc-is-command.h"
|
||||||
#include "fimc-is-param.h"
|
#include "fimc-is-param.h"
|
||||||
#include "fimc-is-regs.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));
|
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_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);
|
wake_up(&is->irq_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +389,33 @@ static int fimc_isp_subdev_open(struct v4l2_subdev *sd,
|
|||||||
return 0;
|
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 = {
|
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,
|
.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_WIDTH_MIN (16 + 8)
|
||||||
#define FIMC_ISP_SINK_HEIGHT_MIN (12 + 8)
|
#define FIMC_ISP_SINK_HEIGHT_MIN (12 + 8)
|
||||||
#define FIMC_ISP_SOURCE_WIDTH_MIN 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_WIDTH 16
|
||||||
#define FIMC_ISP_CAC_MARGIN_HEIGHT 12
|
#define FIMC_ISP_CAC_MARGIN_HEIGHT 12
|
||||||
|
|
||||||
#define FIMC_ISP_SINK_WIDTH_MAX (4000 - 16)
|
#define FIMC_ISP_SINK_WIDTH_MAX (4000 - 16)
|
||||||
#define FIMC_ISP_SINK_HEIGHT_MAX (4000 + 12)
|
#define FIMC_ISP_SINK_HEIGHT_MAX (4000 + 12)
|
||||||
#define FIMC_ISP_SOURCE_WIDTH_MAX 4000
|
#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_NUM_FORMATS 3
|
||||||
#define FIMC_ISP_REQ_BUFS_MIN 2
|
#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_SINK 0
|
||||||
#define FIMC_ISP_SD_PAD_SRC_FIFO 1
|
#define FIMC_ISP_SD_PAD_SRC_FIFO 1
|
||||||
@ -100,6 +101,16 @@ struct fimc_isp_ctrls {
|
|||||||
struct v4l2_ctrl *colorfx;
|
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
|
* struct fimc_is_video - fimc-is video device structure
|
||||||
* @vdev: video_device structure
|
* @vdev: video_device structure
|
||||||
@ -114,18 +125,26 @@ struct fimc_isp_ctrls {
|
|||||||
* @format: current pixel format
|
* @format: current pixel format
|
||||||
*/
|
*/
|
||||||
struct fimc_is_video {
|
struct fimc_is_video {
|
||||||
struct video_device vdev;
|
struct exynos_video_entity ve;
|
||||||
enum v4l2_buf_type type;
|
enum v4l2_buf_type type;
|
||||||
struct media_pad pad;
|
struct media_pad pad;
|
||||||
struct list_head pending_buf_q;
|
struct list_head pending_buf_q;
|
||||||
struct list_head active_buf_q;
|
struct list_head active_buf_q;
|
||||||
struct vb2_queue vb_queue;
|
struct vb2_queue vb_queue;
|
||||||
unsigned int frame_count;
|
|
||||||
unsigned int reqbufs_count;
|
unsigned int reqbufs_count;
|
||||||
|
unsigned int buf_count;
|
||||||
|
unsigned int buf_mask;
|
||||||
|
unsigned int frame_count;
|
||||||
int streaming;
|
int streaming;
|
||||||
|
struct isp_video_buf *buffers[FIMC_ISP_MAX_BUFS];
|
||||||
const struct fimc_fmt *format;
|
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
|
* struct fimc_isp - FIMC-IS ISP data structure
|
||||||
* @pdev: pointer to FIMC-IS platform device
|
* @pdev: pointer to FIMC-IS platform device
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/bug.h>
|
#include <linux/bug.h>
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/errno.h>
|
#include <linux/errno.h>
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
@ -25,6 +27,7 @@
|
|||||||
#include <linux/pm_runtime.h>
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
#include <media/v4l2-async.h>
|
||||||
#include <media/v4l2-ctrls.h>
|
#include <media/v4l2-ctrls.h>
|
||||||
#include <media/v4l2-of.h>
|
#include <media/v4l2-of.h>
|
||||||
#include <media/media-device.h>
|
#include <media/media-device.h>
|
||||||
@ -219,6 +222,7 @@ static int __fimc_pipeline_open(struct exynos_media_pipeline *ep,
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = fimc_md_set_camclk(sd, true);
|
ret = fimc_md_set_camclk(sd, true);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto err_wbclk;
|
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_client *client = v4l2_get_subdevdata(sd);
|
||||||
struct i2c_adapter *adapter;
|
struct i2c_adapter *adapter;
|
||||||
|
|
||||||
if (!client)
|
if (!client || client->dev.of_node)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
v4l2_device_unregister_subdev(sd);
|
v4l2_device_unregister_subdev(sd);
|
||||||
|
|
||||||
if (!client->dev.of_node) {
|
adapter = client->adapter;
|
||||||
adapter = client->adapter;
|
i2c_unregister_device(client);
|
||||||
i2c_unregister_device(client);
|
if (adapter)
|
||||||
if (adapter)
|
i2c_put_adapter(adapter);
|
||||||
i2c_put_adapter(adapter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_OF
|
#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. */
|
/* Parse port node and register as a sub-device any sensor specified there. */
|
||||||
static int fimc_md_parse_port_node(struct fimc_md *fmd,
|
static int fimc_md_parse_port_node(struct fimc_md *fmd,
|
||||||
struct device_node *port,
|
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 device_node *rem, *ep, *np;
|
||||||
struct fimc_source_info *pd;
|
struct fimc_source_info *pd;
|
||||||
struct v4l2_of_endpoint endpoint;
|
struct v4l2_of_endpoint endpoint;
|
||||||
int ret;
|
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
pd = &fmd->sensor[index].pdata;
|
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))
|
if (!of_property_read_u32(rem, "clock-frequency", &val))
|
||||||
pd->clk_frequency = val;
|
pd->clk_frequency = val;
|
||||||
|
else
|
||||||
|
pd->clk_frequency = DEFAULT_SENSOR_CLK_FREQ;
|
||||||
|
|
||||||
if (pd->clk_frequency == 0) {
|
if (pd->clk_frequency == 0) {
|
||||||
v4l2_err(&fmd->v4l2_dev, "Wrong clock frequency at node %s\n",
|
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
|
else
|
||||||
pd->fimc_bus_type = pd->sensor_bus_type;
|
pd->fimc_bus_type = pd->sensor_bus_type;
|
||||||
|
|
||||||
ret = fimc_md_of_add_sensor(fmd, rem, index);
|
if (WARN_ON(index >= ARRAY_SIZE(fmd->sensor)))
|
||||||
of_node_put(rem);
|
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 */
|
/* 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)
|
static int register_fimc_is_entity(struct fimc_md *fmd, struct fimc_is *is)
|
||||||
{
|
{
|
||||||
struct v4l2_subdev *sd = &is->isp.subdev;
|
struct v4l2_subdev *sd = &is->isp.subdev;
|
||||||
|
struct exynos_media_pipeline *ep;
|
||||||
int ret;
|
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);
|
ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
v4l2_err(&fmd->v4l2_dev,
|
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);
|
v4l2_device_unregister_subdev(fmd->csis[i].sd);
|
||||||
fmd->csis[i].sd = NULL;
|
fmd->csis[i].sd = NULL;
|
||||||
}
|
}
|
||||||
for (i = 0; i < fmd->num_sensors; i++) {
|
if (fmd->pdev->dev.of_node == NULL) {
|
||||||
if (fmd->sensor[i].subdev == NULL)
|
for (i = 0; i < fmd->num_sensors; i++) {
|
||||||
continue;
|
if (fmd->sensor[i].subdev == NULL)
|
||||||
fimc_md_unregister_sensor(fmd->sensor[i].subdev);
|
continue;
|
||||||
fmd->sensor[i].subdev = NULL;
|
fimc_md_unregister_sensor(fmd->sensor[i].subdev);
|
||||||
|
fmd->sensor[i].subdev = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fmd->fimc_is)
|
if (fmd->fimc_is)
|
||||||
@ -1005,16 +968,17 @@ static int __fimc_md_create_flite_source_links(struct fimc_md *fmd)
|
|||||||
/* Create FIMC-IS links */
|
/* Create FIMC-IS links */
|
||||||
static int __fimc_md_create_fimc_is_links(struct fimc_md *fmd)
|
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;
|
struct media_entity *source, *sink;
|
||||||
int i, ret;
|
int i, ret;
|
||||||
|
|
||||||
source = &fmd->fimc_is->isp.subdev.entity;
|
source = &isp->subdev.entity;
|
||||||
|
|
||||||
for (i = 0; i < FIMC_MAX_DEVS; i++) {
|
for (i = 0; i < FIMC_MAX_DEVS; i++) {
|
||||||
if (fmd->fimc[i] == NULL)
|
if (fmd->fimc[i] == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Link from IS-ISP subdev to FIMC */
|
/* Link from FIMC-IS-ISP subdev to FIMC */
|
||||||
sink = &fmd->fimc[i]->vid_cap.subdev.entity;
|
sink = &fmd->fimc[i]->vid_cap.subdev.entity;
|
||||||
ret = media_entity_create_link(source, FIMC_ISP_SD_PAD_SRC_FIFO,
|
ret = media_entity_create_link(source, FIMC_ISP_SD_PAD_SRC_FIFO,
|
||||||
sink, FIMC_SD_PAD_SINK_FIFO, 0);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
struct fimc_camclk_info *camclk;
|
||||||
int ret = 0;
|
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)
|
if (WARN_ON(si->clk_id >= FIMC_MAX_CAMCLKS) || !fmd || !fmd->pmf)
|
||||||
return -EINVAL;
|
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_source_info *si = v4l2_get_subdev_hostdata(sd);
|
||||||
struct fimc_md *fmd = entity_to_fimc_mdev(&sd->entity);
|
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);
|
return __fimc_md_set_camclk(fmd, si, on);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1438,6 +1426,153 @@ static int fimc_md_get_pinctrl(struct fimc_md *fmd)
|
|||||||
return 0;
|
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)
|
static int fimc_md_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
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);
|
v4l2_err(v4l2_dev, "Failed to register v4l2_device: %d\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = media_device_register(&fmd->media_dev);
|
ret = media_device_register(&fmd->media_dev);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
v4l2_err(v4l2_dev, "Failed to register media device: %d\n", ret);
|
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);
|
ret = fimc_md_get_clocks(fmd);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto err_clk;
|
goto err_md;
|
||||||
|
|
||||||
fmd->user_subdev_api = (dev->of_node != NULL);
|
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);
|
ret = fimc_md_get_pinctrl(fmd);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
if (ret != EPROBE_DEFER)
|
if (ret != EPROBE_DEFER)
|
||||||
dev_err(dev, "Failed to get pinctrl: %d\n", ret);
|
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)
|
if (dev->of_node)
|
||||||
ret = fimc_md_register_of_platform_entities(fmd, dev->of_node);
|
ret = fimc_md_register_of_platform_entities(fmd, dev->of_node);
|
||||||
else
|
else
|
||||||
ret = bus_for_each_dev(&platform_bus_type, NULL, fmd,
|
ret = bus_for_each_dev(&platform_bus_type, NULL, fmd,
|
||||||
fimc_md_pdev_match);
|
fimc_md_pdev_match);
|
||||||
if (ret)
|
if (ret) {
|
||||||
goto err_unlock;
|
mutex_unlock(&fmd->media_dev.graph_mutex);
|
||||||
|
goto err_clk;
|
||||||
|
}
|
||||||
|
|
||||||
if (dev->platform_data || dev->of_node) {
|
if (dev->platform_data || dev->of_node) {
|
||||||
ret = fimc_md_register_sensor_entities(fmd);
|
ret = fimc_md_register_sensor_entities(fmd);
|
||||||
if (ret)
|
if (ret) {
|
||||||
goto err_unlock;
|
mutex_unlock(&fmd->media_dev.graph_mutex);
|
||||||
|
goto err_m_ent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = fimc_md_create_links(fmd);
|
mutex_unlock(&fmd->media_dev.graph_mutex);
|
||||||
if (ret)
|
|
||||||
goto err_unlock;
|
|
||||||
ret = v4l2_device_register_subdev_nodes(&fmd->v4l2_dev);
|
|
||||||
if (ret)
|
|
||||||
goto err_unlock;
|
|
||||||
|
|
||||||
ret = device_create_file(&pdev->dev, &dev_attr_subdev_conf_mode);
|
ret = device_create_file(&pdev->dev, &dev_attr_subdev_conf_mode);
|
||||||
if (ret)
|
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;
|
return 0;
|
||||||
|
|
||||||
err_unlock:
|
err_clk_p:
|
||||||
mutex_unlock(&fmd->media_dev.graph_mutex);
|
fimc_md_unregister_clk_provider(fmd);
|
||||||
|
err_attr:
|
||||||
|
device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode);
|
||||||
err_clk:
|
err_clk:
|
||||||
fimc_md_put_clocks(fmd);
|
fimc_md_put_clocks(fmd);
|
||||||
|
err_m_ent:
|
||||||
fimc_md_unregister_entities(fmd);
|
fimc_md_unregister_entities(fmd);
|
||||||
media_device_unregister(&fmd->media_dev);
|
|
||||||
err_md:
|
err_md:
|
||||||
|
media_device_unregister(&fmd->media_dev);
|
||||||
|
err_v4l2_dev:
|
||||||
v4l2_device_unregister(&fmd->v4l2_dev);
|
v4l2_device_unregister(&fmd->v4l2_dev);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1538,12 +1701,16 @@ static int fimc_md_remove(struct platform_device *pdev)
|
|||||||
if (!fmd)
|
if (!fmd)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
fimc_md_unregister_clk_provider(fmd);
|
||||||
|
v4l2_async_notifier_unregister(&fmd->subdev_notifier);
|
||||||
|
|
||||||
v4l2_device_unregister(&fmd->v4l2_dev);
|
v4l2_device_unregister(&fmd->v4l2_dev);
|
||||||
device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode);
|
device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode);
|
||||||
fimc_md_unregister_entities(fmd);
|
fimc_md_unregister_entities(fmd);
|
||||||
fimc_md_pipelines_free(fmd);
|
fimc_md_pipelines_free(fmd);
|
||||||
media_device_unregister(&fmd->media_dev);
|
media_device_unregister(&fmd->media_dev);
|
||||||
fimc_md_put_clocks(fmd);
|
fimc_md_put_clocks(fmd);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#define FIMC_MDEVICE_H_
|
#define FIMC_MDEVICE_H_
|
||||||
|
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
@ -31,8 +32,9 @@
|
|||||||
|
|
||||||
#define PINCTRL_STATE_IDLE "idle"
|
#define PINCTRL_STATE_IDLE "idle"
|
||||||
|
|
||||||
#define FIMC_MAX_SENSORS 8
|
#define FIMC_MAX_SENSORS 4
|
||||||
#define FIMC_MAX_CAMCLKS 2
|
#define FIMC_MAX_CAMCLKS 2
|
||||||
|
#define DEFAULT_SENSOR_CLK_FREQ 24000000U
|
||||||
|
|
||||||
/* LCD/ISP Writeback clocks (PIXELASYNCMx) */
|
/* LCD/ISP Writeback clocks (PIXELASYNCMx) */
|
||||||
enum {
|
enum {
|
||||||
@ -78,6 +80,7 @@ struct fimc_camclk_info {
|
|||||||
/**
|
/**
|
||||||
* struct fimc_sensor_info - image data source subdev information
|
* struct fimc_sensor_info - image data source subdev information
|
||||||
* @pdata: sensor's atrributes passed as media device's platform data
|
* @pdata: sensor's atrributes passed as media device's platform data
|
||||||
|
* @asd: asynchronous subdev registration data structure
|
||||||
* @subdev: image sensor v4l2 subdev
|
* @subdev: image sensor v4l2 subdev
|
||||||
* @host: fimc device the sensor is currently linked to
|
* @host: fimc device the sensor is currently linked to
|
||||||
*
|
*
|
||||||
@ -85,10 +88,17 @@ struct fimc_camclk_info {
|
|||||||
*/
|
*/
|
||||||
struct fimc_sensor_info {
|
struct fimc_sensor_info {
|
||||||
struct fimc_source_info pdata;
|
struct fimc_source_info pdata;
|
||||||
|
struct v4l2_async_subdev asd;
|
||||||
struct v4l2_subdev *subdev;
|
struct v4l2_subdev *subdev;
|
||||||
struct fimc_dev *host;
|
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
|
* struct fimc_md - fimc media device information
|
||||||
* @csis: MIPI CSIS subdevs data
|
* @csis: MIPI CSIS subdevs data
|
||||||
@ -105,6 +115,7 @@ struct fimc_sensor_info {
|
|||||||
* @pinctrl: camera port pinctrl handle
|
* @pinctrl: camera port pinctrl handle
|
||||||
* @state_default: pinctrl default state handle
|
* @state_default: pinctrl default state handle
|
||||||
* @state_idle: pinctrl idle 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
|
* @user_subdev_api: true if subdevs are not configured by the host driver
|
||||||
* @slock: spinlock protecting @sensor array
|
* @slock: spinlock protecting @sensor array
|
||||||
*/
|
*/
|
||||||
@ -122,13 +133,25 @@ struct fimc_md {
|
|||||||
struct media_device media_dev;
|
struct media_device media_dev;
|
||||||
struct v4l2_device v4l2_dev;
|
struct v4l2_device v4l2_dev;
|
||||||
struct platform_device *pdev;
|
struct platform_device *pdev;
|
||||||
|
|
||||||
struct fimc_pinctrl {
|
struct fimc_pinctrl {
|
||||||
struct pinctrl *pinctrl;
|
struct pinctrl *pinctrl;
|
||||||
struct pinctrl_state *state_default;
|
struct pinctrl_state *state_default;
|
||||||
struct pinctrl_state *state_idle;
|
struct pinctrl_state *state_idle;
|
||||||
} pinctl;
|
} 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;
|
spinlock_t slock;
|
||||||
struct list_head pipelines;
|
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);
|
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)
|
static inline void fimc_md_graph_lock(struct exynos_video_entity *ve)
|
||||||
{
|
{
|
||||||
mutex_lock(&ve->vdev.entity.parent->graph_mutex);
|
mutex_lock(&ve->vdev.entity.parent->graph_mutex);
|
||||||
|
@ -210,19 +210,19 @@
|
|||||||
|
|
||||||
/* JPEG CNTL Register bit */
|
/* JPEG CNTL Register bit */
|
||||||
#define EXYNOS4_ENC_DEC_MODE_MASK (0xfffffffc << 0)
|
#define EXYNOS4_ENC_DEC_MODE_MASK (0xfffffffc << 0)
|
||||||
#define EXYNOS4_DEC_MODE (1 << 0)
|
#define EXYNOS4_DEC_MODE (1 << 0)
|
||||||
#define EXYNOS4_ENC_MODE (1 << 1)
|
#define EXYNOS4_ENC_MODE (1 << 1)
|
||||||
#define EXYNOS4_AUTO_RST_MARKER (1 << 2)
|
#define EXYNOS4_AUTO_RST_MARKER (1 << 2)
|
||||||
#define EXYNOS4_RST_INTERVAL_SHIFT 3
|
#define EXYNOS4_RST_INTERVAL_SHIFT 3
|
||||||
#define EXYNOS4_RST_INTERVAL(x) (((x) & 0xffff) \
|
#define EXYNOS4_RST_INTERVAL(x) (((x) & 0xffff) \
|
||||||
<< EXYNOS4_RST_INTERVAL_SHIFT)
|
<< EXYNOS4_RST_INTERVAL_SHIFT)
|
||||||
#define EXYNOS4_HUF_TBL_EN (1 << 19)
|
#define EXYNOS4_HUF_TBL_EN (1 << 19)
|
||||||
#define EXYNOS4_HOR_SCALING_SHIFT 20
|
#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) \
|
#define EXYNOS4_HOR_SCALING(x) (((x) & 0x3) \
|
||||||
<< EXYNOS4_HOR_SCALING_SHIFT)
|
<< EXYNOS4_HOR_SCALING_SHIFT)
|
||||||
#define EXYNOS4_VER_SCALING_SHIFT 22
|
#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) \
|
#define EXYNOS4_VER_SCALING(x) (((x) & 0x3) \
|
||||||
<< EXYNOS4_VER_SCALING_SHIFT)
|
<< EXYNOS4_VER_SCALING_SHIFT)
|
||||||
#define EXYNOS4_PADDING (1 << 27)
|
#define EXYNOS4_PADDING (1 << 27)
|
||||||
@ -238,8 +238,8 @@
|
|||||||
#define EXYNOS4_FRAME_ERR_EN (1 << 4)
|
#define EXYNOS4_FRAME_ERR_EN (1 << 4)
|
||||||
#define EXYNOS4_INT_EN_ALL (0x1f << 0)
|
#define EXYNOS4_INT_EN_ALL (0x1f << 0)
|
||||||
|
|
||||||
#define EXYNOS4_MOD_REG_PROC_ENC (0 << 3)
|
#define EXYNOS4_MOD_REG_PROC_ENC (0 << 3)
|
||||||
#define EXYNOS4_MOD_REG_PROC_DEC (1 << 3)
|
#define EXYNOS4_MOD_REG_PROC_DEC (1 << 3)
|
||||||
|
|
||||||
#define EXYNOS4_MOD_REG_SUBSAMPLE_444 (0 << 0)
|
#define EXYNOS4_MOD_REG_SUBSAMPLE_444 (0 << 0)
|
||||||
#define EXYNOS4_MOD_REG_SUBSAMPLE_422 (1 << 0)
|
#define EXYNOS4_MOD_REG_SUBSAMPLE_422 (1 << 0)
|
||||||
@ -270,7 +270,7 @@
|
|||||||
#define EXYNOS4_DEC_YUV_420_IMG (4 << 0)
|
#define EXYNOS4_DEC_YUV_420_IMG (4 << 0)
|
||||||
|
|
||||||
#define EXYNOS4_GRAY_IMG_IP_SHIFT 3
|
#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_GRAY_IMG_IP (4 << EXYNOS4_GRAY_IMG_IP_SHIFT)
|
||||||
|
|
||||||
#define EXYNOS4_RGB_IP_SHIFT 6
|
#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_16BIT_IMG (4 << EXYNOS4_RGB_IP_SHIFT)
|
||||||
#define EXYNOS4_RGB_IP_RGB_32BIT_IMG (5 << 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_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_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_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_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_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_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_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_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_2P_IMG (4 << EXYNOS4_YUV_420_IP_SHIFT)
|
||||||
#define EXYNOS4_YUV_420_IP_YUV_420_3P_IMG (5 << 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_JPEG_DECODED_IMG_FMT_MASK 0x03
|
||||||
|
|
||||||
#define EXYNOS4_SWAP_CHROMA_CRCB (1 << 26)
|
#define EXYNOS4_SWAP_CHROMA_CRCB (1 << 26)
|
||||||
#define EXYNOS4_SWAP_CHROMA_CBCR (0 << 26)
|
#define EXYNOS4_SWAP_CHROMA_CBCR (0 << 26)
|
||||||
|
|
||||||
/* JPEG HUFF count Register bit */
|
/* JPEG HUFF count Register bit */
|
||||||
#define EXYNOS4_HUFF_COUNT_MASK 0xffff
|
#define EXYNOS4_HUFF_COUNT_MASK 0xffff
|
||||||
|
Loading…
x
Reference in New Issue
Block a user