|
|
|
@@ -0,0 +1,810 @@
|
|
|
|
|
// 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, \
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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 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[] = {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
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 = {
|
|
|
|
|
.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_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;
|
|
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
|
|
sensor->port = atomisp_csi2_get_port(adev);
|
|
|
|
|
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;
|
|
|
|
|
}
|