@@ -27,6 +27,7 @@
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
+#include <linux/spi/spi.h>
#include <linux/vmalloc.h>
#include <media/soc_camera.h>
@@ -1430,6 +1431,91 @@ static void soc_camera_i2c_free(struct soc_camera_device *icd)
icd->clk = NULL;
}
+static int soc_camera_spi_init(struct soc_camera_device *icd,
+ struct soc_camera_desc *sdesc)
+{
+ struct soc_camera_subdev_desc *ssdd;
+ struct spi_device *spi;
+ struct soc_camera_host *ici;
+ struct soc_camera_host_desc *shd = &sdesc->host_desc;
+ struct spi_master *spi_master;
+ struct v4l2_subdev *subdev;
+ char clk_name[V4L2_SUBDEV_NAME_SIZE];
+ int ret;
+
+ /* First find out how we link the main client */
+ if (icd->sasc) {
+ /* Async non-OF probing handled by the subdevice list */
+ return -EPROBE_DEFER;
+ }
+
+ ici = to_soc_camera_host(icd->parent);
+ spi_master = spi_busnum_to_master(shd->spi_bus_id);
+ if (!spi_master) {
+ dev_err(icd->pdev, "Cannot get SPI master #%d. No driver?\n",
+ shd->spi_bus_id);
+ return -ENODEV;
+ }
+
+ ssdd = kmemdup(&sdesc->subdev_desc, sizeof(*ssdd), GFP_KERNEL);
+ if (!ssdd)
+ return -ENOMEM;
+ /*
+ * In synchronous case we request regulators ourselves in
+ * soc_camera_pdrv_probe(), make sure the subdevice driver doesn't try
+ * to allocate them again.
+ */
+ ssdd->sd_pdata.num_regulators = 0;
+ ssdd->sd_pdata.regulators = NULL;
+ shd->board_info_spi->platform_data = ssdd;
+
+ snprintf(clk_name, sizeof(clk_name), "%d",
+ shd->spi_bus_id);
+
+ icd->clk = v4l2_clk_register(&soc_camera_clk_ops, clk_name, "mclk", icd);
+ if (IS_ERR(icd->clk)) {
+ ret = PTR_ERR(icd->clk);
+ goto eclkreg;
+ }
+
+ subdev = v4l2_spi_new_subdev(&ici->v4l2_dev, spi_master,
+ shd->board_info_spi);
+ if (!subdev) {
+ ret = -ENODEV;
+ goto espind;
+ }
+
+ spi = v4l2_get_subdevdata(subdev);
+
+ icd->control = &spi->dev;
+
+ return 0;
+espind:
+ v4l2_clk_unregister(icd->clk);
+ icd->clk = NULL;
+eclkreg:
+ kfree(ssdd);
+ return ret;
+}
+
+static void soc_camera_spi_free(struct soc_camera_device *icd)
+{
+ struct spi_device *spi =
+ to_spi_device(to_soc_camera_control(icd));
+ struct soc_camera_subdev_desc *ssdd;
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+
+ icd->control = NULL;
+ if (icd->sasc)
+ return;
+ ssdd = spi->dev.platform_data;
+ v4l2_device_unregister_subdev(sd);
+ spi_unregister_device(spi);
+ kfree(ssdd);
+ v4l2_clk_unregister(icd->clk);
+ icd->clk = NULL;
+}
+
/*
* V4L2 asynchronous notifier callbacks. They are all called under a v4l2-async
* internal global mutex, therefore cannot race against other asynchronous
@@ -1762,6 +1848,10 @@ static int soc_camera_probe(struct soc_camera_host *ici,
ret = soc_camera_i2c_init(icd, sdesc);
if (ret < 0 && ret != -EPROBE_DEFER)
goto eadd;
+ } else if (shd->board_info_spi) {
+ ret = soc_camera_spi_init(icd, sdesc);
+ if (ret < 0)
+ goto eadd;
} else if (!shd->add_device || !shd->del_device) {
ret = -EINVAL;
goto eadd;
@@ -1803,6 +1893,8 @@ static int soc_camera_probe(struct soc_camera_host *ici,
efinish:
if (shd->board_info) {
soc_camera_i2c_free(icd);
+ } else if (shd->board_info_spi) {
+ soc_camera_spi_free(icd);
} else {
shd->del_device(icd);
module_put(control->driver->owner);
@@ -1843,6 +1935,8 @@ static int soc_camera_remove(struct soc_camera_device *icd)
if (sdesc->host_desc.board_info) {
soc_camera_i2c_free(icd);
+ } else if (sdesc->host_desc.board_info_spi) {
+ soc_camera_spi_free(icd);
} else {
struct device *dev = to_soc_camera_control(icd);
struct device_driver *drv = dev ? dev->driver : NULL;
@@ -178,6 +178,8 @@ struct soc_camera_host_desc {
int i2c_adapter_id;
struct i2c_board_info *board_info;
const char *module_name;
+ struct spi_board_info *board_info_spi;
+ int spi_bus_id;
/*
* For non-I2C devices platform has to provide methods to add a device
@@ -243,6 +245,8 @@ struct soc_camera_link {
int i2c_adapter_id;
struct i2c_board_info *board_info;
const char *module_name;
+ struct spi_board_info *board_info_spi;
+ int spi_bus_id;
/*
* For non-I2C devices platform has to provide methods to add a device