summaryrefslogtreecommitdiffstats
path: root/drivers/media/platform/s5p-fimc
diff options
context:
space:
mode:
authorSylwester Nawrocki <s.nawrocki@samsung.com>2013-03-29 18:12:39 +0100
committerMauro Carvalho Chehab <mchehab@redhat.com>2013-03-31 15:54:18 +0200
commit2b13f7d4e3822ed4de37b73b009ff81932e884bb (patch)
tree15a7e2ffc126318fdfd6244957ae64b18b03ddc9 /drivers/media/platform/s5p-fimc
parent[media] s5p-fimc: Add device tree support for the media device driver (diff)
downloadlinux-2b13f7d4e3822ed4de37b73b009ff81932e884bb.tar.xz
linux-2b13f7d4e3822ed4de37b73b009ff81932e884bb.zip
[media] s5p-fimc: Add device tree based sensors registration
The sensor (I2C and/or SPI client) devices are instantiated by their corresponding control bus drivers. Since the I2C client's master clock is often provided by a video bus receiver (host interface) or other than I2C/SPI controller device, the drivers of those client devices are not accessing hardware in their driver's probe() callback. Instead, after enabling clock, the host driver calls back into a sub-device when it wants to activate them. This pattern is used by some in-tree drivers and this patch also uses it for DT case. This patch is intended as a first step for adding device tree support to the S5P/Exynos SoC camera drivers. The second one is adding support for asynchronous sub-devices registration and clock control from sub-device driver level. The bindings shall not change when asynchronous probing support is added. 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>
Diffstat (limited to 'drivers/media/platform/s5p-fimc')
-rw-r--r--drivers/media/platform/s5p-fimc/fimc-mdevice.c240
1 files changed, 214 insertions, 26 deletions
diff --git a/drivers/media/platform/s5p-fimc/fimc-mdevice.c b/drivers/media/platform/s5p-fimc/fimc-mdevice.c
index d34106c66427..bfa02abd94c8 100644
--- a/drivers/media/platform/s5p-fimc/fimc-mdevice.c
+++ b/drivers/media/platform/s5p-fimc/fimc-mdevice.c
@@ -251,7 +251,7 @@ static struct v4l2_subdev *fimc_md_register_sensor(struct fimc_md *fmd,
sd->grp_id = GRP_ID_SENSOR;
v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice %s\n",
- s_info->pdata.board_info->type);
+ sd->name);
return sd;
}
@@ -263,13 +263,191 @@ static void fimc_md_unregister_sensor(struct v4l2_subdev *sd)
if (!client)
return;
v4l2_device_unregister_subdev(sd);
- adapter = client->adapter;
- i2c_unregister_device(client);
- if (adapter)
- i2c_put_adapter(adapter);
+
+ if (!client->dev.of_node) {
+ adapter = client->adapter;
+ i2c_unregister_device(client);
+ if (adapter)
+ i2c_put_adapter(adapter);
+ }
}
#ifdef CONFIG_OF
+/* Register I2C client subdev associated with @node. */
+static int fimc_md_of_add_sensor(struct fimc_md *fmd,
+ struct device_node *node, int index)
+{
+ struct fimc_sensor_info *si;
+ struct i2c_client *client;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ if (WARN_ON(index >= ARRAY_SIZE(fmd->sensor)))
+ return -EINVAL;
+ si = &fmd->sensor[index];
+
+ client = of_find_i2c_device_by_node(node);
+ if (!client)
+ return -EPROBE_DEFER;
+
+ device_lock(&client->dev);
+
+ if (!client->driver ||
+ !try_module_get(client->driver->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, 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, false);
+ if (ret < 0)
+ goto mod_put;
+
+ v4l2_set_subdev_hostdata(sd, si);
+ 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->driver->driver.owner);
+dev_put:
+ device_unlock(&client->dev);
+ put_device(&client->dev);
+ return ret;
+}
+
+/* Parse port node and register as a sub-device any sensor specified there. */
+static int fimc_md_parse_port_node(struct fimc_md *fmd,
+ struct device_node *port,
+ unsigned int index)
+{
+ struct device_node *rem, *ep, *np;
+ struct fimc_source_info *pd;
+ struct v4l2_of_endpoint endpoint;
+ int ret;
+ u32 val;
+
+ pd = &fmd->sensor[index].pdata;
+
+ /* Assume here a port node can have only one endpoint node. */
+ ep = of_get_next_child(port, NULL);
+ if (!ep)
+ return 0;
+
+ v4l2_of_parse_endpoint(ep, &endpoint);
+ if (WARN_ON(endpoint.port == 0) || index >= FIMC_MAX_SENSORS)
+ return -EINVAL;
+
+ pd->mux_id = (endpoint.port - 1) & 0x1;
+
+ rem = v4l2_of_get_remote_port_parent(ep);
+ of_node_put(ep);
+ if (rem == NULL) {
+ v4l2_info(&fmd->v4l2_dev, "Remote device at %s not found\n",
+ ep->full_name);
+ return 0;
+ }
+ if (!of_property_read_u32(rem, "samsung,camclk-out", &val))
+ pd->clk_id = val;
+
+ if (!of_property_read_u32(rem, "clock-frequency", &val))
+ pd->clk_frequency = val;
+
+ if (pd->clk_frequency == 0) {
+ v4l2_err(&fmd->v4l2_dev, "Wrong clock frequency at node %s\n",
+ rem->full_name);
+ of_node_put(rem);
+ return -EINVAL;
+ }
+
+ if (fimc_input_is_parallel(endpoint.port)) {
+ if (endpoint.bus_type == V4L2_MBUS_PARALLEL)
+ pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_601;
+ else
+ pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_656;
+ pd->flags = endpoint.bus.parallel.flags;
+ } else if (fimc_input_is_mipi_csi(endpoint.port)) {
+ /*
+ * MIPI CSI-2: only input mux selection and
+ * the sensor's clock frequency is needed.
+ */
+ pd->sensor_bus_type = FIMC_BUS_TYPE_MIPI_CSI2;
+ } else {
+ v4l2_err(&fmd->v4l2_dev, "Wrong port id (%u) at node %s\n",
+ endpoint.port, rem->full_name);
+ }
+ /*
+ * For FIMC-IS handled sensors, that are placed under i2c-isp device
+ * node, FIMC is connected to the FIMC-IS through its ISP Writeback
+ * input. Sensors are attached to the FIMC-LITE hostdata interface
+ * directly or through MIPI-CSIS, depending on the external media bus
+ * used. This needs to be handled in a more reliable way, not by just
+ * checking parent's node name.
+ */
+ np = of_get_parent(rem);
+
+ if (np && !of_node_cmp(np->name, "i2c-isp"))
+ pd->fimc_bus_type = FIMC_BUS_TYPE_ISP_WRITEBACK;
+ else
+ pd->fimc_bus_type = pd->sensor_bus_type;
+
+ ret = fimc_md_of_add_sensor(fmd, rem, index);
+ of_node_put(rem);
+
+ return ret;
+}
+
+/* Register all SoC external sub-devices */
+static int fimc_md_of_sensors_register(struct fimc_md *fmd,
+ struct device_node *np)
+{
+ struct device_node *parent = fmd->pdev->dev.of_node;
+ struct device_node *node, *ports;
+ int index = 0;
+ int ret;
+
+ /* Attach sensors linked to MIPI CSI-2 receivers */
+ for_each_available_child_of_node(parent, node) {
+ struct device_node *port;
+
+ if (of_node_cmp(node->name, "csis"))
+ continue;
+ /* The csis node can have only port subnode. */
+ port = of_get_next_child(node, NULL);
+ if (!port)
+ continue;
+
+ ret = fimc_md_parse_port_node(fmd, port, index);
+ if (ret < 0)
+ return ret;
+ index++;
+ }
+
+ /* Attach sensors listed in the parallel-ports node */
+ ports = of_get_child_by_name(parent, "parallel-ports");
+ if (!ports)
+ return 0;
+
+ for_each_child_of_node(ports, node) {
+ ret = fimc_md_parse_port_node(fmd, node, index);
+ if (ret < 0)
+ break;
+ index++;
+ }
+
+ return 0;
+}
+
static int __of_get_csis_id(struct device_node *np)
{
u32 reg = 0;
@@ -281,14 +459,17 @@ static int __of_get_csis_id(struct device_node *np)
return reg - FIMC_INPUT_MIPI_CSI2_0;
}
#else
+#define fimc_md_of_sensors_register(fmd, np) (-ENOSYS)
#define __of_get_csis_id(np) (-ENOSYS)
#endif
static int fimc_md_register_sensor_entities(struct fimc_md *fmd)
{
struct s5p_platform_fimc *pdata = fmd->pdev->dev.platform_data;
+ struct device_node *of_node = fmd->pdev->dev.of_node;
struct fimc_dev *fd = NULL;
- int num_clients, ret, i;
+ int num_clients = 0;
+ int ret, i;
/*
* Runtime resume one of the FIMC entities to make sure
@@ -299,34 +480,41 @@ static int fimc_md_register_sensor_entities(struct fimc_md *fmd)
fd = fmd->fimc[i];
if (!fd)
return -ENXIO;
+
ret = pm_runtime_get_sync(&fd->pdev->dev);
if (ret < 0)
return ret;
- WARN_ON(pdata->num_clients > ARRAY_SIZE(fmd->sensor));
- num_clients = min_t(u32, pdata->num_clients, ARRAY_SIZE(fmd->sensor));
+ if (of_node) {
+ fmd->num_sensors = 0;
+ ret = fimc_md_of_sensors_register(fmd, of_node);
+ } else if (pdata) {
+ WARN_ON(pdata->num_clients > ARRAY_SIZE(fmd->sensor));
+ num_clients = min_t(u32, pdata->num_clients,
+ ARRAY_SIZE(fmd->sensor));
+ fmd->num_sensors = num_clients;
- fmd->num_sensors = num_clients;
- for (i = 0; i < num_clients; i++) {
- struct v4l2_subdev *sd;
+ for (i = 0; i < num_clients; i++) {
+ struct v4l2_subdev *sd;
- fmd->sensor[i].pdata = pdata->source_info[i];
- ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], true);
- if (ret)
- break;
- sd = fimc_md_register_sensor(fmd, &fmd->sensor[i]);
- ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], false);
-
- if (!IS_ERR(sd)) {
+ fmd->sensor[i].pdata = pdata->source_info[i];
+ ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], true);
+ if (ret)
+ break;
+ sd = fimc_md_register_sensor(fmd, &fmd->sensor[i]);
+ ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], false);
+
+ if (IS_ERR(sd)) {
+ fmd->sensor[i].subdev = NULL;
+ ret = PTR_ERR(sd);
+ break;
+ }
fmd->sensor[i].subdev = sd;
- } else {
- fmd->sensor[i].subdev = NULL;
- ret = PTR_ERR(sd);
- break;
+ if (ret)
+ break;
}
- if (ret)
- break;
}
+
pm_runtime_put(&fd->pdev->dev);
return ret;
}
@@ -1037,7 +1225,7 @@ static int fimc_md_probe(struct platform_device *pdev)
if (ret)
goto err_unlock;
- if (dev->platform_data) {
+ if (dev->platform_data || dev->of_node) {
ret = fimc_md_register_sensor_entities(fmd);
if (ret)
goto err_unlock;