In most cases when a VCM is used there is a single integrated module with the sensor + VCM + lens. This means that the sensor and VCM often share regulators and possibly also something like a powerdown pin. In the ACPI tables this is modelled as a single ACPI device with multiple I2cSerialBus resources. On atomisp devices the regulators and clks are modelled as ACPI power-resources, which are controlled by the (ACPI) power state of the sensor. So the sensor must be in D0 power state for the VCM to work. To make this work add a device-link with DL_FLAG_PM_RUNTIME flag so that the sensor will automatically be runtime-resumed whenever the VCM is runtime-resumed. This requires the probing of the VCM and thus the creation of the VCM I2C-client to be delayed till after the sensor driver has bound. Move the instantiation of the VCM I2C-client to the v4l2_async_notifier bound op, so that it is done after the sensor driver has bound; and add code to add the device-link. This fixes the problem with the shared ACPI power-resources on atomisp2 and this avoids the need for VCM related workarounds on IPU3. E.g. until now the dw9719 driver needed to get and control a Vsio (V sensor IO) regulator since that needs to be enabled to enable I2C pass-through on the PMIC on the sensor module. So the driver was controlling this regulator even though the actual dw9719 chip has no Vsio pin / power-plane. This also removes the need for intel_cio2_bridge_init() to return -EPROBE_DEFER since the VCM is now instantiated later. Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx> --- drivers/media/common/intel-cio2-bridge.c | 160 +++++++++++------- drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 5 + drivers/media/pci/intel/ipu3/ipu3-cio2.h | 3 + include/media/intel-cio2-bridge.h | 4 +- 4 files changed, 111 insertions(+), 61 deletions(-) diff --git a/drivers/media/common/intel-cio2-bridge.c b/drivers/media/common/intel-cio2-bridge.c index 2f4256f9152c..997581bcc3b9 100644 --- a/drivers/media/common/intel-cio2-bridge.c +++ b/drivers/media/common/intel-cio2-bridge.c @@ -5,7 +5,9 @@ #include <linux/acpi.h> #include <linux/device.h> #include <linux/i2c.h> +#include <linux/pm_runtime.h> #include <linux/property.h> +#include <linux/workqueue.h> #include <media/intel-cio2-bridge.h> #include <media/v4l2-fwnode.h> @@ -189,30 +191,111 @@ static void cio2_bridge_create_connection_swnodes( cio2_bridge_init_swnode_group(sensor); } -static void cio2_bridge_instantiate_vcm_i2c_client( - struct intel_cio2_sensor *sensor) -{ - struct i2c_board_info board_info = { }; +/* + * The actual instantiation must be done from a workqueue to avoid + * a deadlock on taking list_lock from v4l2-async twice. + */ +struct intel_cio2_bridge_instantiate_vcm_work_data { + struct work_struct work; + struct device *sensor; char name[16]; + struct i2c_board_info board_info; +}; - if (!sensor->vcm_type) - return; +static void intel_cio2_bridge_instantiate_vcm_work(struct work_struct *_work) +{ + struct intel_cio2_bridge_instantiate_vcm_work_data *work = + container_of(_work, + struct intel_cio2_bridge_instantiate_vcm_work_data, + work); + struct acpi_device *adev = ACPI_COMPANION(work->sensor); + struct i2c_client *vcm_client; + bool put_fwnode = true; + int ret; - snprintf(name, sizeof(name), "%s-VCM", acpi_dev_name(sensor->adev)); - board_info.dev_name = name; - strscpy(board_info.type, sensor->vcm_type, ARRAY_SIZE(board_info.type)); - board_info.swnode = &sensor->swnodes[SWNODE_VCM]; - - sensor->vcm_i2c_client = - i2c_acpi_new_device_by_fwnode(acpi_fwnode_handle(sensor->adev), - 1, &board_info); - if (IS_ERR(sensor->vcm_i2c_client)) { - dev_warn(&sensor->adev->dev, "Error instantiation VCM i2c-client: %ld\n", - PTR_ERR(sensor->vcm_i2c_client)); - sensor->vcm_i2c_client = NULL; + /* + * The client may get probed before the device_link gets added below + * make sure the sensor is powered-up during probe. + */ + ret = pm_runtime_get_sync(work->sensor); + if (ret < 0) { + dev_err(work->sensor, "Error %d runtime-resuming sensor, cannot instantiate VCM\n", + ret); + goto out; } + + /* + * Note the client is created only once and then kept around + * even after a rmmod, just like the software-nodes. + */ + vcm_client = i2c_acpi_new_device_by_fwnode(acpi_fwnode_handle(adev), + 1, &work->board_info); + if (IS_ERR(vcm_client)) { + dev_err(work->sensor, "Error instantiating VCM client: %ld\n", + PTR_ERR(vcm_client)); + goto out; + } + + device_link_add(&vcm_client->dev, work->sensor, DL_FLAG_PM_RUNTIME); + + dev_info(work->sensor, "Instantiated %s VCM\n", work->board_info.type); + put_fwnode = false; /* Ownership has passed to the i2c-client */ + +out: + pm_runtime_put(work->sensor); + put_device(work->sensor); + if (put_fwnode) + fwnode_handle_put(work->board_info.fwnode); + kfree(work); } +int intel_cio2_bridge_instantiate_vcm(struct device *sensor) +{ + struct intel_cio2_bridge_instantiate_vcm_work_data *work; + struct acpi_device *adev = ACPI_COMPANION(sensor); + struct fwnode_handle *vcm_fwnode; + struct i2c_client *vcm_client; + char *sep; + + if (!adev) + return 0; + + vcm_fwnode = fwnode_find_reference(dev_fwnode(sensor), "lens-focus", 0); + if (IS_ERR(vcm_fwnode)) + return 0; + + /* When reloading modules the client will already exist */ + vcm_client = i2c_find_device_by_fwnode(vcm_fwnode); + if (vcm_client) { + fwnode_handle_put(vcm_fwnode); + put_device(&vcm_client->dev); + return 0; + } + + work = kzalloc(sizeof(*work), GFP_KERNEL); + if (!work) { + fwnode_handle_put(vcm_fwnode); + return -ENOMEM; + } + + INIT_WORK(&work->work, intel_cio2_bridge_instantiate_vcm_work); + work->sensor = get_device(sensor); + snprintf(work->name, sizeof(work->name), "%s-VCM", + acpi_dev_name(adev)); + work->board_info.dev_name = work->name; + work->board_info.fwnode = vcm_fwnode; + strscpy(work->board_info.type, fwnode_get_name(vcm_fwnode), + I2C_NAME_SIZE); + /* Strip "-<link>" postfix */ + sep = strchrnul(work->board_info.type, '-'); + *sep = 0; + + queue_work(system_long_wq, &work->work); + + return 0; +} +EXPORT_SYMBOL(intel_cio2_bridge_instantiate_vcm); + static void cio2_bridge_unregister_sensors(struct intel_cio2_bridge *bridge) { struct intel_cio2_sensor *sensor; @@ -222,7 +305,6 @@ static void cio2_bridge_unregister_sensors(struct intel_cio2_bridge *bridge) sensor = &bridge->sensors[i]; software_node_unregister_node_group(sensor->group); acpi_dev_put(sensor->adev); - i2c_unregister_device(sensor->vcm_i2c_client); } } @@ -273,8 +355,6 @@ static int cio2_bridge_connect_sensor( primary = acpi_fwnode_handle(adev); primary->secondary = fwnode; - cio2_bridge_instantiate_vcm_i2c_client(sensor); - dev_info(bridge->dev, "Found supported sensor %s\n", acpi_dev_name(adev)); @@ -311,41 +391,6 @@ static int cio2_bridge_connect_sensors(struct intel_cio2_bridge *bridge) return ret; } -/* - * The VCM cannot be probed until the PMIC is completely setup. We cannot rely - * on -EPROBE_DEFER for this, since the consumer<->supplier relations between - * the VCM and regulators/clks are not described in ACPI, instead they are - * passed as board-data to the PMIC drivers. Since -PROBE_DEFER does not work - * for the clks/regulators the VCM i2c-clients must not be instantiated until - * the PMIC is fully setup. - * - * The sensor/VCM ACPI device has an ACPI _DEP on the PMIC, check this using the - * acpi_dev_ready_for_enumeration() helper, like the i2c-core-acpi code does - * for the sensors. - */ -static int cio2_bridge_sensors_are_ready( - const struct intel_cio2_sensor_config *supported_sensors) -{ - struct acpi_device *adev; - bool ready = true; - unsigned int i; - - for (i = 0; supported_sensors[i].hid; i++) { - const struct intel_cio2_sensor_config *cfg = - &supported_sensors[i]; - - for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { - if (!adev->status.enabled) - continue; - - if (!acpi_dev_ready_for_enumeration(adev)) - ready = false; - } - } - - return ready; -} - int intel_cio2_bridge_init( struct device *dev, int (*parse_sensor_fwnode)(struct acpi_device *adev, @@ -358,9 +403,6 @@ int intel_cio2_bridge_init( unsigned int i; int ret; - if (!cio2_bridge_sensors_are_ready(supported_sensors)) - return -EPROBE_DEFER; - bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); if (!bridge) return -ENOMEM; diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c index afc3255b5eb8..a22d213d66e9 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c @@ -1386,10 +1386,15 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, struct cio2_device *cio2 = to_cio2_device(notifier); struct sensor_async_subdev *s_asd = to_sensor_asd(asd); struct cio2_queue *q; + int ret; if (cio2->queue[s_asd->csi2.port].sensor) return -EBUSY; + ret = intel_cio2_bridge_instantiate_vcm(sd->dev); + if (ret) + return ret; + q = &cio2->queue[s_asd->csi2.port]; q->csi2 = s_asd->csi2; diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h index 7ff7915f9823..208d60364f44 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2.h +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h @@ -12,6 +12,7 @@ #include <asm/page.h> +#include <media/intel-cio2-bridge.h> #include <media/media-device.h> #include <media/media-entity.h> #include <media/v4l2-async.h> @@ -463,6 +464,8 @@ static inline struct cio2_queue *vb2q_to_cio2_queue(struct vb2_queue *vq) int cio2_bridge_init(struct device *dev); #else static inline int cio2_bridge_init(struct device *dev) { return 0; } +static inline int +intel_cio2_bridge_instantiate_vcm(struct device *dev) { return 0; } #endif #endif diff --git a/include/media/intel-cio2-bridge.h b/include/media/intel-cio2-bridge.h index 90fde180faef..faa60a5b1cd8 100644 --- a/include/media/intel-cio2-bridge.h +++ b/include/media/intel-cio2-bridge.h @@ -10,7 +10,6 @@ #include <media/v4l2-fwnode.h> struct acpi_device; -struct i2c_client; #define INTEL_CIO2_HID "INT343E" #define INTEL_CIO2_MAX_LANES 4 @@ -56,7 +55,6 @@ struct intel_cio2_sensor { /* append link(u8) in "-%u" format as suffix of HID */ char name[ACPI_ID_LEN + 4]; struct acpi_device *adev; - struct i2c_client *vcm_i2c_client; /* SWNODE_COUNT + 1 for terminating NULL */ const struct software_node *group[SWNODE_COUNT + 1]; @@ -99,6 +97,8 @@ int intel_cio2_bridge_init( const struct intel_cio2_sensor_config *cfg), const struct intel_cio2_sensor_config *supported_sensors); +int intel_cio2_bridge_instantiate_vcm(struct device *sensor); + enum v4l2_fwnode_orientation intel_cio2_bridge_get_orientation(struct acpi_device *adev); -- 2.41.0