diff options
Diffstat (limited to 'drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c')
-rw-r--r-- | drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c | 874 |
1 files changed, 874 insertions, 0 deletions
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..0d12ba78d9c1 --- /dev/null +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c @@ -0,0 +1,874 @@ +// 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/clk.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, \ + }) + +#define PMC_CLK_RATE_19_2MHZ 19200000 + +/* + * 79234640-9e10-4fea-a5c1-b5aa8b19756f + * This _DSM GUID returns information about the GPIO lines mapped to a sensor. + * Function number 1 returns a count of the GPIO lines that are mapped. + * Subsequent functions return 32 bit ints encoding information about the GPIO. + */ +static const guid_t intel_sensor_gpio_info_guid = + GUID_INIT(0x79234640, 0x9e10, 0x4fea, + 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); + +#define INTEL_GPIO_DSM_TYPE_SHIFT 0 +#define INTEL_GPIO_DSM_TYPE_MASK GENMASK(7, 0) +#define INTEL_GPIO_DSM_PIN_SHIFT 8 +#define INTEL_GPIO_DSM_PIN_MASK GENMASK(15, 8) +#define INTEL_GPIO_DSM_SENSOR_ON_VAL_SHIFT 24 +#define INTEL_GPIO_DSM_SENSOR_ON_VAL_MASK GENMASK(31, 24) + +#define INTEL_GPIO_DSM_TYPE(x) \ + (((x) & INTEL_GPIO_DSM_TYPE_MASK) >> INTEL_GPIO_DSM_TYPE_SHIFT) +#define INTEL_GPIO_DSM_PIN(x) \ + (((x) & INTEL_GPIO_DSM_PIN_MASK) >> INTEL_GPIO_DSM_PIN_SHIFT) +#define INTEL_GPIO_DSM_SENSOR_ON_VAL(x) \ + (((x) & INTEL_GPIO_DSM_SENSOR_ON_VAL_MASK) >> INTEL_GPIO_DSM_SENSOR_ON_VAL_SHIFT) + +/* + * 822ace8f-2814-4174-a56b-5f029fe079ee + * This _DSM GUID returns a string from the sensor device, which acts as a + * module identifier. + */ +static const guid_t intel_sensor_module_guid = + GUID_INIT(0x822ace8f, 0x2814, 0x4174, + 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); + +/* + * dc2f6c4f-045b-4f1d-97b9-882a6860a4be + * This _DSM GUID returns a package with n*2 strings, with each set of 2 strings + * forming a key, value pair for settings like e.g. "CsiLanes" = "1". + */ +static const guid_t atomisp_dsm_guid = + GUID_INIT(0xdc2f6c4f, 0x045b, 0x4f1d, + 0x97, 0xb9, 0x88, 0x2a, 0x68, 0x60, 0xa4, 0xbe); + +/* + * Extend this array with ACPI Hardware IDs of sensors known to be working + * plus the default number of links + link-frequencies. + * + * 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 char *gmin_cfg_get_dsm(struct acpi_device *adev, const char *key) +{ + union acpi_object *obj, *key_el, *val_el; + 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) { + key_el = &obj->package.elements[i + 0]; + val_el = &obj->package.elements[i + 1]; + + if (key_el->type != ACPI_TYPE_STRING || val_el->type != ACPI_TYPE_STRING) + break; + + if (!strcmp(key_el->string.pointer, key)) { + val = kstrdup(val_el->string.pointer, GFP_KERNEL); + if (!val) + break; + + acpi_handle_info(adev->handle, "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; + + acpi_handle_info(adev->handle, "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 out_use_default; + + ret = kstrtoul(str_val, 0, &int_val); + kfree(str_val); + if (ret) + goto out_use_default; + + return int_val; + +out_use_default: + acpi_handle_info(adev->handle, "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) +{ + /* ACPI_PATH_SEGMENT_LENGTH is guaranteed to be big enough for name + 0 term. */ + char name[ACPI_PATH_SEGMENT_LENGTH]; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer b_name = { sizeof(name), name }; + union acpi_object *package, *element; + int i, ret = -ENOENT; + acpi_handle rhandle; + acpi_status status; + u8 clock_num; + + status = acpi_evaluate_object_typed(adev->handle, "_PR0", NULL, &buffer, ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) + return -ENOENT; + + package = buffer.pointer; + 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 (str_has_prefix(name, "CLK") && !kstrtou8(&name[3], 10, &clock_num) && + clock_num <= 4) { + ret = clock_num; + break; + } + } + + ACPI_FREE(buffer.pointer); + + if (ret < 0) + acpi_handle_warn(adev->handle, "Could not find PMC clk in _PR0\n"); + + return ret; +} + +static int atomisp_csi2_set_pmc_clk_freq(struct acpi_device *adev, int clock_num) +{ + struct clk *clk; + char name[14]; + int ret; + + if (clock_num < 0) + return 0; + + snprintf(name, sizeof(name), "pmc_plt_clk_%d", clock_num); + + clk = clk_get(NULL, name); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + acpi_handle_err(adev->handle, "Error getting clk %s:%d\n", name, ret); + return ret; + } + + /* + * The firmware might enable the clock at boot, to change + * the rate we must ensure the clock is disabled. + */ + ret = clk_prepare_enable(clk); + if (!ret) + clk_disable_unprepare(clk); + if (!ret) + ret = clk_set_rate(clk, PMC_CLK_RATE_19_2MHZ); + if (ret) + acpi_handle_err(adev->handle, "Error setting clk-rate for %s:%d\n", name, ret); + + clk_put(clk); + return ret; +} + +static int atomisp_csi2_get_port(struct acpi_device *adev, int clock_num) +{ + int port; + + /* + * Compare clock-number to the PMC-clock used for CsiPort 1 + * in the CHT/BYT reference designs. + */ + if (IS_ISP2401) + port = clock_num == 4 ? 1 : 0; + else + port = clock_num == 0 ? 1 : 0; + + /* Intel DSM or DMI quirk overrides _PR0 CLK derived default */ + return gmin_cfg_get_int(adev, "CsiPort", port); +} + +/* Note this always returns 1 to continue looping so that res_count is accurate */ +static int atomisp_csi2_handle_acpi_gpio_res(struct acpi_resource *ares, void *_data) +{ + struct atomisp_csi2_acpi_gpio_parsing_data *data = _data; + struct acpi_resource_gpio *agpio; + const char *name; + bool active_low; + unsigned int i; + u32 settings = 0; + u16 pin; + + if (!acpi_gpio_get_io_resource(ares, &agpio)) + return 1; /* Not a GPIO, continue the loop */ + + data->res_count++; + + pin = agpio->pin_table[0]; + for (i = 0; i < data->settings_count; i++) { + if (INTEL_GPIO_DSM_PIN(data->settings[i]) == pin) { + settings = data->settings[i]; + break; + } + } + + if (i == data->settings_count) { + acpi_handle_warn(data->adev->handle, + "Could not find DSM GPIO settings for pin %u\n", pin); + return 1; + } + + switch (INTEL_GPIO_DSM_TYPE(settings)) { + case 0: + name = "reset-gpios"; + break; + case 1: + name = "powerdown-gpios"; + break; + default: + acpi_handle_warn(data->adev->handle, "Unknown GPIO type 0x%02lx for pin %u\n", + INTEL_GPIO_DSM_TYPE(settings), pin); + return 1; + } + + /* + * Both reset and power-down need to be logical false when the sensor + * is on (sensor should not be in reset and not be powered-down). So + * when the sensor-on-value (which is the physical pin value) is high, + * then the signal is active-low. + */ + active_low = INTEL_GPIO_DSM_SENSOR_ON_VAL(settings); + + i = data->map_count; + if (i == CSI2_MAX_ACPI_GPIOS) + return 1; + + /* res_count is already incremented */ + data->map->params[i].crs_entry_index = data->res_count - 1; + data->map->params[i].active_low = active_low; + data->map->mapping[i].name = name; + data->map->mapping[i].data = &data->map->params[i]; + data->map->mapping[i].size = 1; + data->map_count++; + + acpi_handle_info(data->adev->handle, "%s crs %d %s pin %u active-%s\n", name, + data->res_count - 1, agpio->resource_source.string_ptr, + pin, active_low ? "low" : "high"); + + return 1; +} + +/* + * Helper function to create an ACPI GPIO lookup table for sensor reset and + * powerdown signals on Intel Bay Trail (BYT) and Cherry Trail (CHT) devices, + * including setting the correct polarity for the GPIO. + * + * This uses the "79234640-9e10-4fea-a5c1-b5aa8b19756f" DSM method directly + * on the sensor device's ACPI node. This is different from later Intel + * hardware which has a separate INT3472 acpi_device with this info. + * + * This function must be called before creating the sw-noded describing + * the fwnode graph endpoint. And sensor drivers used on these devices + * must return -EPROBE_DEFER when there is no endpoint description yet. + * Together this guarantees that the GPIO lookups are in place before + * the sensor driver tries to get GPIOs with gpiod_get(). + * + * Note this code uses the same DSM GUID as the int3472_gpio_guid in + * the INT3472 discrete.c code and there is some overlap, but there are + * enough differences that it is difficult to share the code. + */ +static int atomisp_csi2_add_gpio_mappings(struct atomisp_csi2_sensor *sensor, + struct acpi_device *adev) +{ + struct atomisp_csi2_acpi_gpio_parsing_data data = { }; + LIST_HEAD(resource_list); + union acpi_object *obj; + unsigned int i, j; + int ret; + + obj = acpi_evaluate_dsm_typed(adev->handle, &intel_sensor_module_guid, + 0x00, 1, NULL, ACPI_TYPE_STRING); + if (obj) { + acpi_handle_info(adev->handle, "Sensor module id: '%s'\n", obj->string.pointer); + ACPI_FREE(obj); + } + + /* + * First get the GPIO-settings count and then get count GPIO-settings + * values. Note the order of these may differ from the order in which + * the GPIOs are listed on the ACPI resources! So we first store them all + * and then enumerate the ACPI resources and match them up by pin number. + */ + obj = acpi_evaluate_dsm_typed(adev->handle, + &intel_sensor_gpio_info_guid, 0x00, 1, + NULL, ACPI_TYPE_INTEGER); + if (!obj) { + acpi_handle_err(adev->handle, "No _DSM entry for GPIO pin count\n"); + return -EIO; + } + + data.settings_count = obj->integer.value; + ACPI_FREE(obj); + + if (data.settings_count > CSI2_MAX_ACPI_GPIOS) { + acpi_handle_err(adev->handle, "Too many GPIOs %u > %u\n", data.settings_count, CSI2_MAX_ACPI_GPIOS); + return -EOVERFLOW; + } + + for (i = 0; i < data.settings_count; i++) { + /* + * i + 2 because the index of this _DSM function is 1-based + * and the first function is just a count. + */ + obj = acpi_evaluate_dsm_typed(adev->handle, + &intel_sensor_gpio_info_guid, + 0x00, i + 2, + NULL, ACPI_TYPE_INTEGER); + if (!obj) { + acpi_handle_err(adev->handle, "No _DSM entry for pin %u\n", i); + return -EIO; + } + + data.settings[i] = obj->integer.value; + ACPI_FREE(obj); + } + + /* Since we match up by pin-number the pin-numbers must be unique */ + for (i = 0; i < data.settings_count; i++) { + for (j = i + 1; j < data.settings_count; j++) { + if (INTEL_GPIO_DSM_PIN(data.settings[i]) != + INTEL_GPIO_DSM_PIN(data.settings[j])) + continue; + + acpi_handle_err(adev->handle, "Duplicate pin number %lu\n", + INTEL_GPIO_DSM_PIN(data.settings[i])); + return -EIO; + } + } + + /* Now parse the ACPI resources and build the lookup table */ + data.adev = adev; + data.map = &sensor->gpio_map; + ret = acpi_dev_get_resources(adev, &resource_list, + atomisp_csi2_handle_acpi_gpio_res, &data); + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&resource_list); + + if (data.map_count != data.settings_count || + data.res_count != data.settings_count) + acpi_handle_warn(adev->handle, "ACPI GPIO resources vs DSM GPIO-info count mismatch (dsm: %d res: %d map %d\n", + data.settings_count, data.res_count, data.map_count); + + ret = acpi_dev_add_driver_gpios(adev, data.map->mapping); + if (ret) + acpi_handle_err(adev->handle, "Error adding driver GPIOs: %d\n", ret); + + return ret; +} + +static const struct atomisp_csi2_property_names prop_names = { + .clock_frequency = "clock-frequency", + .rotation = "rotation", + .bus_type = "bus-type", + .data_lanes = "data-lanes", + .remote_endpoint = "remote-endpoint", + .link_frequencies = "link-frequencies", +}; + +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.clock_frequency, + PMC_CLK_RATE_19_2MHZ); + sensor->dev_properties[1] = 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); + if (cfg->nr_link_freqs > 0) + sensor->ep_properties[3] = + PROPERTY_ENTRY_U64_ARRAY_LEN(sensor->prop_names.link_frequencies, + cfg->link_freqs, cfg->nr_link_freqs); + + 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_remove_driver_gpios(sensor->adev); + 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, clock_num; + + 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 = -EOVERFLOW; + goto err_put_adev; + } + + sensor = &bridge->sensors[bridge->n_sensors]; + + /* + * ACPI takes care of turning the PMC clock on and off, but on BYT + * the clock defaults to 25 MHz instead of the expected 19.2 MHz. + * Get the PMC-clock number from ACPI _PR0 method and set it to 19.2 MHz. + * The PMC-clock number is also used to determine the default CSI port. + */ + clock_num = atomisp_csi2_get_pmc_clk_nr_from_acpi_pr0(adev); + + ret = atomisp_csi2_set_pmc_clk_freq(adev, clock_num); + if (ret) + goto err_put_adev; + + sensor->port = atomisp_csi2_get_port(adev, clock_num); + if (sensor->port >= ATOMISP_CAMERA_NR_PORTS) { + acpi_handle_err(adev->handle, "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) { + acpi_handle_err(adev->handle, "Invalid number of lanes: %d\n", sensor->lanes); + ret = -EINVAL; + goto err_put_adev; + } + + ret = atomisp_csi2_add_gpio_mappings(sensor, adev); + if (ret) + 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_remove_mappings; + + 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; + + bridge->n_sensors++; + } + + return 0; + +err_free_swnodes: + software_node_unregister_node_group(sensor->group); +err_remove_mappings: + acpi_dev_remove_driver_gpios(adev); +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, therefore: + * 1. The bridge memory is leaked deliberately on success + * 2. If a secondary fwnode is already set exit early. + */ + fwnode = dev_fwnode(dev); + if (fwnode && 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; +} |