Message ID | 20230518153733.195306-10-hdegoede@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | media: atomisp: Add support for v4l2-async sensor registration | expand |
Hi, On 5/18/23 17:37, Hans de Goede wrote: > WIP > > Signed-off-by: Hans de Goede <hdegoede@redhat.com> My bad I forgot to update the commit message after finishing my work on this. Here is the new commit msg now in my local tree: """ media: atomisp: Add support for v4l2-async sensor registration Add support for using v4l2-async sensor registration. This has been tested with both the gc0310 and the ov2680 sensor drivers. Drivers should add the ACPI HIDs they match on to the supported_sensors[] array in the same commit as that they are convered to v4l2_async_register_subdev_sensor(). For now it also is still possible to use the old atomisp_gmin_platform based sensor drivers. This is mainly intended for testing while moving """ I have also made the supported_sensors[] array empty in my tree and moved the addition of the INT0310 and OVTI2680 HIDs to the commits converting the 2 sensor drivers to v4l2_async_register_subdev_sensor(), so that there is no intermittend state where the sensors will not work. Regards, Hans > --- > drivers/staging/media/atomisp/Makefile | 1 + > .../staging/media/atomisp/pci/atomisp_csi2.c | 4 + > .../staging/media/atomisp/pci/atomisp_csi2.h | 64 ++ > .../media/atomisp/pci/atomisp_csi2_bridge.c | 592 ++++++++++++++++++ > .../media/atomisp/pci/atomisp_internal.h | 2 + > .../staging/media/atomisp/pci/atomisp_v4l2.c | 38 +- > .../staging/media/atomisp/pci/atomisp_v4l2.h | 1 + > 7 files changed, 681 insertions(+), 21 deletions(-) > create mode 100644 drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c > > diff --git a/drivers/staging/media/atomisp/Makefile b/drivers/staging/media/atomisp/Makefile > index 532e12ed72e6..38b370124109 100644 > --- a/drivers/staging/media/atomisp/Makefile > +++ b/drivers/staging/media/atomisp/Makefile > @@ -16,6 +16,7 @@ atomisp-objs += \ > pci/atomisp_cmd.o \ > pci/atomisp_compat_css20.o \ > pci/atomisp_csi2.o \ > + pci/atomisp_csi2_bridge.o \ > pci/atomisp_drvfs.o \ > pci/atomisp_fops.o \ > pci/atomisp_ioctl.o \ > diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2.c b/drivers/staging/media/atomisp/pci/atomisp_csi2.c > index 0045c4d3a7f6..abf55a86f795 100644 > --- a/drivers/staging/media/atomisp/pci/atomisp_csi2.c > +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2.c > @@ -371,6 +371,10 @@ int atomisp_mipi_csi2_init(struct atomisp_device *isp) > unsigned int i; > int ret; > > + ret = atomisp_csi2_bridge_init(isp); > + if (ret < 0) > + return ret; > + > for (i = 0; i < ATOMISP_CAMERA_NR_PORTS; i++) { > csi2_port = &isp->csi2_port[i]; > csi2_port->isp = isp; > diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2.h b/drivers/staging/media/atomisp/pci/atomisp_csi2.h > index b245b2f5ce99..c714202db52a 100644 > --- a/drivers/staging/media/atomisp/pci/atomisp_csi2.h > +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2.h > @@ -21,14 +21,76 @@ > #include <media/v4l2-subdev.h> > #include <media/v4l2-ctrls.h> > > +#include "../../include/linux/atomisp.h" > + > #define CSI2_PAD_SINK 0 > #define CSI2_PAD_SOURCE 1 > #define CSI2_PADS_NUM 2 > > +#define CSI2_MAX_LANES 4 > + > struct atomisp_device; > struct v4l2_device; > struct atomisp_sub_device; > > +enum atomisp_csi2_sensor_swnodes { > + SWNODE_SENSOR, > + SWNODE_SENSOR_PORT, > + SWNODE_SENSOR_ENDPOINT, > + SWNODE_CSI2_PORT, > + SWNODE_CSI2_ENDPOINT, > + SWNODE_COUNT > +}; > + > +struct atomisp_csi2_property_names { > + char rotation[9]; > + char bus_type[9]; > + char data_lanes[11]; > + char remote_endpoint[16]; > +}; > + > +struct atomisp_csi2_node_names { > + char port[7]; > + char endpoint[11]; > + char remote_port[7]; > +}; > + > +struct atomisp_csi2_sensor_config { > + const char *hid; > + int lanes; > +}; > + > +struct atomisp_csi2_sensor { > + /* Append port in "-%u" format as suffix of HID */ > + char name[ACPI_ID_LEN + 4]; > + struct acpi_device *adev; > + int port; > + int lanes; > + > + /* SWNODE_COUNT + 1 for terminating NULL */ > + const struct software_node *group[SWNODE_COUNT + 1]; > + struct software_node swnodes[SWNODE_COUNT]; > + struct atomisp_csi2_node_names node_names; > + struct atomisp_csi2_property_names prop_names; > + /* "rotation" + terminating entry */ > + struct property_entry dev_properties[2]; > + /* "bus-type", "data-lanes", "remote-endpoint" + terminating entry */ > + struct property_entry ep_properties[4]; > + /* "data-lanes", "remote-endpoint" + terminating entry */ > + struct property_entry csi2_properties[3]; > + struct software_node_ref_args local_ref[1]; > + struct software_node_ref_args remote_ref[1]; > + struct software_node_ref_args vcm_ref[1]; > +}; > + > +struct atomisp_csi2_bridge { > + char csi2_node_name[14]; > + struct software_node csi2_node; > + u32 data_lanes[CSI2_MAX_LANES]; > + unsigned int n_sensors; > + struct atomisp_csi2_sensor sensors[ATOMISP_CAMERA_NR_PORTS]; > +}; > + > struct atomisp_mipi_csi2_device { > struct v4l2_subdev subdev; > struct media_pad pads[CSI2_PADS_NUM]; > @@ -48,6 +110,8 @@ void atomisp_mipi_csi2_unregister_entities( > struct atomisp_mipi_csi2_device *csi2); > int atomisp_mipi_csi2_register_entities(struct atomisp_mipi_csi2_device *csi2, > struct v4l2_device *vdev); > +int atomisp_csi2_bridge_init(struct atomisp_device *isp); > +int atomisp_csi2_bridge_parse_firmware(struct atomisp_device *isp); > > void atomisp_csi2_configure(struct atomisp_sub_device *asd); > > diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c b/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c > new file mode 100644 > index 000000000000..8539d7f354ae > --- /dev/null > +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c > @@ -0,0 +1,592 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Code to build software firmware node graph for atomisp2 connected sensors > + * from ACPI tables. > + * > + * Copyright (C) 2023 Hans de Goede <hdegoede@redhat.com> > + * > + * Based on drivers/media/pci/intel/ipu3/cio2-bridge.c written by: > + * Dan Scally <djrscally@gmail.com> > + */ > + > +#include <linux/acpi.h> > +#include <linux/device.h> > +#include <linux/dmi.h> > +#include <linux/property.h> > +#include <media/v4l2-fwnode.h> > + > +#include "atomisp_cmd.h" > +#include "atomisp_csi2.h" > +#include "atomisp_internal.h" > + > +#define NODE_SENSOR(_HID, _PROPS) \ > + ((const struct software_node) { \ > + .name = _HID, \ > + .properties = _PROPS, \ > + }) > + > +#define NODE_PORT(_PORT, _SENSOR_NODE) \ > + ((const struct software_node) { \ > + .name = _PORT, \ > + .parent = _SENSOR_NODE, \ > + }) > + > +#define NODE_ENDPOINT(_EP, _PORT, _PROPS) \ > + ((const struct software_node) { \ > + .name = _EP, \ > + .parent = _PORT, \ > + .properties = _PROPS, \ > + }) > + > +/* > + * Extend this array with ACPI Hardware IDs of sensors known to be working > + * plus the number of links expected by their drivers. > + * > + * Do not add an entry for a sensor that is not actually supported, > + * or which have not yet been converted to work without atomisp_gmin > + * power-management and with v4l2-async probing. > + */ > +static const struct atomisp_csi2_sensor_config supported_sensors[] = { > + /* GalaxyCore GC0310 */ > + { "INT0310", 1 }, > + /* Omnivision OV2680 */ > + { "OVTI2680", 1 }, > +}; > + > +/* > + * gmin_cfg parsing code. This is a cleaned up version of the gmin_cfg parsing > + * code from atomisp_gmin_platform.c. > + * Once all sensors are moved to v4l2-async probing atomisp_gmin_platform.c can > + * be removed and the duplication of this code goes away. > + */ > +struct gmin_cfg_var { > + const char *acpi_dev_name; > + const char *key; > + const char *val; > +}; > + > +static struct gmin_cfg_var lenovo_ideapad_miix_310_vars[] = { > + /* _DSM contains the wrong CsiPort! */ > + { "OVTI2680:01", "CsiPort", "0" }, > + {} > +}; > + > +static const struct dmi_system_id gmin_cfg_dmi_overrides[] = { > + { > + /* Lenovo Ideapad Miix 310 */ > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_VERSION, "MIIX 310-10"), > + }, > + .driver_data = lenovo_ideapad_miix_310_vars, > + }, > + {} > +}; > + > +static const guid_t atomisp_dsm_guid = GUID_INIT(0xdc2f6c4f, 0x045b, 0x4f1d, > + 0x97, 0xb9, 0x88, 0x2a, > + 0x68, 0x60, 0xa4, 0xbe); > + > +static char *gmin_cfg_get_dsm(struct acpi_device *adev, const char *key) > +{ > + union acpi_object *obj; > + char *val = NULL; > + int i; > + > + obj = acpi_evaluate_dsm_typed(adev->handle, &atomisp_dsm_guid, 0, 0, > + NULL, ACPI_TYPE_PACKAGE); > + if (!obj) > + return NULL; > + > + for (i = 0; i < obj->package.count - 1; i += 2) { > + /* The package should only contain strings */ > + if (obj->package.elements[i].type != ACPI_TYPE_STRING || > + obj->package.elements[i + 1].type != ACPI_TYPE_STRING) > + break; > + > + if (!strcmp(obj->package.elements[i].string.pointer, key)) { > + val = kstrdup(obj->package.elements[i + 1].string.pointer, GFP_KERNEL); > + dev_info(&adev->dev, "Using DSM entry %s=%s\n", key, val); > + break; > + } > + } > + > + ACPI_FREE(obj); > + return val; > +} > + > +static char *gmin_cfg_get_dmi_override(struct acpi_device *adev, const char *key) > +{ > + const struct dmi_system_id *id; > + struct gmin_cfg_var *gv; > + > + id = dmi_first_match(gmin_cfg_dmi_overrides); > + if (!id) > + return NULL; > + > + for (gv = id->driver_data; gv->acpi_dev_name; gv++) { > + if (strcmp(gv->acpi_dev_name, acpi_dev_name(adev))) > + continue; > + > + if (strcmp(key, gv->key)) > + continue; > + > + dev_info(&adev->dev, "Using DMI entry %s=%s\n", key, gv->val); > + return kstrdup(gv->val, GFP_KERNEL); > + } > + > + return NULL; > +} > + > +static char *gmin_cfg_get(struct acpi_device *adev, const char *key) > +{ > + char *val; > + > + val = gmin_cfg_get_dmi_override(adev, key); > + if (val) > + return val; > + > + return gmin_cfg_get_dsm(adev, key); > +} > + > +static int gmin_cfg_get_int(struct acpi_device *adev, const char *key, int default_val) > +{ > + char *str_val; > + long int_val; > + int ret; > + > + str_val = gmin_cfg_get(adev, key); > + if (!str_val) > + goto use_default; > + > + ret = kstrtoul(str_val, 0, &int_val); > + kfree(str_val); > + if (ret) > + goto use_default; > + > + return int_val; > + > +use_default: > + dev_info(&adev->dev, "Using default %s=%d\n", key, default_val); > + return default_val; > +} > + > +static int atomisp_csi2_get_pmc_clk_nr_from_acpi_pr0(struct acpi_device *adev) > +{ > + char name[5]; > + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; > + struct acpi_buffer b_name = { sizeof(name), name }; > + union acpi_object *package, *element; > + int clock_num = -ENOENT; > + acpi_handle rhandle; > + acpi_status status; > + int i; > + > + status = acpi_evaluate_object(adev->handle, "_PR0", NULL, &buffer); > + if (!ACPI_SUCCESS(status)) > + return -ENOENT; > + > + package = buffer.pointer; > + > + if (!buffer.length || !package || package->type != ACPI_TYPE_PACKAGE) > + goto fail; > + > + for (i = 0; i < package->package.count; i++) { > + element = &package->package.elements[i]; > + > + if (element->type != ACPI_TYPE_LOCAL_REFERENCE) > + continue; > + > + rhandle = element->reference.handle; > + if (!rhandle) > + continue; > + > + acpi_get_name(rhandle, ACPI_SINGLE_NAME, &b_name); > + > + if (strlen(name) == 4 && !strncmp(name, "CLK", 3) && > + name[3] >= '0' && name[3] <= '4') { > + clock_num = name[3] - '0'; > + break; > + } > + } > +fail: > + ACPI_FREE(buffer.pointer); > + > + return clock_num; > +} > + > +static int atomisp_csi2_get_port(struct acpi_device *adev) > +{ > + int clock_num, port; > + > + /* > + * Get pmc-clock number from ACPI _PR0 method and compare this to > + * the CsiPort 1 pmc-clock used in the CHT/BYT reference designs. > + */ > + clock_num = atomisp_csi2_get_pmc_clk_nr_from_acpi_pr0(adev); > + if (IS_ISP2401) > + port = clock_num == 4 ? 1 : 0; > + else > + port = clock_num == 0 ? 1 : 0; > + > + /* Intel DSM or DMI quirk overrides PR0 derived default */ > + port = gmin_cfg_get_int(adev, "CsiPort", port); > + > + return port; > +} > + > +static const struct atomisp_csi2_property_names prop_names = { > + .rotation = "rotation", > + .bus_type = "bus-type", > + .data_lanes = "data-lanes", > + .remote_endpoint = "remote-endpoint", > +}; > + > +static void atomisp_csi2_create_fwnode_properties(struct atomisp_csi2_sensor *sensor, > + struct atomisp_csi2_bridge *bridge, > + const struct atomisp_csi2_sensor_config *cfg) > +{ > + sensor->prop_names = prop_names; > + > + sensor->local_ref[0] = SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_CSI2_ENDPOINT]); > + sensor->remote_ref[0] = SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_SENSOR_ENDPOINT]); > + > + sensor->dev_properties[0] = PROPERTY_ENTRY_U32(sensor->prop_names.rotation, 0); > + > + sensor->ep_properties[0] = PROPERTY_ENTRY_U32(sensor->prop_names.bus_type, > + V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); > + sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN(sensor->prop_names.data_lanes, > + bridge->data_lanes, > + sensor->lanes); > + sensor->ep_properties[2] = PROPERTY_ENTRY_REF_ARRAY(sensor->prop_names.remote_endpoint, > + sensor->local_ref); > + > + sensor->csi2_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN(sensor->prop_names.data_lanes, > + bridge->data_lanes, > + sensor->lanes); > + sensor->csi2_properties[1] = PROPERTY_ENTRY_REF_ARRAY(sensor->prop_names.remote_endpoint, > + sensor->remote_ref); > +} > + > +static void atomisp_csi2_init_swnode_names(struct atomisp_csi2_sensor *sensor) > +{ > + snprintf(sensor->node_names.remote_port, > + sizeof(sensor->node_names.remote_port), > + SWNODE_GRAPH_PORT_NAME_FMT, sensor->port); > + snprintf(sensor->node_names.port, > + sizeof(sensor->node_names.port), > + SWNODE_GRAPH_PORT_NAME_FMT, 0); /* Always port 0 */ > + snprintf(sensor->node_names.endpoint, > + sizeof(sensor->node_names.endpoint), > + SWNODE_GRAPH_ENDPOINT_NAME_FMT, 0); /* And endpoint 0 */ > +} > + > +static void atomisp_csi2_init_swnode_group(struct atomisp_csi2_sensor *sensor) > +{ > + struct software_node *nodes = sensor->swnodes; > + > + sensor->group[SWNODE_SENSOR] = &nodes[SWNODE_SENSOR]; > + sensor->group[SWNODE_SENSOR_PORT] = &nodes[SWNODE_SENSOR_PORT]; > + sensor->group[SWNODE_SENSOR_ENDPOINT] = &nodes[SWNODE_SENSOR_ENDPOINT]; > + sensor->group[SWNODE_CSI2_PORT] = &nodes[SWNODE_CSI2_PORT]; > + sensor->group[SWNODE_CSI2_ENDPOINT] = &nodes[SWNODE_CSI2_ENDPOINT]; > +} > + > +static void atomisp_csi2_create_connection_swnodes(struct atomisp_csi2_bridge *bridge, > + struct atomisp_csi2_sensor *sensor) > +{ > + struct software_node *nodes = sensor->swnodes; > + > + atomisp_csi2_init_swnode_names(sensor); > + > + nodes[SWNODE_SENSOR] = NODE_SENSOR(sensor->name, > + sensor->dev_properties); > + nodes[SWNODE_SENSOR_PORT] = NODE_PORT(sensor->node_names.port, > + &nodes[SWNODE_SENSOR]); > + nodes[SWNODE_SENSOR_ENDPOINT] = NODE_ENDPOINT(sensor->node_names.endpoint, > + &nodes[SWNODE_SENSOR_PORT], > + sensor->ep_properties); > + nodes[SWNODE_CSI2_PORT] = NODE_PORT(sensor->node_names.remote_port, > + &bridge->csi2_node); > + nodes[SWNODE_CSI2_ENDPOINT] = NODE_ENDPOINT(sensor->node_names.endpoint, > + &nodes[SWNODE_CSI2_PORT], > + sensor->csi2_properties); > + > + atomisp_csi2_init_swnode_group(sensor); > +} > + > +static void atomisp_csi2_unregister_sensors(struct atomisp_csi2_bridge *bridge) > +{ > + struct atomisp_csi2_sensor *sensor; > + unsigned int i; > + > + for (i = 0; i < bridge->n_sensors; i++) { > + sensor = &bridge->sensors[i]; > + software_node_unregister_node_group(sensor->group); > + acpi_dev_put(sensor->adev); > + } > +} > + > +static int atomisp_csi2_connect_sensor(const struct atomisp_csi2_sensor_config *cfg, > + struct atomisp_csi2_bridge *bridge, > + struct atomisp_device *isp) > +{ > + struct fwnode_handle *fwnode, *primary; > + struct atomisp_csi2_sensor *sensor; > + struct acpi_device *adev; > + int ret; > + > + for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { > + if (!adev->status.enabled) > + continue; > + > + if (bridge->n_sensors >= ATOMISP_CAMERA_NR_PORTS) { > + dev_err(isp->dev, "Exceeded available CSI2 ports\n"); > + ret = -EINVAL; > + goto err_put_adev; > + } > + > + sensor = &bridge->sensors[bridge->n_sensors]; > + > + sensor->port = atomisp_csi2_get_port(adev); > + if (sensor->port >= ATOMISP_CAMERA_NR_PORTS) { > + dev_err(&adev->dev, "Invalid port: %d\n", sensor->port); > + ret = -EINVAL; > + goto err_put_adev; > + } > + > + sensor->lanes = gmin_cfg_get_int(adev, "CsiLanes", cfg->lanes); > + if (sensor->lanes > CSI2_MAX_LANES) { > + dev_err(&adev->dev, "Invalid number of lanes: %d\n", sensor->lanes); > + ret = -EINVAL; > + goto err_put_adev; > + } > + > + snprintf(sensor->name, sizeof(sensor->name), "%s-%u", > + cfg->hid, sensor->port); > + > + atomisp_csi2_create_fwnode_properties(sensor, bridge, cfg); > + atomisp_csi2_create_connection_swnodes(bridge, sensor); > + > + ret = software_node_register_node_group(sensor->group); > + if (ret) > + goto err_put_adev; > + > + fwnode = software_node_fwnode(&sensor->swnodes[SWNODE_SENSOR]); > + if (!fwnode) { > + ret = -ENODEV; > + goto err_free_swnodes; > + } > + > + sensor->adev = acpi_dev_get(adev); > + > + primary = acpi_fwnode_handle(adev); > + primary->secondary = fwnode; > + > + dev_info(isp->dev, "Found supported sensor %s\n", acpi_dev_name(adev)); > + > + bridge->n_sensors++; > + } > + > + return 0; > + > +err_free_swnodes: > + software_node_unregister_node_group(sensor->group); > +err_put_adev: > + acpi_dev_put(adev); > + return ret; > +} > + > +static int atomisp_csi2_connect_sensors(struct atomisp_csi2_bridge *bridge, > + struct atomisp_device *isp) > +{ > + unsigned int i; > + int ret; > + > + for (i = 0; i < ARRAY_SIZE(supported_sensors); i++) { > + const struct atomisp_csi2_sensor_config *cfg = &supported_sensors[i]; > + > + ret = atomisp_csi2_connect_sensor(cfg, bridge, isp); > + if (ret) > + goto err_unregister_sensors; > + } > + > + return 0; > + > +err_unregister_sensors: > + atomisp_csi2_unregister_sensors(bridge); > + return ret; > +} > + > +int atomisp_csi2_bridge_init(struct atomisp_device *isp) > +{ > + struct atomisp_csi2_bridge *bridge; > + struct device *dev = isp->dev; > + struct fwnode_handle *fwnode; > + int i, ret; > + > + /* > + * This function is intended to run only once and then leave > + * the created nodes attached even after a rmmod, therefor: > + * 1. The bridge memory is leaked deliberately on success > + * 2. If a secondary fwnode is already set exit early. > + */ > + if (dev->fwnode && dev->fwnode->secondary) > + return 0; > + > + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); > + if (!bridge) > + return -ENOMEM; > + > + strscpy(bridge->csi2_node_name, "atomisp-csi2", sizeof(bridge->csi2_node_name)); > + bridge->csi2_node.name = bridge->csi2_node_name; > + > + ret = software_node_register(&bridge->csi2_node); > + if (ret < 0) { > + dev_err(dev, "Failed to register the CSI2 HID node\n"); > + goto err_free_bridge; > + } > + > + /* > + * Map the lane arrangement, which is fixed for the ISP2 (meaning we > + * only need one, rather than one per sensor). We include it as a > + * member of the bridge struct rather than a global variable so > + * that it survives if the module is unloaded along with the rest of > + * the struct. > + */ > + for (i = 0; i < CSI2_MAX_LANES; i++) > + bridge->data_lanes[i] = i + 1; > + > + ret = atomisp_csi2_connect_sensors(bridge, isp); > + if (ret || bridge->n_sensors == 0) > + goto err_unregister_csi2; > + > + fwnode = software_node_fwnode(&bridge->csi2_node); > + if (!fwnode) { > + dev_err(dev, "Error getting fwnode from csi2 software_node\n"); > + ret = -ENODEV; > + goto err_unregister_sensors; > + } > + > + set_secondary_fwnode(dev, fwnode); > + > + return 0; > + > +err_unregister_sensors: > + atomisp_csi2_unregister_sensors(bridge); > +err_unregister_csi2: > + software_node_unregister(&bridge->csi2_node); > +err_free_bridge: > + kfree(bridge); > + > + return ret; > +} > + > +/******* V4L2 sub-device asynchronous registration callbacks***********/ > + > +struct sensor_async_subdev { > + struct v4l2_async_subdev asd; > + int port; > +}; > + > +#define to_sensor_asd(a) container_of(a, struct sensor_async_subdev, asd) > +#define notifier_to_atomisp(n) container_of(n, struct atomisp_device, notifier) > + > +/* .bound() notifier callback when a match is found */ > +static int atomisp_notifier_bound(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *sd, > + struct v4l2_async_subdev *asd) > +{ > + struct atomisp_device *isp = notifier_to_atomisp(notifier); > + struct sensor_async_subdev *s_asd = to_sensor_asd(asd); > + > + if (s_asd->port >= ATOMISP_CAMERA_NR_PORTS) { > + dev_err(isp->dev, "port %d not supported\n", s_asd->port); > + return -EINVAL; > + } > + > + if (isp->sensor_subdevs[s_asd->port]) { > + dev_err(isp->dev, "port %d already has a sensor attached\n", s_asd->port); > + return -EBUSY; > + } > + > + isp->sensor_subdevs[s_asd->port] = sd; > + return 0; > +} > + > +/* The .unbind callback */ > +static void atomisp_notifier_unbind(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *sd, > + struct v4l2_async_subdev *asd) > +{ > + struct atomisp_device *isp = notifier_to_atomisp(notifier); > + struct sensor_async_subdev *s_asd = to_sensor_asd(asd); > + > + isp->sensor_subdevs[s_asd->port] = NULL; > +} > + > +/* .complete() is called after all subdevices have been located */ > +static int atomisp_notifier_complete(struct v4l2_async_notifier *notifier) > +{ > + struct atomisp_device *isp = notifier_to_atomisp(notifier); > + > + return atomisp_register_device_nodes(isp); > +} > + > +static const struct v4l2_async_notifier_operations atomisp_async_ops = { > + .bound = atomisp_notifier_bound, > + .unbind = atomisp_notifier_unbind, > + .complete = atomisp_notifier_complete, > +}; > + > +int atomisp_csi2_bridge_parse_firmware(struct atomisp_device *isp) > +{ > + int i, mipi_port, ret; > + > + v4l2_async_nf_init(&isp->notifier); > + isp->notifier.ops = &atomisp_async_ops; > + > + for (i = 0; i < ATOMISP_CAMERA_NR_PORTS; i++) { > + struct v4l2_fwnode_endpoint vep = { > + .bus_type = V4L2_MBUS_CSI2_DPHY > + }; > + struct sensor_async_subdev *s_asd; > + struct fwnode_handle *ep; > + > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), i, 0, > + FWNODE_GRAPH_ENDPOINT_NEXT); > + if (!ep) > + continue; > + > + ret = v4l2_fwnode_endpoint_parse(ep, &vep); > + if (ret) > + goto err_parse; > + > + if (vep.base.port >= ATOMISP_CAMERA_NR_PORTS) { > + dev_err(isp->dev, "port %d not supported\n", vep.base.port); > + ret = -EINVAL; > + goto err_parse; > + } > + > + mipi_port = atomisp_port_to_mipi_port(isp, vep.base.port); > + isp->sensor_lanes[mipi_port] = vep.bus.mipi_csi2.num_data_lanes; > + > + s_asd = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep, > + struct sensor_async_subdev); > + if (IS_ERR(s_asd)) { > + ret = PTR_ERR(s_asd); > + goto err_parse; > + } > + > + s_asd->port = vep.base.port; > + > + fwnode_handle_put(ep); > + continue; > + > +err_parse: > + fwnode_handle_put(ep); > + return ret; > + } > + > + return 0; > +} > diff --git a/drivers/staging/media/atomisp/pci/atomisp_internal.h b/drivers/staging/media/atomisp/pci/atomisp_internal.h > index 514c360d4d03..e59c0f1e7f53 100644 > --- a/drivers/staging/media/atomisp/pci/atomisp_internal.h > +++ b/drivers/staging/media/atomisp/pci/atomisp_internal.h > @@ -27,6 +27,7 @@ > #include <linux/idr.h> > > #include <media/media-device.h> > +#include <media/v4l2-async.h> > #include <media/v4l2-subdev.h> > > /* ISP2400*/ > @@ -173,6 +174,7 @@ struct atomisp_device { > struct v4l2_device v4l2_dev; > struct media_device media_dev; > struct atomisp_sub_device asd; > + struct v4l2_async_notifier notifier; > struct atomisp_platform_data *pdata; > void *mmu_l1_base; > void __iomem *base; > diff --git a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c > index ef04effc66bf..802c16697ca6 100644 > --- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c > +++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c > @@ -27,6 +27,7 @@ > #include <linux/dmi.h> > #include <linux/interrupt.h> > #include <linux/bits.h> > +#include <media/v4l2-fwnode.h> > > #include <asm/iosf_mbi.h> > > @@ -782,7 +783,11 @@ static int atomisp_subdev_probe(struct atomisp_device *isp) > { > const struct atomisp_platform_data *pdata; > struct intel_v4l2_subdev_table *subdevs; > - int ret, mipi_port, count; > + int ret, mipi_port; > + > + ret = atomisp_csi2_bridge_parse_firmware(isp); > + if (ret) > + return ret; > > pdata = atomisp_get_platform_data(); > if (!pdata) { > @@ -790,23 +795,12 @@ static int atomisp_subdev_probe(struct atomisp_device *isp) > return 0; > } > > - /* FIXME: should return -EPROBE_DEFER if not all subdevs were probed */ > - for (count = 0; count < SUBDEV_WAIT_TIMEOUT_MAX_COUNT; count++) { > - int camera_count = 0; > - > - for (subdevs = pdata->subdevs; subdevs->type; ++subdevs) { > - if (subdevs->type == RAW_CAMERA) > - camera_count++; > - } > - if (camera_count) > - break; > - msleep(SUBDEV_WAIT_TIMEOUT); > - } > - /* Wait more time to give more time for subdev init code to finish */ > - msleep(5 * SUBDEV_WAIT_TIMEOUT); > - > - /* FIXME: should, instead, use I2C probe */ > - > + /* > + * TODO: this is left here for now to allow testing atomisp-sensor > + * drivers which are still using the atomisp_gmin_platform infra before > + * converting them to standard v4l2 sensor drivers using runtime-pm + > + * ACPI for pm and v4l2_async_register_subdev_sensor() registration. > + */ > for (subdevs = pdata->subdevs; subdevs->type; ++subdevs) { > ret = v4l2_device_register_subdev(&isp->v4l2_dev, subdevs->subdev); > if (ret) > @@ -937,7 +931,7 @@ static int atomisp_register_entities(struct atomisp_device *isp) > return ret; > } > > -static int atomisp_register_device_nodes(struct atomisp_device *isp) > +int atomisp_register_device_nodes(struct atomisp_device *isp) > { > struct atomisp_input_subdev *input; > int i, err; > @@ -1429,9 +1423,11 @@ static int atomisp_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i > isp->firmware = NULL; > isp->css_env.isp_css_fw.data = NULL; > > - err = atomisp_register_device_nodes(isp); > - if (err) > + err = v4l2_async_nf_register(&isp->v4l2_dev, &isp->notifier); > + if (err) { > + dev_err(isp->dev, "failed to register async notifier : %d\n", err); > goto css_init_fail; > + } > > atomisp_drvfs_init(isp); > > diff --git a/drivers/staging/media/atomisp/pci/atomisp_v4l2.h b/drivers/staging/media/atomisp/pci/atomisp_v4l2.h > index c8ee3ad83320..fad9573374b3 100644 > --- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.h > +++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.h > @@ -30,5 +30,6 @@ int atomisp_video_init(struct atomisp_video_pipe *video); > void atomisp_video_unregister(struct atomisp_video_pipe *video); > const struct firmware *atomisp_load_firmware(struct atomisp_device *isp); > int atomisp_csi_lane_config(struct atomisp_device *isp); > +int atomisp_register_device_nodes(struct atomisp_device *isp); > > #endif /* __ATOMISP_V4L2_H__ */
On Thu, May 18, 2023 at 6:38 PM Hans de Goede <hdegoede@redhat.com> wrote: ... > +static const guid_t atomisp_dsm_guid = GUID_INIT(0xdc2f6c4f, 0x045b, 0x4f1d, > + 0x97, 0xb9, 0x88, 0x2a, > + 0x68, 0x60, 0xa4, 0xbe); Can we use the de facto pattern for this kind of assignments? ... guid_t foo = <TAB>GUID_INIT(...first 3 parameters... <TAB><TAB>[spaces if needed)...last 8 parameters...); ? Also would be nice to have a comment where the GUID is represented in text format so it can be easily googled/searched for in internet/documentation. ... > + for (i = 0; i < obj->package.count - 1; i += 2) { > + /* The package should only contain strings */ > + if (obj->package.elements[i].type != ACPI_TYPE_STRING || i + 0 ? > + obj->package.elements[i + 1].type != ACPI_TYPE_STRING) > + break; > + > + if (!strcmp(obj->package.elements[i].string.pointer, key)) { Ditto? > + val = kstrdup(obj->package.elements[i + 1].string.pointer, GFP_KERNEL); > + dev_info(&adev->dev, "Using DSM entry %s=%s\n", key, val); > + break; > + } I would even go for temporary for element pointer ... *elem0 = &[i + 0]; ... *elem1 = &[i + 1]; > + } ... > +use_default: out_use_default: ... > + status = acpi_evaluate_object(adev->handle, "_PR0", NULL, &buffer); acpi_evaluate_object_typed() > + if (!ACPI_SUCCESS(status)) > + return -ENOENT; ... > + if (!buffer.length || !package || package->type != ACPI_TYPE_PACKAGE) See above. > + goto fail; ... > + if (strlen(name) == 4 && !strncmp(name, "CLK", 3) && strlen() assumes that name is NUL-terminated, hence it can be simply replaced with name[5] == '\0' check which can go at the end of conditional, so that it's also implied in strncmp() for the start of the string, but why not using str_has_prefix()? > + name[3] >= '0' && name[3] <= '4') { It's also possible to have it done via kstrtou8() that does almost all checks along with conversion. You will only need to check for > 4. > + clock_num = name[3] - '0'; > + break; > + } > + } Altogether if (str_has_prefix()) { ret = kstrto...(&clock_num); if (ret) ... check for clock_num range if needed. } Yes it's longer in code. ... > +fail: err_free_pointer: (It will be also in align with the rest of the code AFAICS) > + ACPI_FREE(buffer.pointer); > + > + return clock_num; ... > + /* Intel DSM or DMI quirk overrides PR0 derived default */ > + port = gmin_cfg_get_int(adev, "CsiPort", port); > + > + return port; return gmin_...; ... > + if (dev->fwnode && dev->fwnode->secondary) Please, use dev_fwnode() instead of direct access to the fwnode in struct device. > + return 0; ... > + struct v4l2_fwnode_endpoint vep = { > + .bus_type = V4L2_MBUS_CSI2_DPHY I would add a trailing comma here. > + };
Hi Andy, As always thank you for the review. I know you spend a lot of time on reviews and it is much appreciated! I agree with all your comments and I'll address them as suggested for the next version. Regards, Hans On 5/19/23 14:45, Andy Shevchenko wrote: > On Thu, May 18, 2023 at 6:38 PM Hans de Goede <hdegoede@redhat.com> wrote: > > ... > >> +static const guid_t atomisp_dsm_guid = GUID_INIT(0xdc2f6c4f, 0x045b, 0x4f1d, >> + 0x97, 0xb9, 0x88, 0x2a, >> + 0x68, 0x60, 0xa4, 0xbe); > > Can we use the de facto pattern for this kind of assignments? > > ... guid_t foo = > <TAB>GUID_INIT(...first 3 parameters... > <TAB><TAB>[spaces if needed)...last 8 parameters...); > ? > > Also would be nice to have a comment where the GUID is represented in > text format so it can be easily googled/searched for in > internet/documentation. > > ... > >> + for (i = 0; i < obj->package.count - 1; i += 2) { >> + /* The package should only contain strings */ > >> + if (obj->package.elements[i].type != ACPI_TYPE_STRING || > > i + 0 ? > >> + obj->package.elements[i + 1].type != ACPI_TYPE_STRING) >> + break; >> + >> + if (!strcmp(obj->package.elements[i].string.pointer, key)) { > > Ditto? > >> + val = kstrdup(obj->package.elements[i + 1].string.pointer, GFP_KERNEL); >> + dev_info(&adev->dev, "Using DSM entry %s=%s\n", key, val); >> + break; >> + } > > I would even go for temporary for element pointer > > ... *elem0 = &[i + 0]; > ... *elem1 = &[i + 1]; > >> + } > > ... > >> +use_default: > > out_use_default: > > ... > >> + status = acpi_evaluate_object(adev->handle, "_PR0", NULL, &buffer); > > acpi_evaluate_object_typed() > >> + if (!ACPI_SUCCESS(status)) >> + return -ENOENT; > > ... > >> + if (!buffer.length || !package || package->type != ACPI_TYPE_PACKAGE) > > See above. > >> + goto fail; > > ... > >> + if (strlen(name) == 4 && !strncmp(name, "CLK", 3) && > > strlen() assumes that name is NUL-terminated, hence it can be simply > replaced with name[5] == '\0' check which can go at the end of > conditional, so that it's also implied in strncmp() for the start of > the string, but why not using str_has_prefix()? > >> + name[3] >= '0' && name[3] <= '4') { > > It's also possible to have it done via kstrtou8() that does almost all > checks along with conversion. You will only need to check for > 4. > >> + clock_num = name[3] - '0'; >> + break; >> + } >> + } > > Altogether > > if (str_has_prefix()) { > ret = kstrto...(&clock_num); > if (ret) > ... > check for clock_num range if needed. > } > > Yes it's longer in code. > > ... > >> +fail: > > err_free_pointer: > (It will be also in align with the rest of the code AFAICS) > >> + ACPI_FREE(buffer.pointer); >> + >> + return clock_num; > > ... > >> + /* Intel DSM or DMI quirk overrides PR0 derived default */ >> + port = gmin_cfg_get_int(adev, "CsiPort", port); >> + >> + return port; > > return gmin_...; > > ... > >> + if (dev->fwnode && dev->fwnode->secondary) > > Please, use dev_fwnode() instead of direct access to the fwnode in > struct device. > >> + return 0; > > ... > >> + struct v4l2_fwnode_endpoint vep = { >> + .bus_type = V4L2_MBUS_CSI2_DPHY > > I would add a trailing comma here. > >> + }; >
diff --git a/drivers/staging/media/atomisp/Makefile b/drivers/staging/media/atomisp/Makefile index 532e12ed72e6..38b370124109 100644 --- a/drivers/staging/media/atomisp/Makefile +++ b/drivers/staging/media/atomisp/Makefile @@ -16,6 +16,7 @@ atomisp-objs += \ pci/atomisp_cmd.o \ pci/atomisp_compat_css20.o \ pci/atomisp_csi2.o \ + pci/atomisp_csi2_bridge.o \ pci/atomisp_drvfs.o \ pci/atomisp_fops.o \ pci/atomisp_ioctl.o \ diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2.c b/drivers/staging/media/atomisp/pci/atomisp_csi2.c index 0045c4d3a7f6..abf55a86f795 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_csi2.c +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2.c @@ -371,6 +371,10 @@ int atomisp_mipi_csi2_init(struct atomisp_device *isp) unsigned int i; int ret; + ret = atomisp_csi2_bridge_init(isp); + if (ret < 0) + return ret; + for (i = 0; i < ATOMISP_CAMERA_NR_PORTS; i++) { csi2_port = &isp->csi2_port[i]; csi2_port->isp = isp; diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2.h b/drivers/staging/media/atomisp/pci/atomisp_csi2.h index b245b2f5ce99..c714202db52a 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_csi2.h +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2.h @@ -21,14 +21,76 @@ #include <media/v4l2-subdev.h> #include <media/v4l2-ctrls.h> +#include "../../include/linux/atomisp.h" + #define CSI2_PAD_SINK 0 #define CSI2_PAD_SOURCE 1 #define CSI2_PADS_NUM 2 +#define CSI2_MAX_LANES 4 + struct atomisp_device; struct v4l2_device; struct atomisp_sub_device; +enum atomisp_csi2_sensor_swnodes { + SWNODE_SENSOR, + SWNODE_SENSOR_PORT, + SWNODE_SENSOR_ENDPOINT, + SWNODE_CSI2_PORT, + SWNODE_CSI2_ENDPOINT, + SWNODE_COUNT +}; + +struct atomisp_csi2_property_names { + char rotation[9]; + char bus_type[9]; + char data_lanes[11]; + char remote_endpoint[16]; +}; + +struct atomisp_csi2_node_names { + char port[7]; + char endpoint[11]; + char remote_port[7]; +}; + +struct atomisp_csi2_sensor_config { + const char *hid; + int lanes; +}; + +struct atomisp_csi2_sensor { + /* Append port in "-%u" format as suffix of HID */ + char name[ACPI_ID_LEN + 4]; + struct acpi_device *adev; + int port; + int lanes; + + /* SWNODE_COUNT + 1 for terminating NULL */ + const struct software_node *group[SWNODE_COUNT + 1]; + struct software_node swnodes[SWNODE_COUNT]; + struct atomisp_csi2_node_names node_names; + struct atomisp_csi2_property_names prop_names; + /* "rotation" + terminating entry */ + struct property_entry dev_properties[2]; + /* "bus-type", "data-lanes", "remote-endpoint" + terminating entry */ + struct property_entry ep_properties[4]; + /* "data-lanes", "remote-endpoint" + terminating entry */ + struct property_entry csi2_properties[3]; + struct software_node_ref_args local_ref[1]; + struct software_node_ref_args remote_ref[1]; + struct software_node_ref_args vcm_ref[1]; +}; + +struct atomisp_csi2_bridge { + char csi2_node_name[14]; + struct software_node csi2_node; + u32 data_lanes[CSI2_MAX_LANES]; + unsigned int n_sensors; + struct atomisp_csi2_sensor sensors[ATOMISP_CAMERA_NR_PORTS]; +}; + struct atomisp_mipi_csi2_device { struct v4l2_subdev subdev; struct media_pad pads[CSI2_PADS_NUM]; @@ -48,6 +110,8 @@ void atomisp_mipi_csi2_unregister_entities( struct atomisp_mipi_csi2_device *csi2); int atomisp_mipi_csi2_register_entities(struct atomisp_mipi_csi2_device *csi2, struct v4l2_device *vdev); +int atomisp_csi2_bridge_init(struct atomisp_device *isp); +int atomisp_csi2_bridge_parse_firmware(struct atomisp_device *isp); void atomisp_csi2_configure(struct atomisp_sub_device *asd); diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c b/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c new file mode 100644 index 000000000000..8539d7f354ae --- /dev/null +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Code to build software firmware node graph for atomisp2 connected sensors + * from ACPI tables. + * + * Copyright (C) 2023 Hans de Goede <hdegoede@redhat.com> + * + * Based on drivers/media/pci/intel/ipu3/cio2-bridge.c written by: + * Dan Scally <djrscally@gmail.com> + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/property.h> +#include <media/v4l2-fwnode.h> + +#include "atomisp_cmd.h" +#include "atomisp_csi2.h" +#include "atomisp_internal.h" + +#define NODE_SENSOR(_HID, _PROPS) \ + ((const struct software_node) { \ + .name = _HID, \ + .properties = _PROPS, \ + }) + +#define NODE_PORT(_PORT, _SENSOR_NODE) \ + ((const struct software_node) { \ + .name = _PORT, \ + .parent = _SENSOR_NODE, \ + }) + +#define NODE_ENDPOINT(_EP, _PORT, _PROPS) \ + ((const struct software_node) { \ + .name = _EP, \ + .parent = _PORT, \ + .properties = _PROPS, \ + }) + +/* + * Extend this array with ACPI Hardware IDs of sensors known to be working + * plus the number of links expected by their drivers. + * + * Do not add an entry for a sensor that is not actually supported, + * or which have not yet been converted to work without atomisp_gmin + * power-management and with v4l2-async probing. + */ +static const struct atomisp_csi2_sensor_config supported_sensors[] = { + /* GalaxyCore GC0310 */ + { "INT0310", 1 }, + /* Omnivision OV2680 */ + { "OVTI2680", 1 }, +}; + +/* + * gmin_cfg parsing code. This is a cleaned up version of the gmin_cfg parsing + * code from atomisp_gmin_platform.c. + * Once all sensors are moved to v4l2-async probing atomisp_gmin_platform.c can + * be removed and the duplication of this code goes away. + */ +struct gmin_cfg_var { + const char *acpi_dev_name; + const char *key; + const char *val; +}; + +static struct gmin_cfg_var lenovo_ideapad_miix_310_vars[] = { + /* _DSM contains the wrong CsiPort! */ + { "OVTI2680:01", "CsiPort", "0" }, + {} +}; + +static const struct dmi_system_id gmin_cfg_dmi_overrides[] = { + { + /* Lenovo Ideapad Miix 310 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "MIIX 310-10"), + }, + .driver_data = lenovo_ideapad_miix_310_vars, + }, + {} +}; + +static const guid_t atomisp_dsm_guid = GUID_INIT(0xdc2f6c4f, 0x045b, 0x4f1d, + 0x97, 0xb9, 0x88, 0x2a, + 0x68, 0x60, 0xa4, 0xbe); + +static char *gmin_cfg_get_dsm(struct acpi_device *adev, const char *key) +{ + union acpi_object *obj; + char *val = NULL; + int i; + + obj = acpi_evaluate_dsm_typed(adev->handle, &atomisp_dsm_guid, 0, 0, + NULL, ACPI_TYPE_PACKAGE); + if (!obj) + return NULL; + + for (i = 0; i < obj->package.count - 1; i += 2) { + /* The package should only contain strings */ + if (obj->package.elements[i].type != ACPI_TYPE_STRING || + obj->package.elements[i + 1].type != ACPI_TYPE_STRING) + break; + + if (!strcmp(obj->package.elements[i].string.pointer, key)) { + val = kstrdup(obj->package.elements[i + 1].string.pointer, GFP_KERNEL); + dev_info(&adev->dev, "Using DSM entry %s=%s\n", key, val); + break; + } + } + + ACPI_FREE(obj); + return val; +} + +static char *gmin_cfg_get_dmi_override(struct acpi_device *adev, const char *key) +{ + const struct dmi_system_id *id; + struct gmin_cfg_var *gv; + + id = dmi_first_match(gmin_cfg_dmi_overrides); + if (!id) + return NULL; + + for (gv = id->driver_data; gv->acpi_dev_name; gv++) { + if (strcmp(gv->acpi_dev_name, acpi_dev_name(adev))) + continue; + + if (strcmp(key, gv->key)) + continue; + + dev_info(&adev->dev, "Using DMI entry %s=%s\n", key, gv->val); + return kstrdup(gv->val, GFP_KERNEL); + } + + return NULL; +} + +static char *gmin_cfg_get(struct acpi_device *adev, const char *key) +{ + char *val; + + val = gmin_cfg_get_dmi_override(adev, key); + if (val) + return val; + + return gmin_cfg_get_dsm(adev, key); +} + +static int gmin_cfg_get_int(struct acpi_device *adev, const char *key, int default_val) +{ + char *str_val; + long int_val; + int ret; + + str_val = gmin_cfg_get(adev, key); + if (!str_val) + goto use_default; + + ret = kstrtoul(str_val, 0, &int_val); + kfree(str_val); + if (ret) + goto use_default; + + return int_val; + +use_default: + dev_info(&adev->dev, "Using default %s=%d\n", key, default_val); + return default_val; +} + +static int atomisp_csi2_get_pmc_clk_nr_from_acpi_pr0(struct acpi_device *adev) +{ + char name[5]; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer b_name = { sizeof(name), name }; + union acpi_object *package, *element; + int clock_num = -ENOENT; + acpi_handle rhandle; + acpi_status status; + int i; + + status = acpi_evaluate_object(adev->handle, "_PR0", NULL, &buffer); + if (!ACPI_SUCCESS(status)) + return -ENOENT; + + package = buffer.pointer; + + if (!buffer.length || !package || package->type != ACPI_TYPE_PACKAGE) + goto fail; + + for (i = 0; i < package->package.count; i++) { + element = &package->package.elements[i]; + + if (element->type != ACPI_TYPE_LOCAL_REFERENCE) + continue; + + rhandle = element->reference.handle; + if (!rhandle) + continue; + + acpi_get_name(rhandle, ACPI_SINGLE_NAME, &b_name); + + if (strlen(name) == 4 && !strncmp(name, "CLK", 3) && + name[3] >= '0' && name[3] <= '4') { + clock_num = name[3] - '0'; + break; + } + } +fail: + ACPI_FREE(buffer.pointer); + + return clock_num; +} + +static int atomisp_csi2_get_port(struct acpi_device *adev) +{ + int clock_num, port; + + /* + * Get pmc-clock number from ACPI _PR0 method and compare this to + * the CsiPort 1 pmc-clock used in the CHT/BYT reference designs. + */ + clock_num = atomisp_csi2_get_pmc_clk_nr_from_acpi_pr0(adev); + if (IS_ISP2401) + port = clock_num == 4 ? 1 : 0; + else + port = clock_num == 0 ? 1 : 0; + + /* Intel DSM or DMI quirk overrides PR0 derived default */ + port = gmin_cfg_get_int(adev, "CsiPort", port); + + return port; +} + +static const struct atomisp_csi2_property_names prop_names = { + .rotation = "rotation", + .bus_type = "bus-type", + .data_lanes = "data-lanes", + .remote_endpoint = "remote-endpoint", +}; + +static void atomisp_csi2_create_fwnode_properties(struct atomisp_csi2_sensor *sensor, + struct atomisp_csi2_bridge *bridge, + const struct atomisp_csi2_sensor_config *cfg) +{ + sensor->prop_names = prop_names; + + sensor->local_ref[0] = SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_CSI2_ENDPOINT]); + sensor->remote_ref[0] = SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_SENSOR_ENDPOINT]); + + sensor->dev_properties[0] = PROPERTY_ENTRY_U32(sensor->prop_names.rotation, 0); + + sensor->ep_properties[0] = PROPERTY_ENTRY_U32(sensor->prop_names.bus_type, + V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); + sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN(sensor->prop_names.data_lanes, + bridge->data_lanes, + sensor->lanes); + sensor->ep_properties[2] = PROPERTY_ENTRY_REF_ARRAY(sensor->prop_names.remote_endpoint, + sensor->local_ref); + + sensor->csi2_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN(sensor->prop_names.data_lanes, + bridge->data_lanes, + sensor->lanes); + sensor->csi2_properties[1] = PROPERTY_ENTRY_REF_ARRAY(sensor->prop_names.remote_endpoint, + sensor->remote_ref); +} + +static void atomisp_csi2_init_swnode_names(struct atomisp_csi2_sensor *sensor) +{ + snprintf(sensor->node_names.remote_port, + sizeof(sensor->node_names.remote_port), + SWNODE_GRAPH_PORT_NAME_FMT, sensor->port); + snprintf(sensor->node_names.port, + sizeof(sensor->node_names.port), + SWNODE_GRAPH_PORT_NAME_FMT, 0); /* Always port 0 */ + snprintf(sensor->node_names.endpoint, + sizeof(sensor->node_names.endpoint), + SWNODE_GRAPH_ENDPOINT_NAME_FMT, 0); /* And endpoint 0 */ +} + +static void atomisp_csi2_init_swnode_group(struct atomisp_csi2_sensor *sensor) +{ + struct software_node *nodes = sensor->swnodes; + + sensor->group[SWNODE_SENSOR] = &nodes[SWNODE_SENSOR]; + sensor->group[SWNODE_SENSOR_PORT] = &nodes[SWNODE_SENSOR_PORT]; + sensor->group[SWNODE_SENSOR_ENDPOINT] = &nodes[SWNODE_SENSOR_ENDPOINT]; + sensor->group[SWNODE_CSI2_PORT] = &nodes[SWNODE_CSI2_PORT]; + sensor->group[SWNODE_CSI2_ENDPOINT] = &nodes[SWNODE_CSI2_ENDPOINT]; +} + +static void atomisp_csi2_create_connection_swnodes(struct atomisp_csi2_bridge *bridge, + struct atomisp_csi2_sensor *sensor) +{ + struct software_node *nodes = sensor->swnodes; + + atomisp_csi2_init_swnode_names(sensor); + + nodes[SWNODE_SENSOR] = NODE_SENSOR(sensor->name, + sensor->dev_properties); + nodes[SWNODE_SENSOR_PORT] = NODE_PORT(sensor->node_names.port, + &nodes[SWNODE_SENSOR]); + nodes[SWNODE_SENSOR_ENDPOINT] = NODE_ENDPOINT(sensor->node_names.endpoint, + &nodes[SWNODE_SENSOR_PORT], + sensor->ep_properties); + nodes[SWNODE_CSI2_PORT] = NODE_PORT(sensor->node_names.remote_port, + &bridge->csi2_node); + nodes[SWNODE_CSI2_ENDPOINT] = NODE_ENDPOINT(sensor->node_names.endpoint, + &nodes[SWNODE_CSI2_PORT], + sensor->csi2_properties); + + atomisp_csi2_init_swnode_group(sensor); +} + +static void atomisp_csi2_unregister_sensors(struct atomisp_csi2_bridge *bridge) +{ + struct atomisp_csi2_sensor *sensor; + unsigned int i; + + for (i = 0; i < bridge->n_sensors; i++) { + sensor = &bridge->sensors[i]; + software_node_unregister_node_group(sensor->group); + acpi_dev_put(sensor->adev); + } +} + +static int atomisp_csi2_connect_sensor(const struct atomisp_csi2_sensor_config *cfg, + struct atomisp_csi2_bridge *bridge, + struct atomisp_device *isp) +{ + struct fwnode_handle *fwnode, *primary; + struct atomisp_csi2_sensor *sensor; + struct acpi_device *adev; + int ret; + + for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { + if (!adev->status.enabled) + continue; + + if (bridge->n_sensors >= ATOMISP_CAMERA_NR_PORTS) { + dev_err(isp->dev, "Exceeded available CSI2 ports\n"); + ret = -EINVAL; + goto err_put_adev; + } + + sensor = &bridge->sensors[bridge->n_sensors]; + + sensor->port = atomisp_csi2_get_port(adev); + if (sensor->port >= ATOMISP_CAMERA_NR_PORTS) { + dev_err(&adev->dev, "Invalid port: %d\n", sensor->port); + ret = -EINVAL; + goto err_put_adev; + } + + sensor->lanes = gmin_cfg_get_int(adev, "CsiLanes", cfg->lanes); + if (sensor->lanes > CSI2_MAX_LANES) { + dev_err(&adev->dev, "Invalid number of lanes: %d\n", sensor->lanes); + ret = -EINVAL; + goto err_put_adev; + } + + snprintf(sensor->name, sizeof(sensor->name), "%s-%u", + cfg->hid, sensor->port); + + atomisp_csi2_create_fwnode_properties(sensor, bridge, cfg); + atomisp_csi2_create_connection_swnodes(bridge, sensor); + + ret = software_node_register_node_group(sensor->group); + if (ret) + goto err_put_adev; + + fwnode = software_node_fwnode(&sensor->swnodes[SWNODE_SENSOR]); + if (!fwnode) { + ret = -ENODEV; + goto err_free_swnodes; + } + + sensor->adev = acpi_dev_get(adev); + + primary = acpi_fwnode_handle(adev); + primary->secondary = fwnode; + + dev_info(isp->dev, "Found supported sensor %s\n", acpi_dev_name(adev)); + + bridge->n_sensors++; + } + + return 0; + +err_free_swnodes: + software_node_unregister_node_group(sensor->group); +err_put_adev: + acpi_dev_put(adev); + return ret; +} + +static int atomisp_csi2_connect_sensors(struct atomisp_csi2_bridge *bridge, + struct atomisp_device *isp) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(supported_sensors); i++) { + const struct atomisp_csi2_sensor_config *cfg = &supported_sensors[i]; + + ret = atomisp_csi2_connect_sensor(cfg, bridge, isp); + if (ret) + goto err_unregister_sensors; + } + + return 0; + +err_unregister_sensors: + atomisp_csi2_unregister_sensors(bridge); + return ret; +} + +int atomisp_csi2_bridge_init(struct atomisp_device *isp) +{ + struct atomisp_csi2_bridge *bridge; + struct device *dev = isp->dev; + struct fwnode_handle *fwnode; + int i, ret; + + /* + * This function is intended to run only once and then leave + * the created nodes attached even after a rmmod, therefor: + * 1. The bridge memory is leaked deliberately on success + * 2. If a secondary fwnode is already set exit early. + */ + if (dev->fwnode && dev->fwnode->secondary) + return 0; + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) + return -ENOMEM; + + strscpy(bridge->csi2_node_name, "atomisp-csi2", sizeof(bridge->csi2_node_name)); + bridge->csi2_node.name = bridge->csi2_node_name; + + ret = software_node_register(&bridge->csi2_node); + if (ret < 0) { + dev_err(dev, "Failed to register the CSI2 HID node\n"); + goto err_free_bridge; + } + + /* + * Map the lane arrangement, which is fixed for the ISP2 (meaning we + * only need one, rather than one per sensor). We include it as a + * member of the bridge struct rather than a global variable so + * that it survives if the module is unloaded along with the rest of + * the struct. + */ + for (i = 0; i < CSI2_MAX_LANES; i++) + bridge->data_lanes[i] = i + 1; + + ret = atomisp_csi2_connect_sensors(bridge, isp); + if (ret || bridge->n_sensors == 0) + goto err_unregister_csi2; + + fwnode = software_node_fwnode(&bridge->csi2_node); + if (!fwnode) { + dev_err(dev, "Error getting fwnode from csi2 software_node\n"); + ret = -ENODEV; + goto err_unregister_sensors; + } + + set_secondary_fwnode(dev, fwnode); + + return 0; + +err_unregister_sensors: + atomisp_csi2_unregister_sensors(bridge); +err_unregister_csi2: + software_node_unregister(&bridge->csi2_node); +err_free_bridge: + kfree(bridge); + + return ret; +} + +/******* V4L2 sub-device asynchronous registration callbacks***********/ + +struct sensor_async_subdev { + struct v4l2_async_subdev asd; + int port; +}; + +#define to_sensor_asd(a) container_of(a, struct sensor_async_subdev, asd) +#define notifier_to_atomisp(n) container_of(n, struct atomisp_device, notifier) + +/* .bound() notifier callback when a match is found */ +static int atomisp_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct atomisp_device *isp = notifier_to_atomisp(notifier); + struct sensor_async_subdev *s_asd = to_sensor_asd(asd); + + if (s_asd->port >= ATOMISP_CAMERA_NR_PORTS) { + dev_err(isp->dev, "port %d not supported\n", s_asd->port); + return -EINVAL; + } + + if (isp->sensor_subdevs[s_asd->port]) { + dev_err(isp->dev, "port %d already has a sensor attached\n", s_asd->port); + return -EBUSY; + } + + isp->sensor_subdevs[s_asd->port] = sd; + return 0; +} + +/* The .unbind callback */ +static void atomisp_notifier_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct atomisp_device *isp = notifier_to_atomisp(notifier); + struct sensor_async_subdev *s_asd = to_sensor_asd(asd); + + isp->sensor_subdevs[s_asd->port] = NULL; +} + +/* .complete() is called after all subdevices have been located */ +static int atomisp_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct atomisp_device *isp = notifier_to_atomisp(notifier); + + return atomisp_register_device_nodes(isp); +} + +static const struct v4l2_async_notifier_operations atomisp_async_ops = { + .bound = atomisp_notifier_bound, + .unbind = atomisp_notifier_unbind, + .complete = atomisp_notifier_complete, +}; + +int atomisp_csi2_bridge_parse_firmware(struct atomisp_device *isp) +{ + int i, mipi_port, ret; + + v4l2_async_nf_init(&isp->notifier); + isp->notifier.ops = &atomisp_async_ops; + + for (i = 0; i < ATOMISP_CAMERA_NR_PORTS; i++) { + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct sensor_async_subdev *s_asd; + struct fwnode_handle *ep; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), i, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + continue; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_parse; + + if (vep.base.port >= ATOMISP_CAMERA_NR_PORTS) { + dev_err(isp->dev, "port %d not supported\n", vep.base.port); + ret = -EINVAL; + goto err_parse; + } + + mipi_port = atomisp_port_to_mipi_port(isp, vep.base.port); + isp->sensor_lanes[mipi_port] = vep.bus.mipi_csi2.num_data_lanes; + + s_asd = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep, + struct sensor_async_subdev); + if (IS_ERR(s_asd)) { + ret = PTR_ERR(s_asd); + goto err_parse; + } + + s_asd->port = vep.base.port; + + fwnode_handle_put(ep); + continue; + +err_parse: + fwnode_handle_put(ep); + return ret; + } + + return 0; +} diff --git a/drivers/staging/media/atomisp/pci/atomisp_internal.h b/drivers/staging/media/atomisp/pci/atomisp_internal.h index 514c360d4d03..e59c0f1e7f53 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_internal.h +++ b/drivers/staging/media/atomisp/pci/atomisp_internal.h @@ -27,6 +27,7 @@ #include <linux/idr.h> #include <media/media-device.h> +#include <media/v4l2-async.h> #include <media/v4l2-subdev.h> /* ISP2400*/ @@ -173,6 +174,7 @@ struct atomisp_device { struct v4l2_device v4l2_dev; struct media_device media_dev; struct atomisp_sub_device asd; + struct v4l2_async_notifier notifier; struct atomisp_platform_data *pdata; void *mmu_l1_base; void __iomem *base; diff --git a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c index ef04effc66bf..802c16697ca6 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c +++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c @@ -27,6 +27,7 @@ #include <linux/dmi.h> #include <linux/interrupt.h> #include <linux/bits.h> +#include <media/v4l2-fwnode.h> #include <asm/iosf_mbi.h> @@ -782,7 +783,11 @@ static int atomisp_subdev_probe(struct atomisp_device *isp) { const struct atomisp_platform_data *pdata; struct intel_v4l2_subdev_table *subdevs; - int ret, mipi_port, count; + int ret, mipi_port; + + ret = atomisp_csi2_bridge_parse_firmware(isp); + if (ret) + return ret; pdata = atomisp_get_platform_data(); if (!pdata) { @@ -790,23 +795,12 @@ static int atomisp_subdev_probe(struct atomisp_device *isp) return 0; } - /* FIXME: should return -EPROBE_DEFER if not all subdevs were probed */ - for (count = 0; count < SUBDEV_WAIT_TIMEOUT_MAX_COUNT; count++) { - int camera_count = 0; - - for (subdevs = pdata->subdevs; subdevs->type; ++subdevs) { - if (subdevs->type == RAW_CAMERA) - camera_count++; - } - if (camera_count) - break; - msleep(SUBDEV_WAIT_TIMEOUT); - } - /* Wait more time to give more time for subdev init code to finish */ - msleep(5 * SUBDEV_WAIT_TIMEOUT); - - /* FIXME: should, instead, use I2C probe */ - + /* + * TODO: this is left here for now to allow testing atomisp-sensor + * drivers which are still using the atomisp_gmin_platform infra before + * converting them to standard v4l2 sensor drivers using runtime-pm + + * ACPI for pm and v4l2_async_register_subdev_sensor() registration. + */ for (subdevs = pdata->subdevs; subdevs->type; ++subdevs) { ret = v4l2_device_register_subdev(&isp->v4l2_dev, subdevs->subdev); if (ret) @@ -937,7 +931,7 @@ static int atomisp_register_entities(struct atomisp_device *isp) return ret; } -static int atomisp_register_device_nodes(struct atomisp_device *isp) +int atomisp_register_device_nodes(struct atomisp_device *isp) { struct atomisp_input_subdev *input; int i, err; @@ -1429,9 +1423,11 @@ static int atomisp_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i isp->firmware = NULL; isp->css_env.isp_css_fw.data = NULL; - err = atomisp_register_device_nodes(isp); - if (err) + err = v4l2_async_nf_register(&isp->v4l2_dev, &isp->notifier); + if (err) { + dev_err(isp->dev, "failed to register async notifier : %d\n", err); goto css_init_fail; + } atomisp_drvfs_init(isp); diff --git a/drivers/staging/media/atomisp/pci/atomisp_v4l2.h b/drivers/staging/media/atomisp/pci/atomisp_v4l2.h index c8ee3ad83320..fad9573374b3 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.h +++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.h @@ -30,5 +30,6 @@ int atomisp_video_init(struct atomisp_video_pipe *video); void atomisp_video_unregister(struct atomisp_video_pipe *video); const struct firmware *atomisp_load_firmware(struct atomisp_device *isp); int atomisp_csi_lane_config(struct atomisp_device *isp); +int atomisp_register_device_nodes(struct atomisp_device *isp); #endif /* __ATOMISP_V4L2_H__ */
WIP Signed-off-by: Hans de Goede <hdegoede@redhat.com> --- drivers/staging/media/atomisp/Makefile | 1 + .../staging/media/atomisp/pci/atomisp_csi2.c | 4 + .../staging/media/atomisp/pci/atomisp_csi2.h | 64 ++ .../media/atomisp/pci/atomisp_csi2_bridge.c | 592 ++++++++++++++++++ .../media/atomisp/pci/atomisp_internal.h | 2 + .../staging/media/atomisp/pci/atomisp_v4l2.c | 38 +- .../staging/media/atomisp/pci/atomisp_v4l2.h | 1 + 7 files changed, 681 insertions(+), 21 deletions(-) create mode 100644 drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c