[media] s5p-csis: Add device tree support
This patch support for binding the driver to the MIPI-CSIS devices instantiated from device tree and parsing the SoC and board specific properties. The MIPI CSI-2 channel is determined by the value of reg property placed in csis' port subnode. Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
This commit is contained in:
parent
ee12b04910
commit
02399e35e6
|
@ -0,0 +1,81 @@
|
|||
Samsung S5P/EXYNOS SoC series MIPI CSI-2 receiver (MIPI CSIS)
|
||||
-------------------------------------------------------------
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "samsung,s5pv210-csis" for S5PV210 (S5PC110),
|
||||
"samsung,exynos4210-csis" for Exynos4210 (S5PC210),
|
||||
"samsung,exynos4212-csis" for Exynos4212/Exynos4412
|
||||
SoC series;
|
||||
- reg : offset and length of the register set for the device;
|
||||
- interrupts : should contain MIPI CSIS interrupt; the format of the
|
||||
interrupt specifier depends on the interrupt controller;
|
||||
- bus-width : maximum number of data lanes supported (SoC specific);
|
||||
- vddio-supply : MIPI CSIS I/O and PLL voltage supply (e.g. 1.8V);
|
||||
- vddcore-supply : MIPI CSIS Core voltage supply (e.g. 1.1V);
|
||||
- clocks : list of clock specifiers, corresponding to entries in
|
||||
clock-names property;
|
||||
- clock-names : must contain "csis", "sclk_csis" entries, matching entries
|
||||
in the clocks property.
|
||||
|
||||
Optional properties:
|
||||
|
||||
- clock-frequency : The IP's main (system bus) clock frequency in Hz, default
|
||||
value when this property is not specified is 166 MHz;
|
||||
- samsung,csis-wclk : CSI-2 wrapper clock selection. If this property is present
|
||||
external clock from CMU will be used, or the bus clock if
|
||||
if it's not specified.
|
||||
|
||||
The device node should contain one 'port' child node with one child 'endpoint'
|
||||
node, according to the bindings defined in Documentation/devicetree/bindings/
|
||||
media/video-interfaces.txt. The following are properties specific to those nodes.
|
||||
|
||||
port node
|
||||
---------
|
||||
|
||||
- reg : (required) must be 3 for camera C input (CSIS0) or 4 for
|
||||
camera D input (CSIS1);
|
||||
|
||||
endpoint node
|
||||
-------------
|
||||
|
||||
- data-lanes : (required) an array specifying active physical MIPI-CSI2
|
||||
data input lanes and their mapping to logical lanes; the
|
||||
array's content is unused, only its length is meaningful;
|
||||
|
||||
- samsung,csis-hs-settle : (optional) differential receiver (HS-RX) settle time;
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
reg0: regulator@0 {
|
||||
};
|
||||
|
||||
reg1: regulator@1 {
|
||||
};
|
||||
|
||||
/* SoC properties */
|
||||
|
||||
csis_0: csis@11880000 {
|
||||
compatible = "samsung,exynos4210-csis";
|
||||
reg = <0x11880000 0x1000>;
|
||||
interrupts = <0 78 0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
/* Board properties */
|
||||
|
||||
csis_0: csis@11880000 {
|
||||
clock-frequency = <166000000>;
|
||||
vddio-supply = <®0>;
|
||||
vddcore-supply = <®1>;
|
||||
port {
|
||||
reg = <3>; /* 3 - CSIS0, 4 - CSIS1 */
|
||||
csis0_ep: endpoint {
|
||||
remote-endpoint = <...>;
|
||||
data-lanes = <1>, <2>;
|
||||
samsung,csis-hs-settle = <12>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -19,14 +19,18 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/memory.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_data/mipi-csis.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <media/s5p_fimc.h>
|
||||
#include <media/v4l2-of.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
#include <linux/platform_data/mipi-csis.h>
|
||||
|
||||
#include "mipi-csis.h"
|
||||
|
||||
static int debug;
|
||||
|
@ -113,6 +117,7 @@ static char *csi_clock_name[] = {
|
|||
[CSIS_CLK_GATE] = "csis",
|
||||
};
|
||||
#define NUM_CSIS_CLOCKS ARRAY_SIZE(csi_clock_name)
|
||||
#define DEFAULT_SCLK_CSIS_FREQ 166000000UL
|
||||
|
||||
static const char * const csis_supply_name[] = {
|
||||
"vddcore", /* CSIS Core (1.0V, 1.1V or 1.2V) suppply */
|
||||
|
@ -167,6 +172,11 @@ struct csis_pktbuf {
|
|||
* @clock: CSIS clocks
|
||||
* @irq: requested s5p-mipi-csis irq number
|
||||
* @flags: the state variable for power and streaming control
|
||||
* @clock_frequency: device bus clock frequency
|
||||
* @hs_settle: HS-RX settle time
|
||||
* @num_lanes: number of MIPI-CSI data lanes used
|
||||
* @max_num_lanes: maximum number of MIPI-CSI data lanes supported
|
||||
* @wclk_ext: CSI wrapper clock: 0 - bus clock, 1 - external SCLK_CAM
|
||||
* @csis_fmt: current CSIS pixel format
|
||||
* @format: common media bus format for the source and sink pad
|
||||
* @slock: spinlock protecting structure members below
|
||||
|
@ -184,6 +194,13 @@ struct csis_state {
|
|||
struct clk *clock[NUM_CSIS_CLOCKS];
|
||||
int irq;
|
||||
u32 flags;
|
||||
|
||||
u32 clk_frequency;
|
||||
u32 hs_settle;
|
||||
u32 num_lanes;
|
||||
u32 max_num_lanes;
|
||||
u8 wclk_ext;
|
||||
|
||||
const struct csis_pix_format *csis_fmt;
|
||||
struct v4l2_mbus_framefmt format;
|
||||
|
||||
|
@ -273,7 +290,6 @@ static void s5pcsis_reset(struct csis_state *state)
|
|||
|
||||
static void s5pcsis_system_enable(struct csis_state *state, int on)
|
||||
{
|
||||
struct s5p_platform_mipi_csis *pdata = state->pdev->dev.platform_data;
|
||||
u32 val, mask;
|
||||
|
||||
val = s5pcsis_read(state, S5PCSIS_CTRL);
|
||||
|
@ -286,7 +302,7 @@ static void s5pcsis_system_enable(struct csis_state *state, int on)
|
|||
val = s5pcsis_read(state, S5PCSIS_DPHYCTRL);
|
||||
val &= ~S5PCSIS_DPHYCTRL_ENABLE;
|
||||
if (on) {
|
||||
mask = (1 << (pdata->lanes + 1)) - 1;
|
||||
mask = (1 << (state->num_lanes + 1)) - 1;
|
||||
val |= (mask & S5PCSIS_DPHYCTRL_ENABLE);
|
||||
}
|
||||
s5pcsis_write(state, S5PCSIS_DPHYCTRL, val);
|
||||
|
@ -321,15 +337,14 @@ static void s5pcsis_set_hsync_settle(struct csis_state *state, int settle)
|
|||
|
||||
static void s5pcsis_set_params(struct csis_state *state)
|
||||
{
|
||||
struct s5p_platform_mipi_csis *pdata = state->pdev->dev.platform_data;
|
||||
u32 val;
|
||||
|
||||
val = s5pcsis_read(state, S5PCSIS_CONFIG);
|
||||
val = (val & ~S5PCSIS_CFG_NR_LANE_MASK) | (pdata->lanes - 1);
|
||||
val = (val & ~S5PCSIS_CFG_NR_LANE_MASK) | (state->num_lanes - 1);
|
||||
s5pcsis_write(state, S5PCSIS_CONFIG, val);
|
||||
|
||||
__s5pcsis_set_format(state);
|
||||
s5pcsis_set_hsync_settle(state, pdata->hs_settle);
|
||||
s5pcsis_set_hsync_settle(state, state->hs_settle);
|
||||
|
||||
val = s5pcsis_read(state, S5PCSIS_CTRL);
|
||||
if (state->csis_fmt->data_alignment == 32)
|
||||
|
@ -338,7 +353,7 @@ static void s5pcsis_set_params(struct csis_state *state)
|
|||
val &= ~S5PCSIS_CTRL_ALIGN_32BIT;
|
||||
|
||||
val &= ~S5PCSIS_CTRL_WCLK_EXTCLK;
|
||||
if (pdata->wclk_source)
|
||||
if (state->wclk_ext)
|
||||
val |= S5PCSIS_CTRL_WCLK_EXTCLK;
|
||||
s5pcsis_write(state, S5PCSIS_CTRL, val);
|
||||
|
||||
|
@ -701,52 +716,111 @@ static irqreturn_t s5pcsis_irq_handler(int irq, void *dev_id)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int s5pcsis_get_platform_data(struct platform_device *pdev,
|
||||
struct csis_state *state)
|
||||
{
|
||||
struct s5p_platform_mipi_csis *pdata = pdev->dev.platform_data;
|
||||
|
||||
if (pdata == NULL) {
|
||||
dev_err(&pdev->dev, "Platform data not specified\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
state->clk_frequency = pdata->clk_rate;
|
||||
state->num_lanes = pdata->lanes;
|
||||
state->hs_settle = pdata->hs_settle;
|
||||
state->index = max(0, pdev->id);
|
||||
state->max_num_lanes = state->index ? CSIS1_MAX_LANES :
|
||||
CSIS0_MAX_LANES;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int s5pcsis_parse_dt(struct platform_device *pdev,
|
||||
struct csis_state *state)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct v4l2_of_endpoint endpoint;
|
||||
|
||||
if (of_property_read_u32(node, "clock-frequency",
|
||||
&state->clk_frequency))
|
||||
state->clk_frequency = DEFAULT_SCLK_CSIS_FREQ;
|
||||
if (of_property_read_u32(node, "bus-width",
|
||||
&state->max_num_lanes))
|
||||
return -EINVAL;
|
||||
|
||||
node = v4l2_of_get_next_endpoint(node, NULL);
|
||||
if (!node) {
|
||||
dev_err(&pdev->dev, "No port node at %s\n",
|
||||
node->full_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Get port node and validate MIPI-CSI channel id. */
|
||||
v4l2_of_parse_endpoint(node, &endpoint);
|
||||
|
||||
state->index = endpoint.port - FIMC_INPUT_MIPI_CSI2_0;
|
||||
if (state->index < 0 || state->index >= CSIS_MAX_ENTITIES)
|
||||
return -ENXIO;
|
||||
|
||||
/* Get MIPI CSI-2 bus configration from the endpoint node. */
|
||||
of_property_read_u32(node, "samsung,csis-hs-settle",
|
||||
&state->hs_settle);
|
||||
state->wclk_ext = of_property_read_bool(node,
|
||||
"samsung,csis-wclk");
|
||||
|
||||
state->num_lanes = endpoint.bus.mipi_csi2.num_data_lanes;
|
||||
|
||||
of_node_put(node);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define s5pcsis_parse_dt(pdev, state) (-ENOSYS)
|
||||
#endif
|
||||
|
||||
static int s5pcsis_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct s5p_platform_mipi_csis *pdata;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *mem_res;
|
||||
struct csis_state *state;
|
||||
int ret = -ENOMEM;
|
||||
int i;
|
||||
|
||||
state = devm_kzalloc(&pdev->dev, sizeof(*state), GFP_KERNEL);
|
||||
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&state->lock);
|
||||
spin_lock_init(&state->slock);
|
||||
|
||||
state->pdev = pdev;
|
||||
state->index = max(0, pdev->id);
|
||||
|
||||
pdata = pdev->dev.platform_data;
|
||||
if (pdata == NULL) {
|
||||
dev_err(&pdev->dev, "Platform data not fully specified\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (dev->of_node)
|
||||
ret = s5pcsis_parse_dt(pdev, state);
|
||||
else
|
||||
ret = s5pcsis_get_platform_data(pdev, state);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if ((state->index == 1 && pdata->lanes > CSIS1_MAX_LANES) ||
|
||||
pdata->lanes > CSIS0_MAX_LANES) {
|
||||
dev_err(&pdev->dev, "Unsupported number of data lanes: %d\n",
|
||||
pdata->lanes);
|
||||
if (state->num_lanes == 0 || state->num_lanes > state->max_num_lanes) {
|
||||
dev_err(dev, "Unsupported number of data lanes: %d (max. %d)\n",
|
||||
state->num_lanes, state->max_num_lanes);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
state->regs = devm_ioremap_resource(&pdev->dev, mem_res);
|
||||
state->regs = devm_ioremap_resource(dev, mem_res);
|
||||
if (IS_ERR(state->regs))
|
||||
return PTR_ERR(state->regs);
|
||||
|
||||
state->irq = platform_get_irq(pdev, 0);
|
||||
if (state->irq < 0) {
|
||||
dev_err(&pdev->dev, "Failed to get irq\n");
|
||||
dev_err(dev, "Failed to get irq\n");
|
||||
return state->irq;
|
||||
}
|
||||
|
||||
for (i = 0; i < CSIS_NUM_SUPPLIES; i++)
|
||||
state->supplies[i].supply = csis_supply_name[i];
|
||||
|
||||
ret = devm_regulator_bulk_get(&pdev->dev, CSIS_NUM_SUPPLIES,
|
||||
ret = devm_regulator_bulk_get(dev, CSIS_NUM_SUPPLIES,
|
||||
state->supplies);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -755,11 +829,11 @@ static int s5pcsis_probe(struct platform_device *pdev)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (pdata->clk_rate)
|
||||
if (state->clk_frequency)
|
||||
ret = clk_set_rate(state->clock[CSIS_CLK_MUX],
|
||||
pdata->clk_rate);
|
||||
state->clk_frequency);
|
||||
else
|
||||
dev_WARN(&pdev->dev, "No clock frequency specified!\n");
|
||||
dev_WARN(dev, "No clock frequency specified!\n");
|
||||
if (ret < 0)
|
||||
goto e_clkput;
|
||||
|
||||
|
@ -767,16 +841,17 @@ static int s5pcsis_probe(struct platform_device *pdev)
|
|||
if (ret < 0)
|
||||
goto e_clkput;
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, state->irq, s5pcsis_irq_handler,
|
||||
0, dev_name(&pdev->dev), state);
|
||||
ret = devm_request_irq(dev, state->irq, s5pcsis_irq_handler,
|
||||
0, dev_name(dev), state);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Interrupt request failed\n");
|
||||
dev_err(dev, "Interrupt request failed\n");
|
||||
goto e_clkdis;
|
||||
}
|
||||
|
||||
v4l2_subdev_init(&state->sd, &s5pcsis_subdev_ops);
|
||||
state->sd.owner = THIS_MODULE;
|
||||
strlcpy(state->sd.name, dev_name(&pdev->dev), sizeof(state->sd.name));
|
||||
snprintf(state->sd.name, sizeof(state->sd.name), "%s.%d",
|
||||
CSIS_SUBDEV_NAME, state->index);
|
||||
state->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
||||
state->csis_fmt = &s5pcsis_formats[0];
|
||||
|
||||
|
@ -796,10 +871,12 @@ static int s5pcsis_probe(struct platform_device *pdev)
|
|||
|
||||
/* .. and a pointer to the subdev. */
|
||||
platform_set_drvdata(pdev, &state->sd);
|
||||
|
||||
memcpy(state->events, s5pcsis_events, sizeof(state->events));
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
dev_info(&pdev->dev, "lanes: %d, hs_settle: %d, wclk: %d, freq: %u\n",
|
||||
state->num_lanes, state->hs_settle, state->wclk_ext,
|
||||
state->clk_frequency);
|
||||
return 0;
|
||||
|
||||
e_clkdis:
|
||||
|
@ -923,13 +1000,21 @@ static const struct dev_pm_ops s5pcsis_pm_ops = {
|
|||
SET_SYSTEM_SLEEP_PM_OPS(s5pcsis_suspend, s5pcsis_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id s5pcsis_of_match[] = {
|
||||
{ .compatible = "samsung,s5pv210-csis" },
|
||||
{ .compatible = "samsung,exynos4210-csis" },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, s5pcsis_of_match);
|
||||
|
||||
static struct platform_driver s5pcsis_driver = {
|
||||
.probe = s5pcsis_probe,
|
||||
.remove = s5pcsis_remove,
|
||||
.driver = {
|
||||
.name = CSIS_DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &s5pcsis_pm_ops,
|
||||
.of_match_table = s5pcsis_of_match,
|
||||
.name = CSIS_DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &s5pcsis_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#define S5P_MIPI_CSIS_H_
|
||||
|
||||
#define CSIS_DRIVER_NAME "s5p-mipi-csis"
|
||||
#define CSIS_SUBDEV_NAME CSIS_DRIVER_NAME
|
||||
#define CSIS_MAX_ENTITIES 2
|
||||
#define CSIS0_MAX_LANES 4
|
||||
#define CSIS1_MAX_LANES 2
|
||||
|
|
|
@ -14,6 +14,19 @@
|
|||
|
||||
#include <media/media-entity.h>
|
||||
|
||||
/*
|
||||
* Enumeration of data inputs to the camera subsystem.
|
||||
*/
|
||||
enum fimc_input {
|
||||
FIMC_INPUT_PARALLEL_0 = 1,
|
||||
FIMC_INPUT_PARALLEL_1,
|
||||
FIMC_INPUT_MIPI_CSI2_0 = 3,
|
||||
FIMC_INPUT_MIPI_CSI2_1,
|
||||
FIMC_INPUT_WRITEBACK_A = 5,
|
||||
FIMC_INPUT_WRITEBACK_B,
|
||||
FIMC_INPUT_WRITEBACK_ISP = 5,
|
||||
};
|
||||
|
||||
/*
|
||||
* Enumeration of the FIMC data bus types.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue