+
static int soc_camera_video_start(struct soc_camera_device *icd);
static int video_dev_create(struct soc_camera_device *icd);
@@ -1336,6 +1350,10 @@ static int soc_camera_i2c_init(struct soc_camera_device *icd,
return -EPROBE_DEFER;
}
+ /* OF probing skips following I2C init */
+ if (shd->host_wait)
+ return 0;
+
ici = to_soc_camera_host(icd->parent);
adap = i2c_get_adapter(shd->i2c_adapter_id);
if (!adap) {
@@ -1573,10 +1591,180 @@ static void scan_async_host(struct soc_camera_host *ici)
asd += ici->asd_sizes[j];
}
}
+
+static void soc_camera_of_i2c_ifill(struct soc_camera_of_client *sofc,
+ struct i2c_client *client)
+{
+ struct i2c_board_info *info = &sofc->i2c_info;
+ struct soc_camera_desc *sdesc = sofc->sdesc;
+
+ /* on OF I2C devices platform_data == NULL */
+ info->flags = client->flags;
+ info->addr = client->addr;
+ info->irq = client->irq;
+ info->archdata = &sofc->archdata;
+
+ /* archdata is always empty on OF I2C devices */
+ strlcpy(info->type, client->name, sizeof(info->type));
+
+ sdesc->host_desc.i2c_adapter_id = client->adapter->nr;
+}
+
+static void soc_camera_of_i2c_info(struct device_node *node,
+ struct soc_camera_of_client *sofc)
+{
+ struct i2c_client *client;
+ struct soc_camera_desc *sdesc = sofc->sdesc;
+ struct i2c_board_info *info = &sofc->i2c_info;
+ struct device_node *port, *sensor, *bus;
+
+ port = v4l2_of_get_remote_port(node);
+ if (!port)
+ return;
+
+ /* Check the bus */
+ sensor = of_get_parent(port);
+ bus = of_get_parent(sensor);
+
+ if (of_node_cmp(bus->name, "i2c")) {
+ of_node_put(port);
+ of_node_put(sensor);
+ of_node_put(bus);
+ return;
+ }
+
+ info->of_node = sensor;
+ sdesc->host_desc.board_info = info;
+
+ client = of_find_i2c_device_by_node(sensor);
+ /*
+ * of_i2c_register_devices() took a reference to the OF node, it is not
+ * dropped, when the I2C device is removed, so, we don't need an
+ * additional reference.
+ */
+ of_node_put(sensor);
+ if (client) {
+ soc_camera_of_i2c_ifill(sofc, client);
+ sofc->link_node = sensor;
+ put_device(&client->dev);
+ }
+
+ /* client hasn't attached to I2C yet */
+}
+
+static struct soc_camera_of_client *soc_camera_of_alloc_client(const struct soc_camera_host *ici,
+ struct device_node *node)
+{
+ struct soc_camera_of_client *sofc;
+ struct v4l2_async_subdev *sensor;
+ struct soc_camera_desc sdesc = { .host_desc.host_wait = true,};
+ int i, ret;
+
+ sofc = devm_kzalloc(ici->v4l2_dev.dev, sizeof(*sofc), GFP_KERNEL);
+ if (!sofc)
+ return NULL;
+
+ /* Allocate v4l2_async_subdev by ourselves */
+ sensor = devm_kzalloc(ici->v4l2_dev.dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return NULL;
+ sofc->sasc.sensor = sensor;
+
+ mutex_lock(&list_lock);
+ i = find_first_zero_bit(device_map, MAP_MAX_NUM);
+ if (i < MAP_MAX_NUM)
+ set_bit(i, device_map);
+ mutex_unlock(&list_lock);
+ if (i >= MAP_MAX_NUM)
+ return NULL;
+ sofc->sasc.pdev = platform_device_alloc("soc-camera-pdrv", i);
+ if (!sofc->sasc.pdev)
+ return NULL;
+
+ sdesc.host_desc.node = &sofc->node;
+ sdesc.host_desc.bus_id = ici->nr;
+
+ ret = platform_device_add_data(sofc->sasc.pdev, &sdesc, sizeof(sdesc));
+ if (ret < 0)
+ return NULL;
+ sofc->sdesc = sofc->sasc.pdev->dev.platform_data;
+
+ soc_camera_of_i2c_info(node, sofc);
+
+ return sofc;
+}
+
+static void scan_of_host(struct soc_camera_host *ici)
+{
+ struct soc_camera_of_client *sofc;
+ struct soc_camera_async_client *sasc;
+ struct v4l2_async_subdev *asd;
+ struct soc_camera_device *icd;
+ struct device_node *node = NULL;
+
+ for (;;) {
+ int ret;
+
+ node = v4l2_of_get_next_endpoint(ici->v4l2_dev.dev->of_node,
+ node);
+ if (!node)
+ break;
+
+ sofc = soc_camera_of_alloc_client(ici, node);
+ if (!sofc) {
+ dev_err(ici->v4l2_dev.dev,
+ "%s(): failed to create a client device\n",
+ __func__);
+ of_node_put(node);
+ break;
+ }
+ v4l2_of_parse_endpoint(node, &sofc->node);
+
+ sasc = &sofc->sasc;
+ ret = platform_device_add(sasc->pdev);
+ if (ret < 0) {
+ /* Useless thing, but keep trying */
+ platform_device_put(sasc->pdev);
+ of_node_put(node);
+ continue;
+ }
+
+ /* soc_camera_pdrv_probe() probed successfully */
+ icd = platform_get_drvdata(sasc->pdev);
+ if (!icd) {
+ /* Cannot be... */
+ platform_device_put(sasc->pdev);
+ of_node_put(node);
+ continue;
+ }
+
+ asd = sasc->sensor;
+ asd->match_type = V4L2_ASYNC_MATCH_OF;
+ asd->match.of.node = sofc->link_node;
+
+ sasc->notifier.subdevs = &asd;
+ sasc->notifier.num_subdevs = 1;
+ sasc->notifier.bound = soc_camera_async_bound;
+ sasc->notifier.unbind = soc_camera_async_unbind;
+ sasc->notifier.complete = soc_camera_async_complete;
+
+ icd->parent = ici->v4l2_dev.dev;
+
+ ret = v4l2_async_notifier_register(&ici->v4l2_dev, &sasc->notifier);
+ if (!ret)
+ sjUmBMDe3fq35HMObreak;
+
+ /*
+ * We could destroy the icd in there error case here, but the
+ * non-OF version doesn't do that, so, we can keep it around too
+ */
+ }
+}
#else
#define soc_camera_i2c_init(icd, sdesc) (-ENODEV)
#define soc_camera_i2c_free(icd) do {} while (0)
#define scan_async_host(ici) do {} while (0)
+#define scan_of_host(ici) do {} while (0)
#endif
/* Called during host-driver probe */
@@ -1689,6 +1877,7 @@ static int soc_camera_remove(struct soc_camera_device *icd)
{
struct soc_camera_desc *sdesc = to_soc_camera_desc(icd);
struct video_device *vdev = icd->vdev;
+ struct v4l2_of_endpoint *node = sdesc->host_desc.node;
v4l2_ctrl_handler_free(&icd->ctrl_handler);
if (vdev) {
@@ -1719,6 +1908,17 @@ static int soc_camera_remove(struct soc_camera_device *icd)
if (icd->sasc)
platform_device_unregister(icd->sasc->pdev);
+ if (node) {
+ struct soc_camera_of_client *sofc = container_of(node,
+ struct soc_camera_of_client, node);
+ /* Don't dead-lock: remove the device here under the lock */
+ clear_bit(sofc->sasc.pdev->id, device_map);
+ list_del(&icd->list);
+ if (sofc->link_node)
+ of_node_put(sofc->link_node);
+ platform_device_unregister(sofc->sasc.pdev);
+ }
+