diff options
Diffstat (limited to 'drivers/media/v4l2-core/v4l2-i2c.c')
| -rw-r--r-- | drivers/media/v4l2-core/v4l2-i2c.c | 184 | 
1 files changed, 184 insertions, 0 deletions
diff --git a/drivers/media/v4l2-core/v4l2-i2c.c b/drivers/media/v4l2-core/v4l2-i2c.c new file mode 100644 index 000000000000..5bf99e7c0c09 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-i2c.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * v4l2-i2c - I2C helpers for Video4Linux2 + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> + +void v4l2_i2c_subdev_unregister(struct v4l2_subdev *sd) +{ +	struct i2c_client *client = v4l2_get_subdevdata(sd); + +	/* +	 * We need to unregister the i2c client +	 * explicitly. We cannot rely on +	 * i2c_del_adapter to always unregister +	 * clients for us, since if the i2c bus is a +	 * platform bus, then it is never deleted. +	 * +	 * Device tree or ACPI based devices must not +	 * be unregistered as they have not been +	 * registered by us, and would not be +	 * re-created by just probing the V4L2 driver. +	 */ +	if (client && !client->dev.of_node && !client->dev.fwnode) +		i2c_unregister_device(client); +} + +void v4l2_i2c_subdev_set_name(struct v4l2_subdev *sd, +			      struct i2c_client *client, +			      const char *devname, const char *postfix) +{ +	if (!devname) +		devname = client->dev.driver->name; +	if (!postfix) +		postfix = ""; + +	snprintf(sd->name, sizeof(sd->name), "%s%s %d-%04x", devname, postfix, +		 i2c_adapter_id(client->adapter), client->addr); +} +EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_set_name); + +void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client, +			  const struct v4l2_subdev_ops *ops) +{ +	v4l2_subdev_init(sd, ops); +	sd->flags |= V4L2_SUBDEV_FL_IS_I2C; +	/* the owner is the same as the i2c_client's driver owner */ +	sd->owner = client->dev.driver->owner; +	sd->dev = &client->dev; +	/* i2c_client and v4l2_subdev point to one another */ +	v4l2_set_subdevdata(sd, client); +	i2c_set_clientdata(client, sd); +	v4l2_i2c_subdev_set_name(sd, client, NULL, NULL); +} +EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_init); + +/* Load an i2c sub-device. */ +struct v4l2_subdev +*v4l2_i2c_new_subdev_board(struct v4l2_device *v4l2_dev, +			   struct i2c_adapter *adapter, +			   struct i2c_board_info *info, +			   const unsigned short *probe_addrs) +{ +	struct v4l2_subdev *sd = NULL; +	struct i2c_client *client; + +	if (!v4l2_dev) +		return NULL; + +	request_module(I2C_MODULE_PREFIX "%s", info->type); + +	/* Create the i2c client */ +	if (info->addr == 0 && probe_addrs) +		client = i2c_new_probed_device(adapter, info, probe_addrs, +					       NULL); +	else +		client = i2c_new_device(adapter, info); + +	/* +	 * Note: by loading the module first we are certain that c->driver +	 * will be set if the driver was found. If the module was not loaded +	 * first, then the i2c core tries to delay-load the module for us, +	 * and then c->driver is still NULL until the module is finally +	 * loaded. This delay-load mechanism doesn't work if other drivers +	 * want to use the i2c device, so explicitly loading the module +	 * is the best alternative. +	 */ +	if (!client || !client->dev.driver) +		goto error; + +	/* Lock the module so we can safely get the v4l2_subdev pointer */ +	if (!try_module_get(client->dev.driver->owner)) +		goto error; +	sd = i2c_get_clientdata(client); + +	/* +	 * Register with the v4l2_device which increases the module's +	 * use count as well. +	 */ +	if (v4l2_device_register_subdev(v4l2_dev, sd)) +		sd = NULL; +	/* Decrease the module use count to match the first try_module_get. */ +	module_put(client->dev.driver->owner); + +error: +	/* +	 * If we have a client but no subdev, then something went wrong and +	 * we must unregister the client. +	 */ +	if (client && !sd) +		i2c_unregister_device(client); +	return sd; +} +EXPORT_SYMBOL_GPL(v4l2_i2c_new_subdev_board); + +struct v4l2_subdev *v4l2_i2c_new_subdev(struct v4l2_device *v4l2_dev, +					struct i2c_adapter *adapter, +					const char *client_type, +					u8 addr, +					const unsigned short *probe_addrs) +{ +	struct i2c_board_info info; + +	/* +	 * Setup the i2c board info with the device type and +	 * the device address. +	 */ +	memset(&info, 0, sizeof(info)); +	strscpy(info.type, client_type, sizeof(info.type)); +	info.addr = addr; + +	return v4l2_i2c_new_subdev_board(v4l2_dev, adapter, &info, +					 probe_addrs); +} +EXPORT_SYMBOL_GPL(v4l2_i2c_new_subdev); + +/* Return i2c client address of v4l2_subdev. */ +unsigned short v4l2_i2c_subdev_addr(struct v4l2_subdev *sd) +{ +	struct i2c_client *client = v4l2_get_subdevdata(sd); + +	return client ? client->addr : I2C_CLIENT_END; +} +EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_addr); + +/* + * Return a list of I2C tuner addresses to probe. Use only if the tuner + * addresses are unknown. + */ +const unsigned short *v4l2_i2c_tuner_addrs(enum v4l2_i2c_tuner_type type) +{ +	static const unsigned short radio_addrs[] = { +#if IS_ENABLED(CONFIG_MEDIA_TUNER_TEA5761) +		0x10, +#endif +		0x60, +		I2C_CLIENT_END +	}; +	static const unsigned short demod_addrs[] = { +		0x42, 0x43, 0x4a, 0x4b, +		I2C_CLIENT_END +	}; +	static const unsigned short tv_addrs[] = { +		0x42, 0x43, 0x4a, 0x4b,		/* tda8290 */ +		0x60, 0x61, 0x62, 0x63, 0x64, +		I2C_CLIENT_END +	}; + +	switch (type) { +	case ADDRS_RADIO: +		return radio_addrs; +	case ADDRS_DEMOD: +		return demod_addrs; +	case ADDRS_TV: +		return tv_addrs; +	case ADDRS_TV_WITH_DEMOD: +		return tv_addrs + 4; +	} +	return NULL; +} +EXPORT_SYMBOL_GPL(v4l2_i2c_tuner_addrs);  |