18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * v4l2-spi - SPI helpers for Video4Linux2
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/module.h>
78c2ecf20Sopenharmony_ci#include <linux/spi/spi.h>
88c2ecf20Sopenharmony_ci#include <media/v4l2-common.h>
98c2ecf20Sopenharmony_ci#include <media/v4l2-device.h>
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_civoid v4l2_spi_subdev_unregister(struct v4l2_subdev *sd)
128c2ecf20Sopenharmony_ci{
138c2ecf20Sopenharmony_ci	struct spi_device *spi = v4l2_get_subdevdata(sd);
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci	if (spi && !spi->dev.of_node && !spi->dev.fwnode)
168c2ecf20Sopenharmony_ci		spi_unregister_device(spi);
178c2ecf20Sopenharmony_ci}
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_civoid v4l2_spi_subdev_init(struct v4l2_subdev *sd, struct spi_device *spi,
208c2ecf20Sopenharmony_ci			  const struct v4l2_subdev_ops *ops)
218c2ecf20Sopenharmony_ci{
228c2ecf20Sopenharmony_ci	v4l2_subdev_init(sd, ops);
238c2ecf20Sopenharmony_ci	sd->flags |= V4L2_SUBDEV_FL_IS_SPI;
248c2ecf20Sopenharmony_ci	/* the owner is the same as the spi_device's driver owner */
258c2ecf20Sopenharmony_ci	sd->owner = spi->dev.driver->owner;
268c2ecf20Sopenharmony_ci	sd->dev = &spi->dev;
278c2ecf20Sopenharmony_ci	/* spi_device and v4l2_subdev point to one another */
288c2ecf20Sopenharmony_ci	v4l2_set_subdevdata(sd, spi);
298c2ecf20Sopenharmony_ci	spi_set_drvdata(spi, sd);
308c2ecf20Sopenharmony_ci	/* initialize name */
318c2ecf20Sopenharmony_ci	snprintf(sd->name, sizeof(sd->name), "%s %s",
328c2ecf20Sopenharmony_ci		 spi->dev.driver->name, dev_name(&spi->dev));
338c2ecf20Sopenharmony_ci}
348c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(v4l2_spi_subdev_init);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistruct v4l2_subdev *v4l2_spi_new_subdev(struct v4l2_device *v4l2_dev,
378c2ecf20Sopenharmony_ci					struct spi_master *master,
388c2ecf20Sopenharmony_ci					struct spi_board_info *info)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	struct v4l2_subdev *sd = NULL;
418c2ecf20Sopenharmony_ci	struct spi_device *spi = NULL;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	if (!v4l2_dev)
448c2ecf20Sopenharmony_ci		return NULL;
458c2ecf20Sopenharmony_ci	if (info->modalias[0])
468c2ecf20Sopenharmony_ci		request_module(info->modalias);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	spi = spi_new_device(master, info);
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	if (!spi || !spi->dev.driver)
518c2ecf20Sopenharmony_ci		goto error;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	if (!try_module_get(spi->dev.driver->owner))
548c2ecf20Sopenharmony_ci		goto error;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	sd = spi_get_drvdata(spi);
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	/*
598c2ecf20Sopenharmony_ci	 * Register with the v4l2_device which increases the module's
608c2ecf20Sopenharmony_ci	 * use count as well.
618c2ecf20Sopenharmony_ci	 */
628c2ecf20Sopenharmony_ci	if (v4l2_device_register_subdev(v4l2_dev, sd))
638c2ecf20Sopenharmony_ci		sd = NULL;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	/* Decrease the module use count to match the first try_module_get. */
668c2ecf20Sopenharmony_ci	module_put(spi->dev.driver->owner);
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cierror:
698c2ecf20Sopenharmony_ci	/*
708c2ecf20Sopenharmony_ci	 * If we have a client but no subdev, then something went wrong and
718c2ecf20Sopenharmony_ci	 * we must unregister the client.
728c2ecf20Sopenharmony_ci	 */
738c2ecf20Sopenharmony_ci	if (!sd)
748c2ecf20Sopenharmony_ci		spi_unregister_device(spi);
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	return sd;
778c2ecf20Sopenharmony_ci}
788c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(v4l2_spi_new_subdev);
79