forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
8.4 KiB
314 lines
8.4 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* Author: Dan Scally <[email protected]> */ |
|
|
|
#include <linux/acpi.h> |
|
#include <linux/device.h> |
|
#include <linux/pci.h> |
|
#include <linux/property.h> |
|
#include <media/v4l2-fwnode.h> |
|
|
|
#include "cio2-bridge.h" |
|
|
|
/* |
|
* Extend this array with ACPI Hardware IDs of devices known to be working |
|
* plus the number of link-frequencies expected by their drivers, along with |
|
* the frequency values in hertz. This is somewhat opportunistic way of adding |
|
* support for this for now in the hopes of a better source for the information |
|
* (possibly some encoded value in the SSDB buffer that we're unaware of) |
|
* becoming apparent in the future. |
|
* |
|
* Do not add an entry for a sensor that is not actually supported. |
|
*/ |
|
static const struct cio2_sensor_config cio2_supported_sensors[] = { |
|
/* Omnivision OV5693 */ |
|
CIO2_SENSOR_CONFIG("INT33BE", 0), |
|
/* Omnivision OV2680 */ |
|
CIO2_SENSOR_CONFIG("OVTI2680", 0), |
|
}; |
|
|
|
static const struct cio2_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 int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, |
|
void *data, u32 size) |
|
{ |
|
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; |
|
union acpi_object *obj; |
|
acpi_status status; |
|
int ret = 0; |
|
|
|
status = acpi_evaluate_object(adev->handle, id, NULL, &buffer); |
|
if (ACPI_FAILURE(status)) |
|
return -ENODEV; |
|
|
|
obj = buffer.pointer; |
|
if (!obj) { |
|
dev_err(&adev->dev, "Couldn't locate ACPI buffer\n"); |
|
return -ENODEV; |
|
} |
|
|
|
if (obj->type != ACPI_TYPE_BUFFER) { |
|
dev_err(&adev->dev, "Not an ACPI buffer\n"); |
|
ret = -ENODEV; |
|
goto out_free_buff; |
|
} |
|
|
|
if (obj->buffer.length > size) { |
|
dev_err(&adev->dev, "Given buffer is too small\n"); |
|
ret = -EINVAL; |
|
goto out_free_buff; |
|
} |
|
|
|
memcpy(data, obj->buffer.pointer, obj->buffer.length); |
|
|
|
out_free_buff: |
|
kfree(buffer.pointer); |
|
return ret; |
|
} |
|
|
|
static void cio2_bridge_create_fwnode_properties( |
|
struct cio2_sensor *sensor, |
|
struct cio2_bridge *bridge, |
|
const struct cio2_sensor_config *cfg) |
|
{ |
|
sensor->prop_names = prop_names; |
|
|
|
sensor->local_ref[0] = SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_CIO2_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, |
|
sensor->ssdb.mclkspeed); |
|
sensor->dev_properties[1] = PROPERTY_ENTRY_U8( |
|
sensor->prop_names.rotation, |
|
sensor->ssdb.degree); |
|
|
|
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->ssdb.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->cio2_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN( |
|
sensor->prop_names.data_lanes, |
|
bridge->data_lanes, |
|
sensor->ssdb.lanes); |
|
sensor->cio2_properties[1] = PROPERTY_ENTRY_REF_ARRAY( |
|
sensor->prop_names.remote_endpoint, |
|
sensor->remote_ref); |
|
} |
|
|
|
static void cio2_bridge_init_swnode_names(struct cio2_sensor *sensor) |
|
{ |
|
snprintf(sensor->node_names.remote_port, |
|
sizeof(sensor->node_names.remote_port), |
|
SWNODE_GRAPH_PORT_NAME_FMT, sensor->ssdb.link); |
|
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 cio2_bridge_create_connection_swnodes(struct cio2_bridge *bridge, |
|
struct cio2_sensor *sensor) |
|
{ |
|
struct software_node *nodes = sensor->swnodes; |
|
|
|
cio2_bridge_init_swnode_names(sensor); |
|
|
|
nodes[SWNODE_SENSOR_HID] = NODE_SENSOR(sensor->name, |
|
sensor->dev_properties); |
|
nodes[SWNODE_SENSOR_PORT] = NODE_PORT(sensor->node_names.port, |
|
&nodes[SWNODE_SENSOR_HID]); |
|
nodes[SWNODE_SENSOR_ENDPOINT] = NODE_ENDPOINT( |
|
sensor->node_names.endpoint, |
|
&nodes[SWNODE_SENSOR_PORT], |
|
sensor->ep_properties); |
|
nodes[SWNODE_CIO2_PORT] = NODE_PORT(sensor->node_names.remote_port, |
|
&bridge->cio2_hid_node); |
|
nodes[SWNODE_CIO2_ENDPOINT] = NODE_ENDPOINT( |
|
sensor->node_names.endpoint, |
|
&nodes[SWNODE_CIO2_PORT], |
|
sensor->cio2_properties); |
|
} |
|
|
|
static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) |
|
{ |
|
struct cio2_sensor *sensor; |
|
unsigned int i; |
|
|
|
for (i = 0; i < bridge->n_sensors; i++) { |
|
sensor = &bridge->sensors[i]; |
|
software_node_unregister_nodes(sensor->swnodes); |
|
acpi_dev_put(sensor->adev); |
|
} |
|
} |
|
|
|
static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, |
|
struct cio2_bridge *bridge, |
|
struct pci_dev *cio2) |
|
{ |
|
struct fwnode_handle *fwnode; |
|
struct cio2_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 >= CIO2_NUM_PORTS) { |
|
dev_err(&cio2->dev, "Exceeded available CIO2 ports\n"); |
|
cio2_bridge_unregister_sensors(bridge); |
|
ret = -EINVAL; |
|
goto err_out; |
|
} |
|
|
|
sensor = &bridge->sensors[bridge->n_sensors]; |
|
sensor->adev = adev; |
|
strscpy(sensor->name, cfg->hid, sizeof(sensor->name)); |
|
|
|
ret = cio2_bridge_read_acpi_buffer(adev, "SSDB", |
|
&sensor->ssdb, |
|
sizeof(sensor->ssdb)); |
|
if (ret) |
|
goto err_put_adev; |
|
|
|
if (sensor->ssdb.lanes > CIO2_MAX_LANES) { |
|
dev_err(&adev->dev, |
|
"Number of lanes in SSDB is invalid\n"); |
|
ret = -EINVAL; |
|
goto err_put_adev; |
|
} |
|
|
|
cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); |
|
cio2_bridge_create_connection_swnodes(bridge, sensor); |
|
|
|
ret = software_node_register_nodes(sensor->swnodes); |
|
if (ret) |
|
goto err_put_adev; |
|
|
|
fwnode = software_node_fwnode(&sensor->swnodes[ |
|
SWNODE_SENSOR_HID]); |
|
if (!fwnode) { |
|
ret = -ENODEV; |
|
goto err_free_swnodes; |
|
} |
|
|
|
adev->fwnode.secondary = fwnode; |
|
|
|
dev_info(&cio2->dev, "Found supported sensor %s\n", |
|
acpi_dev_name(adev)); |
|
|
|
bridge->n_sensors++; |
|
} |
|
|
|
return 0; |
|
|
|
err_free_swnodes: |
|
software_node_unregister_nodes(sensor->swnodes); |
|
err_put_adev: |
|
acpi_dev_put(sensor->adev); |
|
err_out: |
|
return ret; |
|
} |
|
|
|
static int cio2_bridge_connect_sensors(struct cio2_bridge *bridge, |
|
struct pci_dev *cio2) |
|
{ |
|
unsigned int i; |
|
int ret; |
|
|
|
for (i = 0; i < ARRAY_SIZE(cio2_supported_sensors); i++) { |
|
const struct cio2_sensor_config *cfg = |
|
&cio2_supported_sensors[i]; |
|
|
|
ret = cio2_bridge_connect_sensor(cfg, bridge, cio2); |
|
if (ret) |
|
goto err_unregister_sensors; |
|
} |
|
|
|
return 0; |
|
|
|
err_unregister_sensors: |
|
cio2_bridge_unregister_sensors(bridge); |
|
return ret; |
|
} |
|
|
|
int cio2_bridge_init(struct pci_dev *cio2) |
|
{ |
|
struct device *dev = &cio2->dev; |
|
struct fwnode_handle *fwnode; |
|
struct cio2_bridge *bridge; |
|
unsigned int i; |
|
int ret; |
|
|
|
bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); |
|
if (!bridge) |
|
return -ENOMEM; |
|
|
|
strscpy(bridge->cio2_node_name, CIO2_HID, |
|
sizeof(bridge->cio2_node_name)); |
|
bridge->cio2_hid_node.name = bridge->cio2_node_name; |
|
|
|
ret = software_node_register(&bridge->cio2_hid_node); |
|
if (ret < 0) { |
|
dev_err(dev, "Failed to register the CIO2 HID node\n"); |
|
goto err_free_bridge; |
|
} |
|
|
|
/* |
|
* Map the lane arrangement, which is fixed for the IPU3 (meaning we |
|
* only need one, rather than one per sensor). We include it as a |
|
* member of the struct cio2_bridge 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 < CIO2_MAX_LANES; i++) |
|
bridge->data_lanes[i] = i + 1; |
|
|
|
ret = cio2_bridge_connect_sensors(bridge, cio2); |
|
if (ret || bridge->n_sensors == 0) |
|
goto err_unregister_cio2; |
|
|
|
dev_info(dev, "Connected %d cameras\n", bridge->n_sensors); |
|
|
|
fwnode = software_node_fwnode(&bridge->cio2_hid_node); |
|
if (!fwnode) { |
|
dev_err(dev, "Error getting fwnode from cio2 software_node\n"); |
|
ret = -ENODEV; |
|
goto err_unregister_sensors; |
|
} |
|
|
|
set_secondary_fwnode(dev, fwnode); |
|
|
|
return 0; |
|
|
|
err_unregister_sensors: |
|
cio2_bridge_unregister_sensors(bridge); |
|
err_unregister_cio2: |
|
software_node_unregister(&bridge->cio2_hid_node); |
|
err_free_bridge: |
|
kfree(bridge); |
|
|
|
return ret; |
|
}
|
|
|