Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0-only
source "drivers/media/pci/intel/ipu3/Kconfig"
source "drivers/media/pci/intel/ipu6/Kconfig"
source "drivers/media/pci/intel/ivsc/Kconfig"
config IPU_BRIDGE
tristate "Intel IPU Bridge"
depends on ACPI
depends on I2C
help
The IPU bridge is a helper library for Intel IPU drivers to
function on systems shipped with Windows.
Currently used by the ipu3-cio2 and atomisp drivers.
Supported systems include:
- Microsoft Surface models (except Surface Pro 3)
- The Lenovo Miix line (for example the 510, 520, 710 and 720)
- Dell 7285
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the IPU drivers
#
obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o
obj-y += ipu3/
obj-y += ivsc/
obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/
@@ -0,0 +1,934 @@
// SPDX-License-Identifier: GPL-2.0
/* Author: Dan Scally <djrscally@gmail.com> */
#include <linux/acpi.h>
#include <acpi/acpi_bus.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/i2c.h>
#include <linux/mei_cl_bus.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/string.h>
#include <linux/workqueue.h>
#include <media/ipu-bridge.h>
#include <media/v4l2-fwnode.h>
#define ADEV_DEV(adev) ACPI_PTR(&((adev)->dev))
/*
* 92335fcf-3203-4472-af93-7b4453ac29da
*
* Used to build MEI CSI device name to lookup MEI CSI device by
* device_find_child_by_name().
*/
#define MEI_CSI_UUID \
UUID_LE(0x92335FCF, 0x3203, 0x4472, \
0xAF, 0x93, 0x7B, 0x44, 0x53, 0xAC, 0x29, 0xDA)
/*
* IVSC device name
*
* Used to match IVSC device by ipu_bridge_match_ivsc_dev()
*/
#define IVSC_DEV_NAME "intel_vsc"
/*
* 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.
*
* Please keep the list sorted by ACPI HID.
*/
static const struct ipu_sensor_config ipu_supported_sensors[] = {
/* Himax HM11B1 */
IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000),
/* Himax HM2170 */
IPU_SENSOR_CONFIG("HIMX2170", 1, 384000000),
/* Himax HM2172 */
IPU_SENSOR_CONFIG("HIMX2172", 1, 384000000),
/* GalaxyCore GC0310 */
IPU_SENSOR_CONFIG("INT0310", 1, 55692000),
/* Omnivision OV5693 */
IPU_SENSOR_CONFIG("INT33BE", 1, 419200000),
/* Onsemi MT9M114 */
IPU_SENSOR_CONFIG("INT33F0", 1, 384000000),
/* Omnivision OV2740 */
IPU_SENSOR_CONFIG("INT3474", 1, 180000000),
/* Omnivision OV5670 */
IPU_SENSOR_CONFIG("INT3479", 1, 422400000),
/* Omnivision OV8865 */
IPU_SENSOR_CONFIG("INT347A", 1, 360000000),
/* Omnivision OV7251 */
IPU_SENSOR_CONFIG("INT347E", 1, 319200000),
/* Hynix Hi-556 */
IPU_SENSOR_CONFIG("INT3537", 1, 437000000),
/* Lontium lt6911uxe */
IPU_SENSOR_CONFIG("INTC10C5", 0),
/* Omnivision OV01A10 / OV01A1S */
IPU_SENSOR_CONFIG("OVTI01A0", 1, 400000000),
IPU_SENSOR_CONFIG("OVTI01AS", 1, 400000000),
/* Omnivision OV02C10 */
IPU_SENSOR_CONFIG("OVTI02C1", 1, 400000000),
/* Omnivision OV02E10 */
IPU_SENSOR_CONFIG("OVTI02E1", 1, 360000000),
/* Omnivision ov05c10 */
IPU_SENSOR_CONFIG("OVTI05C1", 1, 480000000),
/* Omnivision OV08A10 */
IPU_SENSOR_CONFIG("OVTI08A1", 1, 500000000),
/* Omnivision OV08x40 */
IPU_SENSOR_CONFIG("OVTI08F4", 3, 400000000, 749000000, 800000000),
/* Omnivision OV13B10 */
IPU_SENSOR_CONFIG("OVTI13B1", 1, 560000000),
IPU_SENSOR_CONFIG("OVTIDB10", 1, 560000000),
/* Omnivision OV2680 */
IPU_SENSOR_CONFIG("OVTI2680", 1, 331200000),
/* Omnivision OV5675 */
IPU_SENSOR_CONFIG("OVTI5675", 1, 450000000),
/* Omnivision OV8856 */
IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000),
/* Sony IMX471 */
IPU_SENSOR_CONFIG("SONY471A", 1, 200000000),
/* Toshiba T4KA3 */
IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000),
};
/*
* DMI matches for laptops which have their sensor mounted upside-down
* without reporting a rotation of 180° in neither the SSDB nor the _PLD.
*/
static const struct dmi_system_id upside_down_sensor_dmi_ids[] = {
{
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 13 9340"),
},
.driver_data = "OVTI02C1",
},
{
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 13 9350"),
},
.driver_data = "OVTI02C1",
},
{
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 14 9440"),
},
.driver_data = "OVTI02C1",
},
{
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 16 9640"),
},
.driver_data = "OVTI02C1",
},
{} /* Terminating entry */
};
static const struct ipu_property_names prop_names = {
.clock_frequency = "clock-frequency",
.rotation = "rotation",
.orientation = "orientation",
.bus_type = "bus-type",
.data_lanes = "data-lanes",
.remote_endpoint = "remote-endpoint",
.link_frequencies = "link-frequencies",
};
static const char * const ipu_vcm_types[] = {
"ad5823",
"dw9714",
"ad5816",
"dw9719",
"dw9718",
"dw9806b",
"wv517s",
"lc898122xa",
"lc898212axb",
};
/*
* Used to figure out IVSC acpi device by ipu_bridge_get_ivsc_acpi_dev()
* instead of device and driver match to probe IVSC device.
*/
static const struct acpi_device_id ivsc_acpi_ids[] = {
{ "INTC1059" },
{ "INTC1095" },
{ "INTC100A" },
{ "INTC10CF" },
{ "INTC10DE" }, /* LNL */
{ "INTC10E0" }, /* ARL */
{ "INTC10E1" }, /* PTL */
};
static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(ivsc_acpi_ids); i++) {
const struct acpi_device_id *acpi_id = &ivsc_acpi_ids[i];
struct acpi_device *consumer, *ivsc_adev;
acpi_handle handle = acpi_device_handle(ACPI_PTR(adev));
for_each_acpi_dev_match(ivsc_adev, acpi_id->id, NULL, -1)
/* camera sensor depends on IVSC in DSDT if exist */
for_each_acpi_consumer_dev(ivsc_adev, consumer)
if (ACPI_PTR(consumer->handle) == handle) {
acpi_dev_put(consumer);
return ivsc_adev;
}
}
return NULL;
}
static int ipu_bridge_match_ivsc_dev(struct device *dev, const void *adev)
{
if (ACPI_COMPANION(dev) != adev)
return 0;
if (!sysfs_streq(dev_name(dev), IVSC_DEV_NAME))
return 0;
return 1;
}
static struct device *ipu_bridge_get_ivsc_csi_dev(struct acpi_device *adev)
{
struct device *dev, *csi_dev;
uuid_le uuid = MEI_CSI_UUID;
char name[64];
/* IVSC device on platform bus */
dev = bus_find_device(&platform_bus_type, NULL, adev,
ipu_bridge_match_ivsc_dev);
if (dev) {
snprintf(name, sizeof(name), "%s-%pUl", dev_name(dev), &uuid);
csi_dev = device_find_child_by_name(dev, name);
put_device(dev);
return csi_dev;
}
/* Try to locate CVS device on the I2C bus */
csi_dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev);
if (csi_dev)
return csi_dev;
/* Fallback to platform bus for CVS device */
return bus_find_device_by_acpi_dev(&platform_bus_type, adev);
}
static int ipu_bridge_check_ivsc_dev(struct ipu_sensor *sensor,
struct acpi_device *sensor_adev)
{
struct acpi_device *adev;
struct device *csi_dev;
adev = ipu_bridge_get_ivsc_acpi_dev(sensor_adev);
if (adev) {
csi_dev = ipu_bridge_get_ivsc_csi_dev(adev);
if (!csi_dev) {
acpi_dev_put(adev);
dev_err(ADEV_DEV(adev), "Failed to find MEI or CVS CSI dev\n");
return -ENODEV;
}
sensor->csi_dev = csi_dev;
sensor->ivsc_adev = adev;
}
return 0;
}
static int ipu_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(ACPI_PTR(adev->handle),
id, NULL, &buffer);
if (ACPI_FAILURE(status))
return -ENODEV;
obj = buffer.pointer;
if (!obj) {
dev_err(ADEV_DEV(adev), "Couldn't locate ACPI buffer\n");
return -ENODEV;
}
if (obj->type != ACPI_TYPE_BUFFER) {
dev_err(ADEV_DEV(adev), "Not an ACPI buffer\n");
ret = -ENODEV;
goto out_free_buff;
}
if (obj->buffer.length > size) {
dev_err(ADEV_DEV(adev), "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 u32 ipu_bridge_parse_rotation(struct acpi_device *adev,
struct ipu_sensor_ssdb *ssdb)
{
const struct dmi_system_id *dmi_id;
dmi_id = dmi_first_match(upside_down_sensor_dmi_ids);
if (dmi_id && acpi_dev_hid_match(adev, dmi_id->driver_data))
return 180;
switch (ssdb->degree) {
case IPU_SENSOR_ROTATION_NORMAL:
return 0;
case IPU_SENSOR_ROTATION_INVERTED:
return 180;
default:
dev_warn(ADEV_DEV(adev),
"Unknown rotation %d. Assume 0 degree rotation\n",
ssdb->degree);
return 0;
}
}
static enum v4l2_fwnode_orientation ipu_bridge_parse_orientation(struct acpi_device *adev)
{
enum v4l2_fwnode_orientation orientation;
struct acpi_pld_info *pld = NULL;
if (!acpi_get_physical_device_location(ACPI_PTR(adev->handle), &pld)) {
dev_warn(ADEV_DEV(adev), "_PLD call failed, using default orientation\n");
return V4L2_FWNODE_ORIENTATION_EXTERNAL;
}
switch (pld->panel) {
case ACPI_PLD_PANEL_FRONT:
orientation = V4L2_FWNODE_ORIENTATION_FRONT;
break;
case ACPI_PLD_PANEL_BACK:
orientation = V4L2_FWNODE_ORIENTATION_BACK;
break;
case ACPI_PLD_PANEL_TOP:
case ACPI_PLD_PANEL_LEFT:
case ACPI_PLD_PANEL_RIGHT:
case ACPI_PLD_PANEL_UNKNOWN:
orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL;
break;
default:
dev_warn(ADEV_DEV(adev), "Unknown _PLD panel val %d\n",
pld->panel);
orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL;
break;
}
ACPI_FREE(pld);
return orientation;
}
int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor)
{
struct ipu_sensor_ssdb ssdb = {};
int ret;
ret = ipu_bridge_read_acpi_buffer(adev, "SSDB", &ssdb, sizeof(ssdb));
if (ret)
return ret;
if (ssdb.vcmtype > ARRAY_SIZE(ipu_vcm_types)) {
dev_warn(ADEV_DEV(adev), "Unknown VCM type %d\n", ssdb.vcmtype);
ssdb.vcmtype = 0;
}
if (ssdb.lanes > IPU_MAX_LANES) {
dev_err(ADEV_DEV(adev), "Number of lanes in SSDB is invalid\n");
return -EINVAL;
}
sensor->link = ssdb.link;
sensor->lanes = ssdb.lanes;
sensor->mclkspeed = ssdb.mclkspeed;
sensor->rotation = ipu_bridge_parse_rotation(adev, &ssdb);
sensor->orientation = ipu_bridge_parse_orientation(adev);
if (ssdb.vcmtype)
sensor->vcm_type = ipu_vcm_types[ssdb.vcmtype - 1];
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu_bridge_parse_ssdb, "INTEL_IPU_BRIDGE");
static void ipu_bridge_create_fwnode_properties(
struct ipu_sensor *sensor,
struct ipu_bridge *bridge,
const struct ipu_sensor_config *cfg)
{
struct ipu_property_names *names = &sensor->prop_names;
struct software_node *nodes = sensor->swnodes;
sensor->prop_names = prop_names;
if (sensor->csi_dev) {
sensor->local_ref[0] =
SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_IVSC_SENSOR_ENDPOINT]);
sensor->remote_ref[0] =
SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_IVSC_IPU_ENDPOINT]);
sensor->ivsc_sensor_ref[0] =
SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_SENSOR_ENDPOINT]);
sensor->ivsc_ipu_ref[0] =
SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_IPU_ENDPOINT]);
sensor->ivsc_sensor_ep_properties[0] =
PROPERTY_ENTRY_U32(names->bus_type,
V4L2_FWNODE_BUS_TYPE_CSI2_DPHY);
sensor->ivsc_sensor_ep_properties[1] =
PROPERTY_ENTRY_U32_ARRAY_LEN(names->data_lanes,
bridge->data_lanes,
sensor->lanes);
sensor->ivsc_sensor_ep_properties[2] =
PROPERTY_ENTRY_REF_ARRAY(names->remote_endpoint,
sensor->ivsc_sensor_ref);
sensor->ivsc_ipu_ep_properties[0] =
PROPERTY_ENTRY_U32(names->bus_type,
V4L2_FWNODE_BUS_TYPE_CSI2_DPHY);
sensor->ivsc_ipu_ep_properties[1] =
PROPERTY_ENTRY_U32_ARRAY_LEN(names->data_lanes,
bridge->data_lanes,
sensor->lanes);
sensor->ivsc_ipu_ep_properties[2] =
PROPERTY_ENTRY_REF_ARRAY(names->remote_endpoint,
sensor->ivsc_ipu_ref);
} else {
sensor->local_ref[0] =
SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_IPU_ENDPOINT]);
sensor->remote_ref[0] =
SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_SENSOR_ENDPOINT]);
}
sensor->dev_properties[0] = PROPERTY_ENTRY_U32(
sensor->prop_names.clock_frequency,
sensor->mclkspeed);
sensor->dev_properties[1] = PROPERTY_ENTRY_U32(
sensor->prop_names.rotation,
sensor->rotation);
sensor->dev_properties[2] = PROPERTY_ENTRY_U32(
sensor->prop_names.orientation,
sensor->orientation);
if (sensor->vcm_type) {
sensor->vcm_ref[0] =
SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_VCM]);
sensor->dev_properties[3] =
PROPERTY_ENTRY_REF_ARRAY("lens-focus", sensor->vcm_ref);
}
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->ipu_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN(
sensor->prop_names.data_lanes,
bridge->data_lanes, sensor->lanes);
sensor->ipu_properties[1] = PROPERTY_ENTRY_REF_ARRAY(
sensor->prop_names.remote_endpoint,
sensor->remote_ref);
}
static void ipu_bridge_init_swnode_names(struct ipu_sensor *sensor)
{
snprintf(sensor->node_names.remote_port,
sizeof(sensor->node_names.remote_port),
SWNODE_GRAPH_PORT_NAME_FMT, sensor->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 */
if (sensor->vcm_type) {
/* append link to distinguish nodes with same model VCM */
snprintf(sensor->node_names.vcm, sizeof(sensor->node_names.vcm),
"%s-%u", sensor->vcm_type, sensor->link);
}
if (sensor->csi_dev) {
snprintf(sensor->node_names.ivsc_sensor_port,
sizeof(sensor->node_names.ivsc_sensor_port),
SWNODE_GRAPH_PORT_NAME_FMT, 0);
snprintf(sensor->node_names.ivsc_ipu_port,
sizeof(sensor->node_names.ivsc_ipu_port),
SWNODE_GRAPH_PORT_NAME_FMT, 1);
}
}
static void ipu_bridge_init_swnode_group(struct ipu_sensor *sensor)
{
struct software_node *nodes = sensor->swnodes;
sensor->group[SWNODE_SENSOR_HID] = &nodes[SWNODE_SENSOR_HID];
sensor->group[SWNODE_SENSOR_PORT] = &nodes[SWNODE_SENSOR_PORT];
sensor->group[SWNODE_SENSOR_ENDPOINT] = &nodes[SWNODE_SENSOR_ENDPOINT];
sensor->group[SWNODE_IPU_PORT] = &nodes[SWNODE_IPU_PORT];
sensor->group[SWNODE_IPU_ENDPOINT] = &nodes[SWNODE_IPU_ENDPOINT];
if (sensor->vcm_type)
sensor->group[SWNODE_VCM] = &nodes[SWNODE_VCM];
if (sensor->csi_dev) {
sensor->group[SWNODE_IVSC_HID] =
&nodes[SWNODE_IVSC_HID];
sensor->group[SWNODE_IVSC_SENSOR_PORT] =
&nodes[SWNODE_IVSC_SENSOR_PORT];
sensor->group[SWNODE_IVSC_SENSOR_ENDPOINT] =
&nodes[SWNODE_IVSC_SENSOR_ENDPOINT];
sensor->group[SWNODE_IVSC_IPU_PORT] =
&nodes[SWNODE_IVSC_IPU_PORT];
sensor->group[SWNODE_IVSC_IPU_ENDPOINT] =
&nodes[SWNODE_IVSC_IPU_ENDPOINT];
if (sensor->vcm_type)
sensor->group[SWNODE_VCM] = &nodes[SWNODE_VCM];
} else {
if (sensor->vcm_type)
sensor->group[SWNODE_IVSC_HID] = &nodes[SWNODE_VCM];
}
}
static void ipu_bridge_create_connection_swnodes(struct ipu_bridge *bridge,
struct ipu_sensor *sensor)
{
struct ipu_node_names *names = &sensor->node_names;
struct software_node *nodes = sensor->swnodes;
ipu_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_IPU_PORT] = NODE_PORT(sensor->node_names.remote_port,
&bridge->ipu_hid_node);
nodes[SWNODE_IPU_ENDPOINT] = NODE_ENDPOINT(
sensor->node_names.endpoint,
&nodes[SWNODE_IPU_PORT],
sensor->ipu_properties);
if (sensor->csi_dev) {
const char *device_hid = "";
device_hid = acpi_device_hid(sensor->ivsc_adev);
snprintf(sensor->ivsc_name, sizeof(sensor->ivsc_name), "%s-%u",
device_hid, sensor->link);
nodes[SWNODE_IVSC_HID] = NODE_SENSOR(sensor->ivsc_name,
sensor->ivsc_properties);
nodes[SWNODE_IVSC_SENSOR_PORT] =
NODE_PORT(names->ivsc_sensor_port,
&nodes[SWNODE_IVSC_HID]);
nodes[SWNODE_IVSC_SENSOR_ENDPOINT] =
NODE_ENDPOINT(names->endpoint,
&nodes[SWNODE_IVSC_SENSOR_PORT],
sensor->ivsc_sensor_ep_properties);
nodes[SWNODE_IVSC_IPU_PORT] =
NODE_PORT(names->ivsc_ipu_port,
&nodes[SWNODE_IVSC_HID]);
nodes[SWNODE_IVSC_IPU_ENDPOINT] =
NODE_ENDPOINT(names->endpoint,
&nodes[SWNODE_IVSC_IPU_PORT],
sensor->ivsc_ipu_ep_properties);
}
nodes[SWNODE_VCM] = NODE_VCM(sensor->node_names.vcm);
ipu_bridge_init_swnode_group(sensor);
}
/*
* The actual instantiation must be done from a workqueue to avoid
* a deadlock on taking list_lock from v4l2-async twice.
*/
struct ipu_bridge_instantiate_vcm_work_data {
struct work_struct work;
struct device *sensor;
char name[16];
struct i2c_board_info board_info;
};
static void ipu_bridge_instantiate_vcm_work(struct work_struct *work)
{
struct ipu_bridge_instantiate_vcm_work_data *data =
container_of(work, struct ipu_bridge_instantiate_vcm_work_data,
work);
struct acpi_device *adev = ACPI_COMPANION(data->sensor);
struct i2c_client *vcm_client;
bool put_fwnode = true;
int ret;
/*
* The client may get probed before the device_link gets added below
* make sure the sensor is powered-up during probe.
*/
ret = pm_runtime_get_sync(data->sensor);
if (ret < 0) {
dev_err(data->sensor, "Error %d runtime-resuming sensor, cannot instantiate VCM\n",
ret);
goto out_pm_put;
}
/*
* Note the client is created only once and then kept around
* even after a rmmod, just like the software-nodes.
*/
vcm_client = i2c_acpi_new_device_by_fwnode(acpi_fwnode_handle(adev),
1, &data->board_info);
if (IS_ERR(vcm_client)) {
dev_err(data->sensor, "Error instantiating VCM client: %pe\n",
vcm_client);
goto out_pm_put;
}
device_link_add(&vcm_client->dev, data->sensor, DL_FLAG_PM_RUNTIME);
dev_info(data->sensor, "Instantiated %s VCM\n", data->board_info.type);
put_fwnode = false; /* Ownership has passed to the i2c-client */
out_pm_put:
pm_runtime_put(data->sensor);
put_device(data->sensor);
if (put_fwnode)
fwnode_handle_put(data->board_info.fwnode);
kfree(data);
}
int ipu_bridge_instantiate_vcm(struct device *sensor)
{
struct ipu_bridge_instantiate_vcm_work_data *data;
struct fwnode_handle *vcm_fwnode;
struct i2c_client *vcm_client;
struct acpi_device *adev;
char *sep;
adev = ACPI_COMPANION(sensor);
if (!adev)
return 0;
vcm_fwnode = fwnode_find_reference(dev_fwnode(sensor), "lens-focus", 0);
if (IS_ERR(vcm_fwnode))
return 0;
/* When reloading modules the client will already exist */
vcm_client = i2c_find_device_by_fwnode(vcm_fwnode);
if (vcm_client) {
fwnode_handle_put(vcm_fwnode);
put_device(&vcm_client->dev);
return 0;
}
data = kzalloc_obj(*data);
if (!data) {
fwnode_handle_put(vcm_fwnode);
return -ENOMEM;
}
INIT_WORK(&data->work, ipu_bridge_instantiate_vcm_work);
data->sensor = get_device(sensor);
snprintf(data->name, sizeof(data->name), "%s-VCM",
acpi_dev_name(adev));
data->board_info.dev_name = data->name;
data->board_info.fwnode = vcm_fwnode;
snprintf(data->board_info.type, sizeof(data->board_info.type),
"%pfwP", vcm_fwnode);
/* Strip "-<link>" postfix */
sep = strchrnul(data->board_info.type, '-');
*sep = 0;
queue_work(system_long_wq, &data->work);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu_bridge_instantiate_vcm, "INTEL_IPU_BRIDGE");
static int ipu_bridge_instantiate_ivsc(struct ipu_sensor *sensor)
{
struct fwnode_handle *fwnode;
if (!sensor->csi_dev)
return 0;
fwnode = software_node_fwnode(&sensor->swnodes[SWNODE_IVSC_HID]);
if (!fwnode)
return -ENODEV;
set_secondary_fwnode(sensor->csi_dev, fwnode);
return 0;
}
static void ipu_bridge_unregister_sensors(struct ipu_bridge *bridge)
{
struct ipu_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);
put_device(sensor->csi_dev);
acpi_dev_put(sensor->ivsc_adev);
}
}
static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg,
struct ipu_bridge *bridge)
{
struct fwnode_handle *fwnode, *primary;
struct ipu_sensor *sensor;
struct acpi_device *adev = NULL;
int ret;
for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) {
if (!ACPI_PTR(adev->status.enabled))
continue;
if (bridge->n_sensors >= IPU_MAX_PORTS) {
acpi_dev_put(adev);
dev_err(bridge->dev, "Exceeded available IPU ports\n");
return -EINVAL;
}
sensor = &bridge->sensors[bridge->n_sensors];
ret = bridge->parse_sensor_fwnode(adev, sensor);
if (ret)
goto err_put_adev;
snprintf(sensor->name, sizeof(sensor->name), "%s-%u",
cfg->hid, sensor->link);
ret = ipu_bridge_check_ivsc_dev(sensor, adev);
if (ret)
goto err_put_adev;
ipu_bridge_create_fwnode_properties(sensor, bridge, cfg);
ipu_bridge_create_connection_swnodes(bridge, sensor);
ret = software_node_register_node_group(sensor->group);
if (ret)
goto err_put_ivsc;
fwnode = software_node_fwnode(&sensor->swnodes[
SWNODE_SENSOR_HID]);
if (!fwnode) {
ret = -ENODEV;
goto err_free_swnodes;
}
sensor->adev = ACPI_PTR(acpi_dev_get(adev));
primary = acpi_fwnode_handle(adev);
primary->secondary = fwnode;
ret = ipu_bridge_instantiate_ivsc(sensor);
if (ret)
goto err_free_swnodes;
dev_info(bridge->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_ivsc:
put_device(sensor->csi_dev);
acpi_dev_put(sensor->ivsc_adev);
err_put_adev:
acpi_dev_put(adev);
return ret;
}
static int ipu_bridge_connect_sensors(struct ipu_bridge *bridge)
{
unsigned int i;
int ret;
for (i = 0; i < ARRAY_SIZE(ipu_supported_sensors); i++) {
const struct ipu_sensor_config *cfg =
&ipu_supported_sensors[i];
ret = ipu_bridge_connect_sensor(cfg, bridge);
if (ret)
goto err_unregister_sensors;
}
return 0;
err_unregister_sensors:
ipu_bridge_unregister_sensors(bridge);
return ret;
}
static int ipu_bridge_ivsc_is_ready(void)
{
struct acpi_device *sensor_adev, *adev;
struct device *csi_dev;
bool ready = true;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(ipu_supported_sensors); i++) {
const struct ipu_sensor_config *cfg =
&ipu_supported_sensors[i];
for_each_acpi_dev_match(sensor_adev, cfg->hid, NULL, -1) {
if (!ACPI_PTR(sensor_adev->status.enabled))
continue;
adev = ipu_bridge_get_ivsc_acpi_dev(sensor_adev);
if (!adev)
continue;
csi_dev = ipu_bridge_get_ivsc_csi_dev(adev);
if (!csi_dev)
ready = false;
put_device(csi_dev);
acpi_dev_put(adev);
}
}
return ready;
}
static int ipu_bridge_check_fwnode_graph(struct fwnode_handle *fwnode)
{
struct fwnode_handle *endpoint;
if (IS_ERR_OR_NULL(fwnode))
return -EINVAL;
endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL);
if (endpoint) {
fwnode_handle_put(endpoint);
return 0;
}
return ipu_bridge_check_fwnode_graph(fwnode->secondary);
}
static DEFINE_MUTEX(ipu_bridge_mutex);
int ipu_bridge_init(struct device *dev,
ipu_parse_sensor_fwnode_t parse_sensor_fwnode)
{
struct fwnode_handle *fwnode;
struct ipu_bridge *bridge;
unsigned int i;
int ret;
guard(mutex)(&ipu_bridge_mutex);
if (!ipu_bridge_check_fwnode_graph(dev_fwnode(dev)))
return 0;
if (!ipu_bridge_ivsc_is_ready())
return dev_err_probe(dev, -EPROBE_DEFER,
"waiting for IVSC to become ready\n");
bridge = kzalloc_obj(*bridge);
if (!bridge)
return -ENOMEM;
strscpy(bridge->ipu_node_name, IPU_HID,
sizeof(bridge->ipu_node_name));
bridge->ipu_hid_node.name = bridge->ipu_node_name;
bridge->dev = dev;
bridge->parse_sensor_fwnode = parse_sensor_fwnode;
ret = software_node_register(&bridge->ipu_hid_node);
if (ret < 0) {
dev_err(dev, "Failed to register the IPU 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 ipu_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 < IPU_MAX_LANES; i++)
bridge->data_lanes[i] = i + 1;
ret = ipu_bridge_connect_sensors(bridge);
if (ret || bridge->n_sensors == 0)
goto err_unregister_ipu;
dev_info(dev, "Connected %d cameras\n", bridge->n_sensors);
fwnode = software_node_fwnode(&bridge->ipu_hid_node);
if (!fwnode) {
dev_err(dev, "Error getting fwnode from ipu software_node\n");
ret = -ENODEV;
goto err_unregister_sensors;
}
set_secondary_fwnode(dev, fwnode);
return 0;
err_unregister_sensors:
ipu_bridge_unregister_sensors(bridge);
err_unregister_ipu:
software_node_unregister(&bridge->ipu_hid_node);
err_free_bridge:
kfree(bridge);
return ret;
}
EXPORT_SYMBOL_NS_GPL(ipu_bridge_init, "INTEL_IPU_BRIDGE");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Intel IPU Sensors Bridge driver");
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0-only
config VIDEO_IPU3_CIO2
tristate "Intel ipu3-cio2 driver"
depends on VIDEO_DEV && PCI
depends on IPU_BRIDGE || !IPU_BRIDGE
depends on ACPI || COMPILE_TEST
depends on X86
select MEDIA_CONTROLLER
select VIDEO_V4L2_SUBDEV_API
select V4L2_FWNODE
select VIDEOBUF2_DMA_SG
help
This is the Intel IPU3 CIO2 CSI-2 receiver unit, found in Intel
Skylake and Kaby Lake SoCs and used for capturing images and
video from a camera sensor.
Say Y or M here if you have a Skylake/Kaby Lake SoC with MIPI CSI-2
connected camera.
The module will be called ipu3-cio2.
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,456 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2017 Intel Corporation */
#ifndef __IPU3_CIO2_H
#define __IPU3_CIO2_H
#include <linux/bits.h>
#include <linux/dma-mapping.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <asm/page.h>
#include <media/media-device.h>
#include <media/media-entity.h>
#include <media/v4l2-async.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-v4l2.h>
struct cio2_fbpt_entry; /* defined here, after the first usage */
struct pci_dev;
#define CIO2_NAME "ipu3-cio2"
#define CIO2_DEVICE_NAME "Intel IPU3 CIO2"
#define CIO2_ENTITY_NAME "ipu3-csi2"
#define CIO2_PCI_ID 0x9d32
#define CIO2_PCI_BAR 0
#define CIO2_DMA_MASK DMA_BIT_MASK(39)
#define CIO2_IMAGE_MAX_WIDTH 4224U
#define CIO2_IMAGE_MAX_HEIGHT 3136U
/* 32MB = 8xFBPT_entry */
#define CIO2_MAX_LOPS 8
#define CIO2_MAX_BUFFERS (PAGE_SIZE / 16 / CIO2_MAX_LOPS)
#define CIO2_LOP_ENTRIES (PAGE_SIZE / sizeof(u32))
#define CIO2_PAD_SINK 0U
#define CIO2_PAD_SOURCE 1U
#define CIO2_PADS 2U
#define CIO2_NUM_DMA_CHAN 20U
#define CIO2_NUM_PORTS 4U /* DPHYs */
/* 1 for each sensor */
#define CIO2_QUEUES CIO2_NUM_PORTS
/* Register and bit field definitions */
#define CIO2_REG_PIPE_BASE(n) ((n) * 0x0400) /* n = 0..3 */
#define CIO2_REG_CSIRX_BASE 0x000
#define CIO2_REG_MIPIBE_BASE 0x100
#define CIO2_REG_PIXELGEN_BAS 0x200
#define CIO2_REG_IRQCTRL_BASE 0x300
#define CIO2_REG_GPREG_BASE 0x1000
/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_CSIRX_BASE */
#define CIO2_REG_CSIRX_ENABLE (CIO2_REG_CSIRX_BASE + 0x0)
#define CIO2_REG_CSIRX_NOF_ENABLED_LANES (CIO2_REG_CSIRX_BASE + 0x4)
#define CIO2_REG_CSIRX_SP_IF_CONFIG (CIO2_REG_CSIRX_BASE + 0x10)
#define CIO2_REG_CSIRX_LP_IF_CONFIG (CIO2_REG_CSIRX_BASE + 0x14)
#define CIO2_CSIRX_IF_CONFIG_FILTEROUT 0x00
#define CIO2_CSIRX_IF_CONFIG_FILTEROUT_VC_INACTIVE 0x01
#define CIO2_CSIRX_IF_CONFIG_PASS 0x02
#define CIO2_CSIRX_IF_CONFIG_FLAG_ERROR BIT(2)
#define CIO2_REG_CSIRX_STATUS (CIO2_REG_CSIRX_BASE + 0x18)
#define CIO2_REG_CSIRX_STATUS_DLANE_HS (CIO2_REG_CSIRX_BASE + 0x1c)
#define CIO2_CSIRX_STATUS_DLANE_HS_MASK 0xff
#define CIO2_REG_CSIRX_STATUS_DLANE_LP (CIO2_REG_CSIRX_BASE + 0x20)
#define CIO2_CSIRX_STATUS_DLANE_LP_MASK 0xffffff
/* Termination enable and settle in 0.0625ns units, lane=0..3 or -1 for clock */
#define CIO2_REG_CSIRX_DLY_CNT_TERMEN(lane) \
(CIO2_REG_CSIRX_BASE + 0x2c + 8 * (lane))
#define CIO2_REG_CSIRX_DLY_CNT_SETTLE(lane) \
(CIO2_REG_CSIRX_BASE + 0x30 + 8 * (lane))
/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_MIPIBE_BASE */
#define CIO2_REG_MIPIBE_ENABLE (CIO2_REG_MIPIBE_BASE + 0x0)
#define CIO2_REG_MIPIBE_STATUS (CIO2_REG_MIPIBE_BASE + 0x4)
#define CIO2_REG_MIPIBE_COMP_FORMAT(vc) \
(CIO2_REG_MIPIBE_BASE + 0x8 + 0x4 * (vc))
#define CIO2_REG_MIPIBE_FORCE_RAW8 (CIO2_REG_MIPIBE_BASE + 0x20)
#define CIO2_REG_MIPIBE_FORCE_RAW8_ENABLE BIT(0)
#define CIO2_REG_MIPIBE_FORCE_RAW8_USE_TYPEID BIT(1)
#define CIO2_REG_MIPIBE_FORCE_RAW8_TYPEID_SHIFT 2U
#define CIO2_REG_MIPIBE_IRQ_STATUS (CIO2_REG_MIPIBE_BASE + 0x24)
#define CIO2_REG_MIPIBE_IRQ_CLEAR (CIO2_REG_MIPIBE_BASE + 0x28)
#define CIO2_REG_MIPIBE_GLOBAL_LUT_DISREGARD (CIO2_REG_MIPIBE_BASE + 0x68)
#define CIO2_MIPIBE_GLOBAL_LUT_DISREGARD 1U
#define CIO2_REG_MIPIBE_PKT_STALL_STATUS (CIO2_REG_MIPIBE_BASE + 0x6c)
#define CIO2_REG_MIPIBE_PARSE_GSP_THROUGH_LP_LUT_REG_IDX \
(CIO2_REG_MIPIBE_BASE + 0x70)
#define CIO2_REG_MIPIBE_SP_LUT_ENTRY(vc) \
(CIO2_REG_MIPIBE_BASE + 0x74 + 4 * (vc))
#define CIO2_REG_MIPIBE_LP_LUT_ENTRY(m) /* m = 0..15 */ \
(CIO2_REG_MIPIBE_BASE + 0x84 + 4 * (m))
#define CIO2_MIPIBE_LP_LUT_ENTRY_DISREGARD 1U
#define CIO2_MIPIBE_LP_LUT_ENTRY_SID_SHIFT 1U
#define CIO2_MIPIBE_LP_LUT_ENTRY_VC_SHIFT 5U
#define CIO2_MIPIBE_LP_LUT_ENTRY_FORMAT_TYPE_SHIFT 7U
/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_IRQCTRL_BASE */
/* IRQ registers are 18-bit wide, see cio2_irq_error for bit definitions */
#define CIO2_REG_IRQCTRL_EDGE (CIO2_REG_IRQCTRL_BASE + 0x00)
#define CIO2_REG_IRQCTRL_MASK (CIO2_REG_IRQCTRL_BASE + 0x04)
#define CIO2_REG_IRQCTRL_STATUS (CIO2_REG_IRQCTRL_BASE + 0x08)
#define CIO2_REG_IRQCTRL_CLEAR (CIO2_REG_IRQCTRL_BASE + 0x0c)
#define CIO2_REG_IRQCTRL_ENABLE (CIO2_REG_IRQCTRL_BASE + 0x10)
#define CIO2_REG_IRQCTRL_LEVEL_NOT_PULSE (CIO2_REG_IRQCTRL_BASE + 0x14)
#define CIO2_REG_GPREG_SRST (CIO2_REG_GPREG_BASE + 0x0)
#define CIO2_GPREG_SRST_ALL 0xffff /* Reset all */
#define CIO2_REG_FB_HPLL_FREQ (CIO2_REG_GPREG_BASE + 0x08)
#define CIO2_REG_ISCLK_RATIO (CIO2_REG_GPREG_BASE + 0xc)
#define CIO2_REG_CGC 0x1400
#define CIO2_CGC_CSI2_TGE BIT(0)
#define CIO2_CGC_PRIM_TGE BIT(1)
#define CIO2_CGC_SIDE_TGE BIT(2)
#define CIO2_CGC_XOSC_TGE BIT(3)
#define CIO2_CGC_MPLL_SHUTDOWN_EN BIT(4)
#define CIO2_CGC_D3I3_TGE BIT(5)
#define CIO2_CGC_CSI2_INTERFRAME_TGE BIT(6)
#define CIO2_CGC_CSI2_PORT_DCGE BIT(8)
#define CIO2_CGC_CSI2_DCGE BIT(9)
#define CIO2_CGC_SIDE_DCGE BIT(10)
#define CIO2_CGC_PRIM_DCGE BIT(11)
#define CIO2_CGC_ROSC_DCGE BIT(12)
#define CIO2_CGC_XOSC_DCGE BIT(13)
#define CIO2_CGC_FLIS_DCGE BIT(14)
#define CIO2_CGC_CLKGATE_HOLDOFF_SHIFT 20U
#define CIO2_CGC_CSI_CLKGATE_HOLDOFF_SHIFT 24U
#define CIO2_REG_D0I3C 0x1408
#define CIO2_D0I3C_I3 BIT(2) /* Set D0I3 */
#define CIO2_D0I3C_RR BIT(3) /* Restore? */
#define CIO2_REG_SWRESET 0x140c
#define CIO2_SWRESET_SWRESET 1U
#define CIO2_REG_SENSOR_ACTIVE 0x1410
#define CIO2_REG_INT_STS 0x1414
#define CIO2_REG_INT_STS_EXT_OE 0x1418
#define CIO2_INT_EXT_OE_DMAOE_SHIFT 0U
#define CIO2_INT_EXT_OE_DMAOE_MASK 0x7ffff
#define CIO2_INT_EXT_OE_OES_SHIFT 24U
#define CIO2_INT_EXT_OE_OES_MASK (0xf << CIO2_INT_EXT_OE_OES_SHIFT)
#define CIO2_REG_INT_EN 0x1420
#define CIO2_REG_INT_EN_IRQ (1 << 24)
#define CIO2_REG_INT_EN_IOS(dma) (1U << (((dma) >> 1U) + 12U))
/*
* Interrupt on completion bit, Eg. DMA 0-3 maps to bit 0-3,
* DMA4 & DMA5 map to bit 4 ... DMA18 & DMA19 map to bit 11 Et cetera
*/
#define CIO2_INT_IOC(dma) (1U << ((dma) < 4U ? (dma) : ((dma) >> 1U) + 2U))
#define CIO2_INT_IOC_SHIFT 0
#define CIO2_INT_IOC_MASK (0x7ff << CIO2_INT_IOC_SHIFT)
#define CIO2_INT_IOS_IOLN(dma) (1U << (((dma) >> 1U) + 12U))
#define CIO2_INT_IOS_IOLN_SHIFT 12
#define CIO2_INT_IOS_IOLN_MASK (0x3ff << CIO2_INT_IOS_IOLN_SHIFT)
#define CIO2_INT_IOIE BIT(22)
#define CIO2_INT_IOOE BIT(23)
#define CIO2_INT_IOIRQ BIT(24)
#define CIO2_REG_INT_EN_EXT_OE 0x1424
#define CIO2_REG_DMA_DBG 0x1448
#define CIO2_REG_DMA_DBG_DMA_INDEX_SHIFT 0U
#define CIO2_REG_PBM_ARB_CTRL 0x1460
#define CIO2_PBM_ARB_CTRL_LANES_DIV 0U /* 4-4-2-2 lanes */
#define CIO2_PBM_ARB_CTRL_LANES_DIV_SHIFT 0U
#define CIO2_PBM_ARB_CTRL_LE_EN BIT(7)
#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN 2U
#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN_SHIFT 8U
#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP 480U
#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP_SHIFT 16U
#define CIO2_REG_PBM_WMCTRL1 0x1464
#define CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT 0U
#define CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT 8U
#define CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT 16U
#define CIO2_PBM_WMCTRL1_TS_COUNT_DISABLE BIT(31)
#define CIO2_PBM_WMCTRL1_MIN_2CK (4 << CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT)
#define CIO2_PBM_WMCTRL1_MID1_2CK (16 << CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT)
#define CIO2_PBM_WMCTRL1_MID2_2CK (21 << CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT)
#define CIO2_REG_PBM_WMCTRL2 0x1468
#define CIO2_PBM_WMCTRL2_HWM_2CK 40U
#define CIO2_PBM_WMCTRL2_HWM_2CK_SHIFT 0U
#define CIO2_PBM_WMCTRL2_LWM_2CK 22U
#define CIO2_PBM_WMCTRL2_LWM_2CK_SHIFT 8U
#define CIO2_PBM_WMCTRL2_OBFFWM_2CK 2U
#define CIO2_PBM_WMCTRL2_OBFFWM_2CK_SHIFT 16U
#define CIO2_PBM_WMCTRL2_TRANSDYN 1U
#define CIO2_PBM_WMCTRL2_TRANSDYN_SHIFT 24U
#define CIO2_PBM_WMCTRL2_DYNWMEN BIT(28)
#define CIO2_PBM_WMCTRL2_OBFF_MEM_EN BIT(29)
#define CIO2_PBM_WMCTRL2_OBFF_CPU_EN BIT(30)
#define CIO2_PBM_WMCTRL2_DRAINNOW BIT(31)
#define CIO2_REG_PBM_TS_COUNT 0x146c
#define CIO2_REG_PBM_FOPN_ABORT 0x1474
/* below n = 0..3 */
#define CIO2_PBM_FOPN_ABORT(n) (0x1 << 8U * (n))
#define CIO2_PBM_FOPN_FORCE_ABORT(n) (0x2 << 8U * (n))
#define CIO2_PBM_FOPN_FRAMEOPEN(n) (0x8 << 8U * (n))
#define CIO2_REG_LTRCTRL 0x1480
#define CIO2_LTRCTRL_LTRDYNEN BIT(16)
#define CIO2_LTRCTRL_LTRSTABLETIME_SHIFT 8U
#define CIO2_LTRCTRL_LTRSTABLETIME_MASK 0xff
#define CIO2_LTRCTRL_LTRSEL1S3 BIT(7)
#define CIO2_LTRCTRL_LTRSEL1S2 BIT(6)
#define CIO2_LTRCTRL_LTRSEL1S1 BIT(5)
#define CIO2_LTRCTRL_LTRSEL1S0 BIT(4)
#define CIO2_LTRCTRL_LTRSEL2S3 BIT(3)
#define CIO2_LTRCTRL_LTRSEL2S2 BIT(2)
#define CIO2_LTRCTRL_LTRSEL2S1 BIT(1)
#define CIO2_LTRCTRL_LTRSEL2S0 BIT(0)
#define CIO2_REG_LTRVAL23 0x1484
#define CIO2_REG_LTRVAL01 0x1488
#define CIO2_LTRVAL02_VAL_SHIFT 0U
#define CIO2_LTRVAL02_SCALE_SHIFT 10U
#define CIO2_LTRVAL13_VAL_SHIFT 16U
#define CIO2_LTRVAL13_SCALE_SHIFT 26U
#define CIO2_LTRVAL0_VAL 175U
/* Value times 1024 ns */
#define CIO2_LTRVAL0_SCALE 2U
#define CIO2_LTRVAL1_VAL 90U
#define CIO2_LTRVAL1_SCALE 2U
#define CIO2_LTRVAL2_VAL 90U
#define CIO2_LTRVAL2_SCALE 2U
#define CIO2_LTRVAL3_VAL 90U
#define CIO2_LTRVAL3_SCALE 2U
#define CIO2_REG_CDMABA(n) (0x1500 + 0x10 * (n)) /* n = 0..19 */
#define CIO2_REG_CDMARI(n) (0x1504 + 0x10 * (n))
#define CIO2_CDMARI_FBPT_RP_SHIFT 0U
#define CIO2_CDMARI_FBPT_RP_MASK 0xff
#define CIO2_REG_CDMAC0(n) (0x1508 + 0x10 * (n))
#define CIO2_CDMAC0_FBPT_LEN_SHIFT 0U
#define CIO2_CDMAC0_FBPT_WIDTH_SHIFT 8U
#define CIO2_CDMAC0_FBPT_NS BIT(25)
#define CIO2_CDMAC0_DMA_INTR_ON_FS BIT(26)
#define CIO2_CDMAC0_DMA_INTR_ON_FE BIT(27)
#define CIO2_CDMAC0_FBPT_UPDATE_FIFO_FULL BIT(28)
#define CIO2_CDMAC0_FBPT_FIFO_FULL_FIX_DIS BIT(29)
#define CIO2_CDMAC0_DMA_EN BIT(30)
#define CIO2_CDMAC0_DMA_HALTED BIT(31)
#define CIO2_REG_CDMAC1(n) (0x150c + 0x10 * (n))
#define CIO2_CDMAC1_LINENUMINT_SHIFT 0U
#define CIO2_CDMAC1_LINENUMUPDATE_SHIFT 16U
/* n = 0..3 */
#define CIO2_REG_PXM_PXF_FMT_CFG0(n) (0x1700 + 0x30 * (n))
#define CIO2_PXM_PXF_FMT_CFG_SID0_SHIFT 0U
#define CIO2_PXM_PXF_FMT_CFG_SID1_SHIFT 16U
#define CIO2_PXM_PXF_FMT_CFG_PCK_64B (0 << 0)
#define CIO2_PXM_PXF_FMT_CFG_PCK_32B (1 << 0)
#define CIO2_PXM_PXF_FMT_CFG_BPP_08 (0 << 2)
#define CIO2_PXM_PXF_FMT_CFG_BPP_10 (1 << 2)
#define CIO2_PXM_PXF_FMT_CFG_BPP_12 (2 << 2)
#define CIO2_PXM_PXF_FMT_CFG_BPP_14 (3 << 2)
#define CIO2_PXM_PXF_FMT_CFG_SPEC_4PPC (0 << 4)
#define CIO2_PXM_PXF_FMT_CFG_SPEC_3PPC_RGBA (1 << 4)
#define CIO2_PXM_PXF_FMT_CFG_SPEC_3PPC_ARGB (2 << 4)
#define CIO2_PXM_PXF_FMT_CFG_SPEC_PLANAR2 (3 << 4)
#define CIO2_PXM_PXF_FMT_CFG_SPEC_PLANAR3 (4 << 4)
#define CIO2_PXM_PXF_FMT_CFG_SPEC_NV16 (5 << 4)
#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_1ST_AB (1 << 7)
#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_1ST_CD (1 << 8)
#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_2ND_AC (1 << 9)
#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_2ND_BD (1 << 10)
#define CIO2_REG_INT_STS_EXT_IE 0x17e4
#define CIO2_REG_INT_EN_EXT_IE 0x17e8
#define CIO2_INT_EXT_IE_ECC_RE(n) (0x01 << (8U * (n)))
#define CIO2_INT_EXT_IE_DPHY_NR(n) (0x02 << (8U * (n)))
#define CIO2_INT_EXT_IE_ECC_NR(n) (0x04 << (8U * (n)))
#define CIO2_INT_EXT_IE_CRCERR(n) (0x08 << (8U * (n)))
#define CIO2_INT_EXT_IE_INTERFRAMEDATA(n) (0x10 << (8U * (n)))
#define CIO2_INT_EXT_IE_PKT2SHORT(n) (0x20 << (8U * (n)))
#define CIO2_INT_EXT_IE_PKT2LONG(n) (0x40 << (8U * (n)))
#define CIO2_INT_EXT_IE_IRQ(n) (0x80 << (8U * (n)))
#define CIO2_REG_PXM_FRF_CFG(n) (0x1720 + 0x30 * (n))
#define CIO2_PXM_FRF_CFG_FNSEL BIT(0)
#define CIO2_PXM_FRF_CFG_FN_RST BIT(1)
#define CIO2_PXM_FRF_CFG_ABORT BIT(2)
#define CIO2_PXM_FRF_CFG_CRC_TH_SHIFT 3U
#define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NR BIT(8)
#define CIO2_PXM_FRF_CFG_MSK_ECC_RE BIT(9)
#define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NE BIT(10)
#define CIO2_PXM_FRF_CFG_EVEN_ODD_MODE_SHIFT 11U
#define CIO2_PXM_FRF_CFG_MASK_CRC_THRES BIT(13)
#define CIO2_PXM_FRF_CFG_MASK_CSI_ACCEPT BIT(14)
#define CIO2_PXM_FRF_CFG_CIOHC_FS_MODE BIT(15)
#define CIO2_PXM_FRF_CFG_CIOHC_FRST_FRM_SHIFT 16U
#define CIO2_REG_PXM_SID2BID0(n) (0x1724 + 0x30 * (n))
#define CIO2_FB_HPLL_FREQ 0x2
#define CIO2_ISCLK_RATIO 0xc
#define CIO2_IRQCTRL_MASK 0x3ffff
#define CIO2_INT_EN_EXT_OE_MASK 0x8f0fffff
#define CIO2_CGC_CLKGATE_HOLDOFF 3U
#define CIO2_CGC_CSI_CLKGATE_HOLDOFF 5U
#define CIO2_PXM_FRF_CFG_CRC_TH 16
#define CIO2_INT_EN_EXT_IE_MASK 0xffffffff
#define CIO2_DMA_CHAN 0U
#define CIO2_CSIRX_DLY_CNT_CLANE_IDX -1
#define CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_A 0
#define CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_B 0
#define CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_A 95
#define CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_B -8
#define CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_A 0
#define CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_B 0
#define CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_A 85
#define CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_B -2
#define CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT 0x4
#define CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT 0x570
struct cio2_csi2_timing {
s32 clk_termen;
s32 clk_settle;
s32 dat_termen;
s32 dat_settle;
};
struct cio2_buffer {
struct vb2_v4l2_buffer vbb;
u32 *lop[CIO2_MAX_LOPS];
dma_addr_t lop_bus_addr[CIO2_MAX_LOPS];
unsigned int offset;
};
#define to_cio2_buffer(vb) container_of(vb, struct cio2_buffer, vbb.vb2_buf)
struct csi2_bus_info {
u32 port;
u32 lanes;
};
struct cio2_queue {
/* mutex to be used by vb2_queue */
struct mutex lock;
struct media_pipeline pipe;
struct csi2_bus_info csi2;
struct v4l2_subdev *sensor;
void __iomem *csi_rx_base;
/* Subdev, /dev/v4l-subdevX */
struct v4l2_subdev subdev;
struct media_pad subdev_pads[CIO2_PADS];
atomic_t frame_sequence;
/* Video device, /dev/videoX */
struct video_device vdev;
struct media_pad vdev_pad;
struct v4l2_pix_format_mplane format;
struct vb2_queue vbq;
/* Buffer queue handling */
struct cio2_fbpt_entry *fbpt; /* Frame buffer pointer table */
dma_addr_t fbpt_bus_addr;
struct cio2_buffer *bufs[CIO2_MAX_BUFFERS];
unsigned int bufs_first; /* Index of the first used entry */
unsigned int bufs_next; /* Index of the first unused entry */
atomic_t bufs_queued;
};
struct cio2_device {
struct pci_dev *pci_dev;
void __iomem *base;
struct v4l2_device v4l2_dev;
struct cio2_queue queue[CIO2_QUEUES];
struct cio2_queue *cur_queue;
/* mutex to be used by video_device */
struct mutex lock;
bool streaming;
struct v4l2_async_notifier notifier;
struct media_device media_dev;
/*
* Safety net to catch DMA fetch ahead
* when reaching the end of LOP
*/
void *dummy_page;
/* DMA handle of dummy_page */
dma_addr_t dummy_page_bus_addr;
/* single List of Pointers (LOP) page */
u32 *dummy_lop;
/* DMA handle of dummy_lop */
dma_addr_t dummy_lop_bus_addr;
};
#define to_cio2_device(n) container_of(n, struct cio2_device, notifier)
/**************** Virtual channel ****************/
/*
* This should come from sensor driver. No
* driver interface nor requirement yet.
*/
#define SENSOR_VIR_CH_DFLT 0
/**************** FBPT operations ****************/
#define CIO2_FBPT_SIZE (CIO2_MAX_BUFFERS * CIO2_MAX_LOPS * \
sizeof(struct cio2_fbpt_entry))
#define CIO2_FBPT_SUBENTRY_UNIT 4
/* cio2 fbpt first_entry ctrl status */
#define CIO2_FBPT_CTRL_VALID BIT(0)
#define CIO2_FBPT_CTRL_IOC BIT(1)
#define CIO2_FBPT_CTRL_IOS BIT(2)
#define CIO2_FBPT_CTRL_SUCCXFAIL BIT(3)
#define CIO2_FBPT_CTRL_CMPLCODE_SHIFT 4
/*
* Frame Buffer Pointer Table(FBPT) entry
* each entry describe an output buffer and consists of
* several sub-entries
*/
struct __packed cio2_fbpt_entry {
union {
struct __packed {
u32 ctrl; /* status ctrl */
u16 cur_line_num; /* current line # written to DDR */
u16 frame_num; /* updated by DMA upon FE */
u32 first_page_offset; /* offset for 1st page in LOP */
} first_entry;
/* Second entry per buffer */
struct __packed {
u32 timestamp;
u32 num_of_bytes;
/* the number of bytes for write on last page */
u16 last_page_available_bytes;
/* the number of pages allocated for this buf */
u16 num_of_pages;
} second_entry;
};
u32 lop_page_addr; /* Points to list of pointers (LOP) table */
};
static inline struct cio2_queue *file_to_cio2_queue(struct file *file)
{
return container_of(video_devdata(file), struct cio2_queue, vdev);
}
static inline struct cio2_queue *vb2q_to_cio2_queue(struct vb2_queue *vq)
{
return container_of(vq, struct cio2_queue, vbq);
}
#endif
@@ -0,0 +1,18 @@
config VIDEO_INTEL_IPU6
tristate "Intel IPU6 driver"
depends on ACPI || COMPILE_TEST
depends on VIDEO_DEV
depends on X86 && HAS_DMA
depends on IPU_BRIDGE || !IPU_BRIDGE
select AUXILIARY_BUS
select IOMMU_IOVA
select VIDEO_V4L2_SUBDEV_API
select MEDIA_CONTROLLER
select VIDEOBUF2_DMA_SG
select V4L2_FWNODE
help
This is the 6th Gen Intel Image Processing Unit, found in Intel SoCs
and used for capturing images and video from camera sensors.
To compile this driver, say Y here! It contains 2 modules -
intel_ipu6 and intel_ipu6_isys.
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: GPL-2.0-only
intel-ipu6-y := ipu6.o \
ipu6-bus.o \
ipu6-dma.o \
ipu6-mmu.o \
ipu6-buttress.o \
ipu6-cpd.o \
ipu6-fw-com.o
obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6.o
intel-ipu6-isys-y := ipu6-isys.o \
ipu6-isys-csi2.o \
ipu6-fw-isys.o \
ipu6-isys-video.o \
ipu6-isys-queue.o \
ipu6-isys-subdev.o \
ipu6-isys-mcd-phy.o \
ipu6-isys-jsl-phy.o \
ipu6-isys-dwc-phy.o
obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 - 2024 Intel Corporation
*/
#include <linux/auxiliary_bus.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-buttress.h"
#include "ipu6-dma.h"
static int bus_pm_runtime_suspend(struct device *dev)
{
struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
int ret;
ret = pm_generic_runtime_suspend(dev);
if (ret)
return ret;
ret = ipu6_buttress_power(dev, adev->ctrl, false);
if (!ret)
return 0;
dev_err(dev, "power down failed!\n");
/* Powering down failed, attempt to resume device now */
ret = pm_generic_runtime_resume(dev);
if (!ret)
return -EBUSY;
return -EIO;
}
static int bus_pm_runtime_resume(struct device *dev)
{
struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
int ret;
ret = ipu6_buttress_power(dev, adev->ctrl, true);
if (ret)
return ret;
ret = pm_generic_runtime_resume(dev);
if (ret)
goto out_err;
return 0;
out_err:
ipu6_buttress_power(dev, adev->ctrl, false);
return -EBUSY;
}
static struct dev_pm_domain ipu6_bus_pm_domain = {
.ops = {
.runtime_suspend = bus_pm_runtime_suspend,
.runtime_resume = bus_pm_runtime_resume,
},
};
static DEFINE_MUTEX(ipu6_bus_mutex);
static void ipu6_bus_release(struct device *dev)
{
struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
kfree(adev->pdata);
kfree(adev);
}
struct ipu6_bus_device *
ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent,
void *pdata, const struct ipu6_buttress_ctrl *ctrl,
char *name)
{
struct auxiliary_device *auxdev;
struct ipu6_bus_device *adev;
struct ipu6_device *isp = pci_get_drvdata(pdev);
int ret;
adev = kzalloc_obj(*adev);
if (!adev)
return ERR_PTR(-ENOMEM);
adev->isp = isp;
adev->ctrl = ctrl;
adev->pdata = pdata;
auxdev = &adev->auxdev;
auxdev->name = name;
auxdev->id = (pci_domain_nr(pdev->bus) << 16) |
PCI_DEVID(pdev->bus->number, pdev->devfn);
auxdev->dev.parent = parent;
auxdev->dev.release = ipu6_bus_release;
ret = auxiliary_device_init(auxdev);
if (ret < 0) {
dev_err(&isp->pdev->dev, "auxiliary device init failed (%d)\n",
ret);
kfree(adev);
return ERR_PTR(ret);
}
dev_pm_domain_set(&auxdev->dev, &ipu6_bus_pm_domain);
pm_runtime_forbid(&adev->auxdev.dev);
pm_runtime_enable(&adev->auxdev.dev);
return adev;
}
int ipu6_bus_add_device(struct ipu6_bus_device *adev)
{
struct auxiliary_device *auxdev = &adev->auxdev;
int ret;
ret = auxiliary_device_add(auxdev);
if (ret) {
auxiliary_device_uninit(auxdev);
return ret;
}
mutex_lock(&ipu6_bus_mutex);
list_add(&adev->list, &adev->isp->devices);
mutex_unlock(&ipu6_bus_mutex);
pm_runtime_allow(&auxdev->dev);
return 0;
}
void ipu6_bus_del_devices(struct pci_dev *pdev)
{
struct ipu6_device *isp = pci_get_drvdata(pdev);
struct ipu6_bus_device *adev, *save;
mutex_lock(&ipu6_bus_mutex);
list_for_each_entry_safe(adev, save, &isp->devices, list) {
pm_runtime_disable(&adev->auxdev.dev);
list_del(&adev->list);
auxiliary_device_delete(&adev->auxdev);
auxiliary_device_uninit(&adev->auxdev);
}
mutex_unlock(&ipu6_bus_mutex);
}
@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013 - 2024 Intel Corporation */
#ifndef IPU6_BUS_H
#define IPU6_BUS_H
#include <linux/auxiliary_bus.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/irqreturn.h>
#include <linux/list.h>
#include <linux/scatterlist.h>
#include <linux/types.h>
struct firmware;
struct pci_dev;
struct ipu6_buttress_ctrl;
struct ipu6_bus_device {
struct auxiliary_device auxdev;
const struct auxiliary_driver *auxdrv;
const struct ipu6_auxdrv_data *auxdrv_data;
struct list_head list;
void *pdata;
struct ipu6_mmu *mmu;
struct ipu6_device *isp;
const struct ipu6_buttress_ctrl *ctrl;
const struct firmware *fw;
struct sg_table fw_sgt;
u64 *pkg_dir;
dma_addr_t pkg_dir_dma_addr;
unsigned int pkg_dir_size;
};
struct ipu6_auxdrv_data {
irqreturn_t (*isr)(struct ipu6_bus_device *adev);
irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev);
bool wake_isr_thread;
};
#define to_ipu6_bus_device(_dev) \
container_of(to_auxiliary_dev(_dev), struct ipu6_bus_device, auxdev)
#define auxdev_to_adev(_auxdev) \
container_of(_auxdev, struct ipu6_bus_device, auxdev)
#define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev)
struct ipu6_bus_device *
ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent,
void *pdata, const struct ipu6_buttress_ctrl *ctrl,
char *name);
int ipu6_bus_add_device(struct ipu6_bus_device *adev);
void ipu6_bus_del_devices(struct pci_dev *pdev);
#endif
@@ -0,0 +1,910 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/firmware.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/math64.h>
#include <linux/mm.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pfn.h>
#include <linux/pm_runtime.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/time64.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-dma.h"
#include "ipu6-buttress.h"
#include "ipu6-platform-buttress-regs.h"
#define BOOTLOADER_STATUS_OFFSET 0x15c
#define BOOTLOADER_MAGIC_KEY 0xb00710ad
#define ENTRY BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1
#define EXIT BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2
#define QUERY BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE
#define BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX 10
#define BUTTRESS_POWER_TIMEOUT_US (200 * USEC_PER_MSEC)
#define BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US (5 * USEC_PER_SEC)
#define BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US (10 * USEC_PER_SEC)
#define BUTTRESS_CSE_FWRESET_TIMEOUT_US (100 * USEC_PER_MSEC)
#define BUTTRESS_IPC_TX_TIMEOUT_MS MSEC_PER_SEC
#define BUTTRESS_IPC_RX_TIMEOUT_MS MSEC_PER_SEC
#define BUTTRESS_IPC_VALIDITY_TIMEOUT_US (1 * USEC_PER_SEC)
#define BUTTRESS_TSC_SYNC_TIMEOUT_US (5 * USEC_PER_MSEC)
#define BUTTRESS_IPC_RESET_RETRY 2000
#define BUTTRESS_CSE_IPC_RESET_RETRY 4
#define BUTTRESS_IPC_CMD_SEND_RETRY 1
#define BUTTRESS_MAX_CONSECUTIVE_IRQS 100
static const u32 ipu6_adev_irq_mask[2] = {
BUTTRESS_ISR_IS_IRQ,
BUTTRESS_ISR_PS_IRQ
};
int ipu6_buttress_ipc_reset(struct ipu6_device *isp,
struct ipu6_buttress_ipc *ipc)
{
unsigned int retries = BUTTRESS_IPC_RESET_RETRY;
struct ipu6_buttress *b = &isp->buttress;
u32 val = 0, csr_in_clr;
if (!isp->secure_mode) {
dev_dbg(&isp->pdev->dev, "Skip IPC reset for non-secure mode");
return 0;
}
mutex_lock(&b->ipc_mutex);
/* Clear-by-1 CSR (all bits), corresponding internal states. */
val = readl(isp->base + ipc->csr_in);
writel(val, isp->base + ipc->csr_in);
/* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */
writel(ENTRY, isp->base + ipc->csr_out);
/*
* Clear-by-1 all CSR bits EXCEPT following
* bits:
* A. IPC_PEER_COMP_ACTIONS_RST_PHASE1.
* B. IPC_PEER_COMP_ACTIONS_RST_PHASE2.
* C. Possibly custom bits, depending on
* their role.
*/
csr_in_clr = BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ |
BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID |
BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ | QUERY;
do {
usleep_range(400, 500);
val = readl(isp->base + ipc->csr_in);
switch (val) {
case ENTRY | EXIT:
case ENTRY | EXIT | QUERY:
/*
* 1) Clear-by-1 CSR bits
* (IPC_PEER_COMP_ACTIONS_RST_PHASE1,
* IPC_PEER_COMP_ACTIONS_RST_PHASE2).
* 2) Set peer CSR bit
* IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE.
*/
writel(ENTRY | EXIT, isp->base + ipc->csr_in);
writel(QUERY, isp->base + ipc->csr_out);
break;
case ENTRY:
case ENTRY | QUERY:
/*
* 1) Clear-by-1 CSR bits
* (IPC_PEER_COMP_ACTIONS_RST_PHASE1,
* IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE).
* 2) Set peer CSR bit
* IPC_PEER_COMP_ACTIONS_RST_PHASE1.
*/
writel(ENTRY | QUERY, isp->base + ipc->csr_in);
writel(ENTRY, isp->base + ipc->csr_out);
break;
case EXIT:
case EXIT | QUERY:
/*
* Clear-by-1 CSR bit
* IPC_PEER_COMP_ACTIONS_RST_PHASE2.
* 1) Clear incoming doorbell.
* 2) Clear-by-1 all CSR bits EXCEPT following
* bits:
* A. IPC_PEER_COMP_ACTIONS_RST_PHASE1.
* B. IPC_PEER_COMP_ACTIONS_RST_PHASE2.
* C. Possibly custom bits, depending on
* their role.
* 3) Set peer CSR bit
* IPC_PEER_COMP_ACTIONS_RST_PHASE2.
*/
writel(EXIT, isp->base + ipc->csr_in);
writel(0, isp->base + ipc->db0_in);
writel(csr_in_clr, isp->base + ipc->csr_in);
writel(EXIT, isp->base + ipc->csr_out);
/*
* Read csr_in again to make sure if RST_PHASE2 is done.
* If csr_in is QUERY, it should be handled again.
*/
usleep_range(200, 300);
val = readl(isp->base + ipc->csr_in);
if (val & QUERY) {
dev_dbg(&isp->pdev->dev,
"RST_PHASE2 retry csr_in = %x\n", val);
break;
}
mutex_unlock(&b->ipc_mutex);
return 0;
case QUERY:
/*
* 1) Clear-by-1 CSR bit
* IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE.
* 2) Set peer CSR bit
* IPC_PEER_COMP_ACTIONS_RST_PHASE1
*/
writel(QUERY, isp->base + ipc->csr_in);
writel(ENTRY, isp->base + ipc->csr_out);
break;
default:
dev_dbg_ratelimited(&isp->pdev->dev,
"Unexpected CSR 0x%x\n", val);
break;
}
} while (retries--);
mutex_unlock(&b->ipc_mutex);
dev_err(&isp->pdev->dev, "Timed out while waiting for CSE\n");
return -ETIMEDOUT;
}
static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp,
struct ipu6_buttress_ipc *ipc)
{
writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ,
isp->base + ipc->csr_out);
}
static int
ipu6_buttress_ipc_validity_open(struct ipu6_device *isp,
struct ipu6_buttress_ipc *ipc)
{
unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID;
void __iomem *addr;
int ret;
u32 val;
writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ,
isp->base + ipc->csr_out);
addr = isp->base + ipc->csr_in;
ret = readl_poll_timeout(addr, val, val & mask, 200,
BUTTRESS_IPC_VALIDITY_TIMEOUT_US);
if (ret) {
dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val);
ipu6_buttress_ipc_validity_close(isp, ipc);
}
return ret;
}
static void ipu6_buttress_ipc_recv(struct ipu6_device *isp,
struct ipu6_buttress_ipc *ipc, u32 *ipc_msg)
{
if (ipc_msg)
*ipc_msg = readl(isp->base + ipc->data0_in);
writel(0, isp->base + ipc->db0_in);
}
static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp,
struct ipu6_ipc_buttress_bulk_msg *msgs,
u32 size)
{
unsigned long tx_timeout_jiffies, rx_timeout_jiffies;
unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY;
struct ipu6_buttress *b = &isp->buttress;
struct ipu6_buttress_ipc *ipc = &b->cse;
u32 val;
int ret;
int tout;
mutex_lock(&b->ipc_mutex);
ret = ipu6_buttress_ipc_validity_open(isp, ipc);
if (ret) {
dev_err(&isp->pdev->dev, "IPC validity open failed\n");
goto out;
}
tx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_TX_TIMEOUT_MS);
rx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_RX_TIMEOUT_MS);
for (i = 0; i < size; i++) {
reinit_completion(&ipc->send_complete);
if (msgs[i].require_resp)
reinit_completion(&ipc->recv_complete);
dev_dbg(&isp->pdev->dev, "bulk IPC command: 0x%x\n",
msgs[i].cmd);
writel(msgs[i].cmd, isp->base + ipc->data0_out);
val = BUTTRESS_IU2CSEDB0_BUSY | msgs[i].cmd_size;
writel(val, isp->base + ipc->db0_out);
tout = wait_for_completion_timeout(&ipc->send_complete,
tx_timeout_jiffies);
if (!tout) {
dev_err(&isp->pdev->dev, "send IPC response timeout\n");
if (!retry--) {
ret = -ETIMEDOUT;
goto out;
}
/* Try again if CSE is not responding on first try */
writel(0, isp->base + ipc->db0_out);
i--;
continue;
}
retry = BUTTRESS_IPC_CMD_SEND_RETRY;
if (!msgs[i].require_resp)
continue;
tout = wait_for_completion_timeout(&ipc->recv_complete,
rx_timeout_jiffies);
if (!tout) {
dev_err(&isp->pdev->dev, "recv IPC response timeout\n");
ret = -ETIMEDOUT;
goto out;
}
if (ipc->nack_mask &&
(ipc->recv_data & ipc->nack_mask) == ipc->nack) {
dev_err(&isp->pdev->dev,
"IPC NACK for cmd 0x%x\n", msgs[i].cmd);
ret = -EIO;
goto out;
}
if (ipc->recv_data != msgs[i].expected_resp) {
dev_err(&isp->pdev->dev,
"expected resp: 0x%x, IPC response: 0x%x ",
msgs[i].expected_resp, ipc->recv_data);
ret = -EIO;
goto out;
}
}
dev_dbg(&isp->pdev->dev, "bulk IPC commands done\n");
out:
ipu6_buttress_ipc_validity_close(isp, ipc);
mutex_unlock(&b->ipc_mutex);
return ret;
}
static int
ipu6_buttress_ipc_send(struct ipu6_device *isp,
u32 ipc_msg, u32 size, bool require_resp,
u32 expected_resp)
{
struct ipu6_ipc_buttress_bulk_msg msg = {
.cmd = ipc_msg,
.cmd_size = size,
.require_resp = require_resp,
.expected_resp = expected_resp,
};
return ipu6_buttress_ipc_send_bulk(isp, &msg, 1);
}
static irqreturn_t ipu6_buttress_call_isr(struct ipu6_bus_device *adev)
{
irqreturn_t ret = IRQ_WAKE_THREAD;
if (!adev || !adev->auxdrv || !adev->auxdrv_data)
return IRQ_NONE;
if (adev->auxdrv_data->isr)
ret = adev->auxdrv_data->isr(adev);
if (ret == IRQ_WAKE_THREAD && !adev->auxdrv_data->isr_threaded)
ret = IRQ_NONE;
return ret;
}
irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr)
{
struct ipu6_device *isp = isp_ptr;
struct ipu6_bus_device *adev[] = { isp->isys, isp->psys };
struct ipu6_buttress *b = &isp->buttress;
u32 reg_irq_sts = BUTTRESS_REG_ISR_STATUS;
irqreturn_t ret = IRQ_NONE;
u32 disable_irqs = 0;
u32 irq_status;
u32 i, count = 0;
int active;
active = pm_runtime_get_if_active(&isp->pdev->dev);
if (!active)
return IRQ_NONE;
irq_status = readl(isp->base + reg_irq_sts);
if (irq_status == 0 || WARN_ON_ONCE(irq_status == 0xffffffffu)) {
if (active > 0)
pm_runtime_put_noidle(&isp->pdev->dev);
return IRQ_NONE;
}
do {
writel(irq_status, isp->base + BUTTRESS_REG_ISR_CLEAR);
for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask); i++) {
irqreturn_t r = ipu6_buttress_call_isr(adev[i]);
if (!(irq_status & ipu6_adev_irq_mask[i]))
continue;
if (r == IRQ_WAKE_THREAD) {
ret = IRQ_WAKE_THREAD;
disable_irqs |= ipu6_adev_irq_mask[i];
} else if (ret == IRQ_NONE && r == IRQ_HANDLED) {
ret = IRQ_HANDLED;
}
}
if ((irq_status & BUTTRESS_EVENT) && ret == IRQ_NONE)
ret = IRQ_HANDLED;
if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) {
dev_dbg(&isp->pdev->dev,
"BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n");
ipu6_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data);
complete(&b->cse.recv_complete);
}
if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) {
dev_dbg(&isp->pdev->dev,
"BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n");
complete(&b->cse.send_complete);
}
if (irq_status & BUTTRESS_ISR_SAI_VIOLATION &&
ipu6_buttress_get_secure_mode(isp))
dev_err(&isp->pdev->dev,
"BUTTRESS_ISR_SAI_VIOLATION\n");
if (irq_status & (BUTTRESS_ISR_IS_FATAL_MEM_ERR |
BUTTRESS_ISR_PS_FATAL_MEM_ERR))
dev_err(&isp->pdev->dev,
"BUTTRESS_ISR_FATAL_MEM_ERR\n");
if (irq_status & BUTTRESS_ISR_UFI_ERROR)
dev_err(&isp->pdev->dev, "BUTTRESS_ISR_UFI_ERROR\n");
if (++count == BUTTRESS_MAX_CONSECUTIVE_IRQS) {
dev_err(&isp->pdev->dev, "too many consecutive IRQs\n");
ret = IRQ_NONE;
break;
}
irq_status = readl(isp->base + reg_irq_sts);
} while (irq_status);
if (disable_irqs)
writel(BUTTRESS_IRQS & ~disable_irqs,
isp->base + BUTTRESS_REG_ISR_ENABLE);
if (active > 0)
pm_runtime_put(&isp->pdev->dev);
return ret;
}
irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr)
{
struct ipu6_device *isp = isp_ptr;
struct ipu6_bus_device *adev[] = { isp->isys, isp->psys };
const struct ipu6_auxdrv_data *drv_data = NULL;
irqreturn_t ret = IRQ_NONE;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask) && adev[i]; i++) {
drv_data = adev[i]->auxdrv_data;
if (!drv_data)
continue;
if (drv_data->wake_isr_thread &&
drv_data->isr_threaded(adev[i]) == IRQ_HANDLED)
ret = IRQ_HANDLED;
}
writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE);
return ret;
}
int ipu6_buttress_power(struct device *dev,
const struct ipu6_buttress_ctrl *ctrl, bool on)
{
struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp;
u32 pwr_sts, val;
int ret;
if (!ctrl)
return 0;
mutex_lock(&isp->buttress.power_mutex);
if (!on) {
val = 0;
pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift;
} else {
val = BUTTRESS_FREQ_CTL_START |
FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK,
ctrl->ratio) |
FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK,
ctrl->qos_floor) |
BUTTRESS_FREQ_CTL_ICCMAX_LEVEL;
pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift;
}
writel(val, isp->base + ctrl->freq_ctl);
ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE,
val, (val & ctrl->pwr_sts_mask) == pwr_sts,
100, BUTTRESS_POWER_TIMEOUT_US);
if (ret)
dev_err(&isp->pdev->dev,
"Change power status timeout with 0x%x\n", val);
mutex_unlock(&isp->buttress.power_mutex);
return ret;
}
bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp)
{
u32 val;
val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL);
return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE;
}
bool ipu6_buttress_auth_done(struct ipu6_device *isp)
{
u32 val;
if (!isp->secure_mode)
return true;
val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL);
val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val);
return val == BUTTRESS_SECURITY_CTL_AUTH_DONE;
}
EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, "INTEL_IPU6");
int ipu6_buttress_reset_authentication(struct ipu6_device *isp)
{
int ret;
u32 val;
if (!isp->secure_mode) {
dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n");
return 0;
}
writel(BUTTRESS_FW_RESET_CTL_START, isp->base +
BUTTRESS_REG_FW_RESET_CTL);
ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val,
val & BUTTRESS_FW_RESET_CTL_DONE, 500,
BUTTRESS_CSE_FWRESET_TIMEOUT_US);
if (ret) {
dev_err(&isp->pdev->dev,
"Time out while resetting authentication state\n");
return ret;
}
dev_dbg(&isp->pdev->dev, "FW reset for authentication done\n");
writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL);
/* leave some time for HW restore */
usleep_range(800, 1000);
return 0;
}
int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys,
const struct firmware *fw, struct sg_table *sgt)
{
bool is_vmalloc = is_vmalloc_addr(fw->data);
struct pci_dev *pdev = sys->isp->pdev;
struct page **pages;
const void *addr;
unsigned long n_pages;
unsigned int i;
int ret;
if (!is_vmalloc && !virt_addr_valid(fw->data))
return -EDOM;
n_pages = PFN_UP(fw->size);
pages = kmalloc_objs(*pages, n_pages);
if (!pages)
return -ENOMEM;
addr = fw->data;
for (i = 0; i < n_pages; i++) {
struct page *p = is_vmalloc ?
vmalloc_to_page(addr) : virt_to_page(addr);
if (!p) {
ret = -ENOMEM;
goto out;
}
pages[i] = p;
addr += PAGE_SIZE;
}
ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, fw->size,
GFP_KERNEL);
if (ret) {
ret = -ENOMEM;
goto out;
}
ret = dma_map_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0);
if (ret) {
sg_free_table(sgt);
goto out;
}
ret = ipu6_dma_map_sgtable(sys, sgt, DMA_TO_DEVICE, 0);
if (ret) {
dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0);
sg_free_table(sgt);
goto out;
}
ipu6_dma_sync_sgtable(sys, sgt);
out:
kfree(pages);
return ret;
}
EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, "INTEL_IPU6");
void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys,
struct sg_table *sgt)
{
struct pci_dev *pdev = sys->isp->pdev;
ipu6_dma_unmap_sgtable(sys, sgt, DMA_TO_DEVICE, 0);
dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0);
sg_free_table(sgt);
}
EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, "INTEL_IPU6");
int ipu6_buttress_authenticate(struct ipu6_device *isp)
{
struct ipu6_buttress *b = &isp->buttress;
struct ipu6_psys_pdata *psys_pdata;
u32 data, mask, done, fail;
int ret;
if (!isp->secure_mode) {
dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n");
return 0;
}
psys_pdata = isp->psys->pdata;
mutex_lock(&b->auth_mutex);
if (ipu6_buttress_auth_done(isp)) {
ret = 0;
goto out_unlock;
}
/*
* Write address of FIT table to FW_SOURCE register
* Let's use fw address. I.e. not using FIT table yet
*/
data = lower_32_bits(isp->psys->pkg_dir_dma_addr);
writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO);
data = upper_32_bits(isp->psys->pkg_dir_dma_addr);
writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI);
/*
* Write boot_load into IU2CSEDATA0
* Write sizeof(boot_load) | 0x2 << CLIENT_ID to
* IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as
*/
dev_info(&isp->pdev->dev, "Sending BOOT_LOAD to CSE\n");
ret = ipu6_buttress_ipc_send(isp,
BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD,
1, true,
BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE);
if (ret) {
dev_err(&isp->pdev->dev, "CSE boot_load failed\n");
goto out_unlock;
}
mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK;
done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE;
fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED;
ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data,
((data & mask) == done ||
(data & mask) == fail), 500,
BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US);
if (ret) {
dev_err(&isp->pdev->dev, "CSE boot_load timeout\n");
goto out_unlock;
}
if ((data & mask) == fail) {
dev_err(&isp->pdev->dev, "CSE auth failed\n");
ret = -EINVAL;
goto out_unlock;
}
ret = readl_poll_timeout(psys_pdata->base + BOOTLOADER_STATUS_OFFSET,
data, data == BOOTLOADER_MAGIC_KEY, 500,
BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US);
if (ret) {
dev_err(&isp->pdev->dev, "Unexpected magic number 0x%x\n",
data);
goto out_unlock;
}
/*
* Write authenticate_run into IU2CSEDATA0
* Write sizeof(boot_load) | 0x2 << CLIENT_ID to
* IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as
*/
dev_info(&isp->pdev->dev, "Sending AUTHENTICATE_RUN to CSE\n");
ret = ipu6_buttress_ipc_send(isp,
BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN,
1, true,
BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE);
if (ret) {
dev_err(&isp->pdev->dev, "CSE authenticate_run failed\n");
goto out_unlock;
}
done = BUTTRESS_SECURITY_CTL_AUTH_DONE;
ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data,
((data & mask) == done ||
(data & mask) == fail), 500,
BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US);
if (ret) {
dev_err(&isp->pdev->dev, "CSE authenticate timeout\n");
goto out_unlock;
}
if ((data & mask) == fail) {
dev_err(&isp->pdev->dev, "CSE boot_load failed\n");
ret = -EINVAL;
goto out_unlock;
}
dev_info(&isp->pdev->dev, "CSE authenticate_run done\n");
out_unlock:
mutex_unlock(&b->auth_mutex);
return ret;
}
static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp)
{
u32 val, mask, done;
int ret;
mask = BUTTRESS_PWR_STATE_HH_STATUS_MASK;
writel(BUTTRESS_FABRIC_CMD_START_TSC_SYNC,
isp->base + BUTTRESS_REG_FABRIC_CMD);
val = readl(isp->base + BUTTRESS_REG_PWR_STATE);
val = FIELD_GET(mask, val);
if (val == BUTTRESS_PWR_STATE_HH_STATE_ERR) {
dev_err(&isp->pdev->dev, "Start tsc sync failed\n");
return -EINVAL;
}
done = BUTTRESS_PWR_STATE_HH_STATE_DONE;
ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, val,
FIELD_GET(mask, val) == done, 500,
BUTTRESS_TSC_SYNC_TIMEOUT_US);
if (ret)
dev_err(&isp->pdev->dev, "Start tsc sync timeout\n");
return ret;
}
int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp)
{
unsigned int i;
for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) {
u32 val;
int ret;
ret = ipu6_buttress_send_tsc_request(isp);
if (ret != -ETIMEDOUT)
return ret;
val = readl(isp->base + BUTTRESS_REG_TSW_CTL);
val = val | BUTTRESS_TSW_CTL_SOFT_RESET;
writel(val, isp->base + BUTTRESS_REG_TSW_CTL);
val = val & ~BUTTRESS_TSW_CTL_SOFT_RESET;
writel(val, isp->base + BUTTRESS_REG_TSW_CTL);
}
dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n");
return -ETIMEDOUT;
}
EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, "INTEL_IPU6");
void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val)
{
u32 tsc_hi_1, tsc_hi_2, tsc_lo;
unsigned long flags;
local_irq_save(flags);
tsc_hi_1 = readl(isp->base + BUTTRESS_REG_TSC_HI);
tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO);
tsc_hi_2 = readl(isp->base + BUTTRESS_REG_TSC_HI);
if (tsc_hi_1 == tsc_hi_2) {
*val = (u64)tsc_hi_1 << 32 | tsc_lo;
} else {
/* Check if TSC has rolled over */
if (tsc_lo & BIT(31))
*val = (u64)tsc_hi_1 << 32 | tsc_lo;
else
*val = (u64)tsc_hi_2 << 32 | tsc_lo;
}
local_irq_restore(flags);
}
EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, "INTEL_IPU6");
u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp)
{
u64 ns = ticks * 10000;
/*
* converting TSC tick count to ns is calculated by:
* Example (TSC clock frequency is 19.2MHz):
* ns = ticks * 1000 000 000 / 19.2Mhz
* = ticks * 1000 000 000 / 19200000Hz
* = ticks * 10000 / 192 ns
*/
return div_u64(ns, isp->buttress.ref_clk);
}
EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, "INTEL_IPU6");
void ipu6_buttress_restore(struct ipu6_device *isp)
{
struct ipu6_buttress *b = &isp->buttress;
writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR);
writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE);
writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_WDT);
}
int ipu6_buttress_init(struct ipu6_device *isp)
{
int ret, ipc_reset_retry = BUTTRESS_CSE_IPC_RESET_RETRY;
struct ipu6_buttress *b = &isp->buttress;
u32 val;
mutex_init(&b->power_mutex);
mutex_init(&b->auth_mutex);
mutex_init(&b->cons_mutex);
mutex_init(&b->ipc_mutex);
init_completion(&b->cse.send_complete);
init_completion(&b->cse.recv_complete);
b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK;
b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK;
b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR;
b->cse.csr_out = BUTTRESS_REG_IU2CSECSR;
b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0;
b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0;
b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0;
b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0;
INIT_LIST_HEAD(&b->constraints);
isp->secure_mode = ipu6_buttress_get_secure_mode(isp);
dev_dbg(&isp->pdev->dev, "IPU6 in %s mode touch 0x%x mask 0x%x\n",
isp->secure_mode ? "secure" : "non-secure",
readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH),
readl(isp->base + BUTTRESS_REG_CAMERA_MASK));
b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_WDT);
writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR);
writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE);
/* get ref_clk frequency by reading the indication in btrs control */
val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL);
val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val);
switch (val) {
case 0x0:
b->ref_clk = 240;
break;
case 0x1:
b->ref_clk = 192;
break;
case 0x2:
b->ref_clk = 384;
break;
default:
dev_warn(&isp->pdev->dev,
"Unsupported ref clock, use 19.2Mhz by default.\n");
b->ref_clk = 192;
break;
}
/* Retry couple of times in case of CSE initialization is delayed */
do {
ret = ipu6_buttress_ipc_reset(isp, &b->cse);
if (ret) {
dev_warn(&isp->pdev->dev,
"IPC reset protocol failed, retrying\n");
} else {
dev_dbg(&isp->pdev->dev, "IPC reset done\n");
return 0;
}
} while (ipc_reset_retry--);
dev_err(&isp->pdev->dev, "IPC reset protocol failed\n");
mutex_destroy(&b->power_mutex);
mutex_destroy(&b->auth_mutex);
mutex_destroy(&b->cons_mutex);
mutex_destroy(&b->ipc_mutex);
return ret;
}
void ipu6_buttress_exit(struct ipu6_device *isp)
{
struct ipu6_buttress *b = &isp->buttress;
writel(0, isp->base + BUTTRESS_REG_ISR_ENABLE);
mutex_destroy(&b->power_mutex);
mutex_destroy(&b->auth_mutex);
mutex_destroy(&b->cons_mutex);
mutex_destroy(&b->ipc_mutex);
}
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_BUTTRESS_H
#define IPU6_BUTTRESS_H
#include <linux/completion.h>
#include <linux/irqreturn.h>
#include <linux/list.h>
#include <linux/mutex.h>
struct device;
struct firmware;
struct ipu6_device;
struct ipu6_bus_device;
#define BUTTRESS_PS_FREQ_STEP 25U
#define BUTTRESS_MIN_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 8)
#define BUTTRESS_MAX_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 32)
#define BUTTRESS_IS_FREQ_STEP 25U
#define BUTTRESS_MIN_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 8)
#define BUTTRESS_MAX_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 22)
struct ipu6_buttress_ctrl {
u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off;
unsigned int ratio;
unsigned int qos_floor;
};
struct ipu6_buttress_ipc {
struct completion send_complete;
struct completion recv_complete;
u32 nack;
u32 nack_mask;
u32 recv_data;
u32 csr_out;
u32 csr_in;
u32 db0_in;
u32 db0_out;
u32 data0_out;
u32 data0_in;
};
struct ipu6_buttress {
struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex;
struct ipu6_buttress_ipc cse;
struct list_head constraints;
u32 wdt_cached_value;
bool force_suspend;
u32 ref_clk;
};
struct ipu6_ipc_buttress_bulk_msg {
u32 cmd;
u32 expected_resp;
bool require_resp;
u8 cmd_size;
};
int ipu6_buttress_ipc_reset(struct ipu6_device *isp,
struct ipu6_buttress_ipc *ipc);
int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys,
const struct firmware *fw,
struct sg_table *sgt);
void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys,
struct sg_table *sgt);
int ipu6_buttress_power(struct device *dev,
const struct ipu6_buttress_ctrl *ctrl, bool on);
bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp);
int ipu6_buttress_authenticate(struct ipu6_device *isp);
int ipu6_buttress_reset_authentication(struct ipu6_device *isp);
bool ipu6_buttress_auth_done(struct ipu6_device *isp);
int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp);
void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val);
u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp);
irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr);
irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr);
int ipu6_buttress_init(struct ipu6_device *isp);
void ipu6_buttress_exit(struct ipu6_device *isp);
void ipu6_buttress_csi_port_config(struct ipu6_device *isp,
u32 legacy, u32 combo);
void ipu6_buttress_restore(struct ipu6_device *isp);
#endif /* IPU6_BUTTRESS_H */
@@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/err.h>
#include <linux/dma-mapping.h>
#include <linux/gfp_types.h>
#include <linux/math64.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-cpd.h"
#include "ipu6-dma.h"
/* 15 entries + header*/
#define MAX_PKG_DIR_ENT_CNT 16
/* 2 qword per entry/header */
#define PKG_DIR_ENT_LEN 2
/* PKG_DIR size in bytes */
#define PKG_DIR_SIZE ((MAX_PKG_DIR_ENT_CNT) * \
(PKG_DIR_ENT_LEN) * sizeof(u64))
/* _IUPKDR_ */
#define PKG_DIR_HDR_MARK 0x5f4955504b44525fULL
/* $CPD */
#define CPD_HDR_MARK 0x44504324
#define MAX_MANIFEST_SIZE (SZ_2K * sizeof(u32))
#define MAX_METADATA_SIZE SZ_64K
#define MAX_COMPONENT_ID 127
#define MAX_COMPONENT_VERSION 0xffff
#define MANIFEST_IDX 0
#define METADATA_IDX 1
#define MODULEDATA_IDX 2
/*
* PKG_DIR Entry (type == id)
* 63:56 55 54:48 47:32 31:24 23:0
* Rsvd Rsvd Type Version Rsvd Size
*/
#define PKG_DIR_SIZE_MASK GENMASK_ULL(23, 0)
#define PKG_DIR_VERSION_MASK GENMASK_ULL(47, 32)
#define PKG_DIR_TYPE_MASK GENMASK_ULL(54, 48)
static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd,
u8 idx)
{
const struct ipu6_cpd_hdr *cpd_hdr = cpd;
const struct ipu6_cpd_ent *ent;
ent = (const struct ipu6_cpd_ent *)((const u8 *)cpd + cpd_hdr->hdr_len);
return ent + idx;
}
#define ipu6_cpd_get_manifest(cpd) ipu6_cpd_get_entry(cpd, MANIFEST_IDX)
#define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX)
#define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX)
static const struct ipu6_cpd_metadata_cmpnt_hdr *
ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata,
unsigned int metadata_size, u8 idx)
{
size_t extn_size = sizeof(struct ipu6_cpd_metadata_extn);
size_t cmpnt_count = metadata_size - extn_size;
cmpnt_count = div_u64(cmpnt_count, isp->cpd_metadata_cmpnt_size);
if (idx > MAX_COMPONENT_ID || idx >= cmpnt_count) {
dev_err(&isp->pdev->dev, "Component index out of range (%d)\n",
idx);
return ERR_PTR(-EINVAL);
}
return metadata + extn_size + idx * isp->cpd_metadata_cmpnt_size;
}
static u32 ipu6_cpd_metadata_cmpnt_version(struct ipu6_device *isp,
const void *metadata,
unsigned int metadata_size, u8 idx)
{
const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt;
cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
if (IS_ERR(cmpnt))
return PTR_ERR(cmpnt);
return cmpnt->ver;
}
static int ipu6_cpd_metadata_get_cmpnt_id(struct ipu6_device *isp,
const void *metadata,
unsigned int metadata_size, u8 idx)
{
const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt;
cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
if (IS_ERR(cmpnt))
return PTR_ERR(cmpnt);
return cmpnt->id;
}
static int ipu6_cpd_parse_module_data(struct ipu6_device *isp,
const void *module_data,
unsigned int module_data_size,
dma_addr_t dma_addr_module_data,
u64 *pkg_dir, const void *metadata,
unsigned int metadata_size)
{
const struct ipu6_cpd_module_data_hdr *module_data_hdr;
const struct ipu6_cpd_hdr *dir_hdr;
const struct ipu6_cpd_ent *dir_ent;
unsigned int i;
u8 len;
if (!module_data)
return -EINVAL;
module_data_hdr = module_data;
dir_hdr = module_data + module_data_hdr->hdr_len;
len = dir_hdr->hdr_len;
dir_ent = (const struct ipu6_cpd_ent *)(((u8 *)dir_hdr) + len);
pkg_dir[0] = PKG_DIR_HDR_MARK;
/* pkg_dir entry count = component count + pkg_dir header */
pkg_dir[1] = dir_hdr->ent_cnt + 1;
for (i = 0; i < dir_hdr->ent_cnt; i++, dir_ent++) {
u64 *p = &pkg_dir[PKG_DIR_ENT_LEN * (1 + i)];
int ver, id;
*p++ = dma_addr_module_data + dir_ent->offset;
id = ipu6_cpd_metadata_get_cmpnt_id(isp, metadata,
metadata_size, i);
if (id < 0 || id > MAX_COMPONENT_ID) {
dev_err(&isp->pdev->dev, "Invalid CPD component id\n");
return -EINVAL;
}
ver = ipu6_cpd_metadata_cmpnt_version(isp, metadata,
metadata_size, i);
if (ver < 0 || ver > MAX_COMPONENT_VERSION) {
dev_err(&isp->pdev->dev,
"Invalid CPD component version\n");
return -EINVAL;
}
*p = FIELD_PREP(PKG_DIR_SIZE_MASK, dir_ent->len) |
FIELD_PREP(PKG_DIR_TYPE_MASK, id) |
FIELD_PREP(PKG_DIR_VERSION_MASK, ver);
}
return 0;
}
int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src)
{
dma_addr_t dma_addr_src = sg_dma_address(adev->fw_sgt.sgl);
const struct ipu6_cpd_ent *ent, *man_ent, *met_ent;
struct ipu6_device *isp = adev->isp;
unsigned int man_sz, met_sz;
void *pkg_dir_pos;
int ret;
man_ent = ipu6_cpd_get_manifest(src);
man_sz = man_ent->len;
met_ent = ipu6_cpd_get_metadata(src);
met_sz = met_ent->len;
adev->pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz;
adev->pkg_dir = ipu6_dma_alloc(adev, adev->pkg_dir_size,
&adev->pkg_dir_dma_addr, GFP_KERNEL, 0);
if (!adev->pkg_dir)
return -ENOMEM;
/*
* pkg_dir entry/header:
* qword | 63:56 | 55 | 54:48 | 47:32 | 31:24 | 23:0
* N Address/Offset/"_IUPKDR_"
* N + 1 | rsvd | rsvd | type | ver | rsvd | size
*
* We can ignore other fields that size in N + 1 qword as they
* are 0 anyway. Just setting size for now.
*/
ent = ipu6_cpd_get_moduledata(src);
ret = ipu6_cpd_parse_module_data(isp, src + ent->offset,
ent->len, dma_addr_src + ent->offset,
adev->pkg_dir, src + met_ent->offset,
met_ent->len);
if (ret) {
dev_err(&isp->pdev->dev, "Failed to parse module data\n");
ipu6_dma_free(adev, adev->pkg_dir_size,
adev->pkg_dir, adev->pkg_dir_dma_addr, 0);
return ret;
}
/* Copy manifest after pkg_dir */
pkg_dir_pos = adev->pkg_dir + PKG_DIR_ENT_LEN * MAX_PKG_DIR_ENT_CNT;
memcpy(pkg_dir_pos, src + man_ent->offset, man_sz);
/* Copy metadata after manifest */
pkg_dir_pos += man_sz;
memcpy(pkg_dir_pos, src + met_ent->offset, met_sz);
ipu6_dma_sync_single(adev, adev->pkg_dir_dma_addr,
adev->pkg_dir_size);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, "INTEL_IPU6");
void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev)
{
ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir,
adev->pkg_dir_dma_addr, 0);
}
EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, "INTEL_IPU6");
static int ipu6_cpd_validate_cpd(struct ipu6_device *isp, const void *cpd,
unsigned long cpd_size,
unsigned long data_size)
{
const struct ipu6_cpd_hdr *cpd_hdr = cpd;
const struct ipu6_cpd_ent *ent;
unsigned int i;
u8 len;
len = cpd_hdr->hdr_len;
/* Ensure cpd hdr is within moduledata */
if (cpd_size < len) {
dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
return -EINVAL;
}
/* Sanity check for CPD header */
if ((cpd_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) {
dev_err(&isp->pdev->dev, "Invalid CPD header\n");
return -EINVAL;
}
/* Ensure that all entries are within moduledata */
ent = (const struct ipu6_cpd_ent *)(((const u8 *)cpd_hdr) + len);
for (i = 0; i < cpd_hdr->ent_cnt; i++, ent++) {
if (data_size < ent->offset ||
data_size - ent->offset < ent->len) {
dev_err(&isp->pdev->dev, "Invalid CPD entry (%d)\n", i);
return -EINVAL;
}
}
return 0;
}
static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp,
const void *moduledata,
u32 moduledata_size)
{
const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata;
int ret;
/* Ensure moduledata hdr is within moduledata */
if (moduledata_size < sizeof(*mod_hdr) ||
moduledata_size < mod_hdr->hdr_len) {
dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
return -EINVAL;
}
dev_dbg(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date);
ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len,
moduledata_size - mod_hdr->hdr_len,
moduledata_size);
if (ret) {
dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n");
return ret;
}
return 0;
}
static int ipu6_cpd_validate_metadata(struct ipu6_device *isp,
const void *metadata, u32 meta_size)
{
const struct ipu6_cpd_metadata_extn *extn = metadata;
/* Sanity check for metadata size */
if (meta_size < sizeof(*extn) || meta_size > MAX_METADATA_SIZE) {
dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
return -EINVAL;
}
/* Validate extension and image types */
if (extn->extn_type != IPU6_CPD_METADATA_EXTN_TYPE_IUNIT ||
extn->img_type != IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE) {
dev_err(&isp->pdev->dev,
"Invalid CPD metadata descriptor img_type (%d)\n",
extn->img_type);
return -EINVAL;
}
/* Validate metadata size multiple of metadata components */
if ((meta_size - sizeof(*extn)) % isp->cpd_metadata_cmpnt_size) {
dev_err(&isp->pdev->dev, "Invalid CPD metadata size\n");
return -EINVAL;
}
return 0;
}
int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file,
unsigned long cpd_file_size)
{
const struct ipu6_cpd_hdr *hdr = cpd_file;
const struct ipu6_cpd_ent *ent;
int ret;
ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size,
cpd_file_size);
if (ret) {
dev_err(&isp->pdev->dev, "Invalid CPD in file\n");
return ret;
}
/* Check for CPD file marker */
if (hdr->hdr_mark != CPD_HDR_MARK) {
dev_err(&isp->pdev->dev, "Invalid CPD header\n");
return -EINVAL;
}
/* Sanity check for manifest size */
ent = ipu6_cpd_get_manifest(cpd_file);
if (ent->len > MAX_MANIFEST_SIZE) {
dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n");
return -EINVAL;
}
/* Validate metadata */
ent = ipu6_cpd_get_metadata(cpd_file);
ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len);
if (ret) {
dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
return ret;
}
/* Validate moduledata */
ent = ipu6_cpd_get_moduledata(cpd_file);
ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset,
ent->len);
if (ret)
dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n");
return ret;
}
@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2015--2024 Intel Corporation */
#ifndef IPU6_CPD_H
#define IPU6_CPD_H
struct ipu6_device;
struct ipu6_bus_device;
#define IPU6_CPD_SIZE_OF_FW_ARCH_VERSION 7
#define IPU6_CPD_SIZE_OF_SYSTEM_VERSION 11
#define IPU6_CPD_SIZE_OF_COMPONENT_NAME 12
#define IPU6_CPD_METADATA_EXTN_TYPE_IUNIT 0x10
#define IPU6_CPD_METADATA_IMAGE_TYPE_RESERVED 0
#define IPU6_CPD_METADATA_IMAGE_TYPE_BOOTLOADER 1
#define IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE 2
#define IPU6_CPD_PKG_DIR_PSYS_SERVER_IDX 0
#define IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX 1
#define IPU6_CPD_PKG_DIR_CLIENT_PG_TYPE 3
#define IPU6_CPD_METADATA_HASH_KEY_SIZE 48
#define IPU6SE_CPD_METADATA_HASH_KEY_SIZE 32
struct ipu6_cpd_module_data_hdr {
u32 hdr_len;
u32 endian;
u32 fw_pkg_date;
u32 hive_sdk_date;
u32 compiler_date;
u32 target_platform_type;
u8 sys_ver[IPU6_CPD_SIZE_OF_SYSTEM_VERSION];
u8 fw_arch_ver[IPU6_CPD_SIZE_OF_FW_ARCH_VERSION];
u8 rsvd[2];
} __packed;
/*
* ipu6_cpd_hdr structure updated as the chksum and
* sub_partition_name is unused on host side
* CSE layout version 1.6 for IPU6SE (hdr_len = 0x10)
* CSE layout version 1.7 for IPU6 (hdr_len = 0x14)
*/
struct ipu6_cpd_hdr {
u32 hdr_mark;
u32 ent_cnt;
u8 hdr_ver;
u8 ent_ver;
u8 hdr_len;
} __packed;
struct ipu6_cpd_ent {
u8 name[IPU6_CPD_SIZE_OF_COMPONENT_NAME];
u32 offset;
u32 len;
u8 rsvd[4];
} __packed;
struct ipu6_cpd_metadata_cmpnt_hdr {
u32 id;
u32 size;
u32 ver;
} __packed;
struct ipu6_cpd_metadata_cmpnt {
struct ipu6_cpd_metadata_cmpnt_hdr hdr;
u8 sha2_hash[IPU6_CPD_METADATA_HASH_KEY_SIZE];
u32 entry_point;
u32 icache_base_offs;
u8 attrs[16];
} __packed;
struct ipu6se_cpd_metadata_cmpnt {
struct ipu6_cpd_metadata_cmpnt_hdr hdr;
u8 sha2_hash[IPU6SE_CPD_METADATA_HASH_KEY_SIZE];
u32 entry_point;
u32 icache_base_offs;
u8 attrs[16];
} __packed;
struct ipu6_cpd_metadata_extn {
u32 extn_type;
u32 len;
u32 img_type;
u8 rsvd[16];
} __packed;
struct ipu6_cpd_client_pkg_hdr {
u32 prog_list_offs;
u32 prog_list_size;
u32 prog_desc_offs;
u32 prog_desc_size;
u32 pg_manifest_offs;
u32 pg_manifest_size;
u32 prog_bin_offs;
u32 prog_bin_size;
} __packed;
int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src);
void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev);
int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file,
unsigned long cpd_file_size);
#endif /* IPU6_CPD_H */
@@ -0,0 +1,459 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/cacheflush.h>
#include <linux/dma-mapping.h>
#include <linux/iova.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-dma.h"
#include "ipu6-mmu.h"
struct vm_info {
struct list_head list;
struct page **pages;
dma_addr_t ipu6_iova;
void *vaddr;
unsigned long size;
};
static struct vm_info *get_vm_info(struct ipu6_mmu *mmu, dma_addr_t iova)
{
struct vm_info *info, *save;
list_for_each_entry_safe(info, save, &mmu->vma_list, list) {
if (iova >= info->ipu6_iova &&
iova < (info->ipu6_iova + info->size))
return info;
}
return NULL;
}
static void __clear_buffer(struct page *page, size_t size, unsigned long attrs)
{
void *ptr;
if (!page)
return;
/*
* Ensure that the allocated pages are zeroed, and that any data
* lurking in the kernel direct-mapped region is invalidated.
*/
ptr = page_address(page);
memset(ptr, 0, size);
if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0)
clflush_cache_range(ptr, size);
}
static struct page **__alloc_buffer(size_t size, gfp_t gfp, unsigned long attrs)
{
int count = PHYS_PFN(size);
int array_size = count * sizeof(struct page *);
struct page **pages;
int i = 0;
pages = kvzalloc(array_size, GFP_KERNEL);
if (!pages)
return NULL;
gfp |= __GFP_NOWARN;
while (count) {
int j, order = __fls(count);
pages[i] = alloc_pages(gfp, order);
while (!pages[i] && order)
pages[i] = alloc_pages(gfp, --order);
if (!pages[i])
goto error;
if (order) {
split_page(pages[i], order);
j = 1 << order;
while (j--)
pages[i + j] = pages[i] + j;
}
__clear_buffer(pages[i], PAGE_SIZE << order, attrs);
i += 1 << order;
count -= 1 << order;
}
return pages;
error:
while (i--)
if (pages[i])
__free_pages(pages[i], 0);
kvfree(pages);
return NULL;
}
static void __free_buffer(struct page **pages, size_t size, unsigned long attrs)
{
int count = PHYS_PFN(size);
unsigned int i;
for (i = 0; i < count && pages[i]; i++) {
__clear_buffer(pages[i], PAGE_SIZE, attrs);
__free_pages(pages[i], 0);
}
kvfree(pages);
}
void ipu6_dma_sync_single(struct ipu6_bus_device *sys, dma_addr_t dma_handle,
size_t size)
{
void *vaddr;
u32 offset;
struct vm_info *info;
struct ipu6_mmu *mmu = sys->mmu;
info = get_vm_info(mmu, dma_handle);
if (WARN_ON(!info))
return;
offset = dma_handle - info->ipu6_iova;
if (WARN_ON(size > (info->size - offset)))
return;
vaddr = info->vaddr + offset;
clflush_cache_range(vaddr, size);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_single, "INTEL_IPU6");
void ipu6_dma_sync_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents)
{
struct scatterlist *sg;
int i;
for_each_sg(sglist, sg, nents, i)
clflush_cache_range(sg_virt(sg), sg->length);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sg, "INTEL_IPU6");
void ipu6_dma_sync_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt)
{
ipu6_dma_sync_sg(sys, sgt->sgl, sgt->orig_nents);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sgtable, "INTEL_IPU6");
void *ipu6_dma_alloc(struct ipu6_bus_device *sys, size_t size,
dma_addr_t *dma_handle, gfp_t gfp,
unsigned long attrs)
{
struct device *dev = &sys->auxdev.dev;
struct pci_dev *pdev = sys->isp->pdev;
dma_addr_t pci_dma_addr, ipu6_iova;
struct ipu6_mmu *mmu = sys->mmu;
struct vm_info *info;
unsigned long count;
struct page **pages;
struct iova *iova;
unsigned int i;
int ret;
info = kzalloc_obj(*info);
if (!info)
return NULL;
size = PAGE_ALIGN(size);
count = PHYS_PFN(size);
iova = alloc_iova(&mmu->dmap->iovad, count,
PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0);
if (!iova)
goto out_kfree;
pages = __alloc_buffer(size, gfp, attrs);
if (!pages)
goto out_free_iova;
dev_dbg(dev, "dma_alloc: size %zu iova low pfn %lu, high pfn %lu\n",
size, iova->pfn_lo, iova->pfn_hi);
for (i = 0; iova->pfn_lo + i <= iova->pfn_hi; i++) {
pci_dma_addr = dma_map_page_attrs(&pdev->dev, pages[i], 0,
PAGE_SIZE, DMA_BIDIRECTIONAL,
attrs);
dev_dbg(dev, "dma_alloc: mapped pci_dma_addr %pad\n",
&pci_dma_addr);
if (dma_mapping_error(&pdev->dev, pci_dma_addr)) {
dev_err(dev, "pci_dma_mapping for page[%d] failed", i);
goto out_unmap;
}
ret = ipu6_mmu_map(mmu->dmap->mmu_info,
PFN_PHYS(iova->pfn_lo + i), pci_dma_addr,
PAGE_SIZE);
if (ret) {
dev_err(dev, "ipu6_mmu_map for pci_dma[%d] %pad failed",
i, &pci_dma_addr);
dma_unmap_page_attrs(&pdev->dev, pci_dma_addr,
PAGE_SIZE, DMA_BIDIRECTIONAL,
attrs);
goto out_unmap;
}
}
info->vaddr = vmap(pages, count, VM_USERMAP, PAGE_KERNEL);
if (!info->vaddr)
goto out_unmap;
*dma_handle = PFN_PHYS(iova->pfn_lo);
info->pages = pages;
info->ipu6_iova = *dma_handle;
info->size = size;
list_add(&info->list, &mmu->vma_list);
return info->vaddr;
out_unmap:
while (i--) {
ipu6_iova = PFN_PHYS(iova->pfn_lo + i);
pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info,
ipu6_iova);
dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE,
DMA_BIDIRECTIONAL, attrs);
ipu6_mmu_unmap(mmu->dmap->mmu_info, ipu6_iova, PAGE_SIZE);
}
__free_buffer(pages, size, attrs);
out_free_iova:
__free_iova(&mmu->dmap->iovad, iova);
out_kfree:
kfree(info);
return NULL;
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_alloc, "INTEL_IPU6");
void ipu6_dma_free(struct ipu6_bus_device *sys, size_t size, void *vaddr,
dma_addr_t dma_handle, unsigned long attrs)
{
struct ipu6_mmu *mmu = sys->mmu;
struct pci_dev *pdev = sys->isp->pdev;
struct iova *iova = find_iova(&mmu->dmap->iovad, PHYS_PFN(dma_handle));
dma_addr_t pci_dma_addr, ipu6_iova;
struct vm_info *info;
struct page **pages;
unsigned int i;
if (WARN_ON(!iova))
return;
info = get_vm_info(mmu, dma_handle);
if (WARN_ON(!info))
return;
if (WARN_ON(!info->vaddr))
return;
if (WARN_ON(!info->pages))
return;
list_del(&info->list);
size = PAGE_ALIGN(size);
pages = info->pages;
vunmap(vaddr);
for (i = 0; i < PHYS_PFN(size); i++) {
ipu6_iova = PFN_PHYS(iova->pfn_lo + i);
pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info,
ipu6_iova);
dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE,
DMA_BIDIRECTIONAL, attrs);
}
ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo),
PFN_PHYS(iova_size(iova)));
__free_buffer(pages, size, attrs);
mmu->tlb_invalidate(mmu);
__free_iova(&mmu->dmap->iovad, iova);
kfree(info);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_free, "INTEL_IPU6");
int ipu6_dma_mmap(struct ipu6_bus_device *sys, struct vm_area_struct *vma,
void *addr, dma_addr_t iova, size_t size,
unsigned long attrs)
{
struct ipu6_mmu *mmu = sys->mmu;
size_t count = PFN_UP(size);
struct vm_info *info;
size_t i;
int ret;
info = get_vm_info(mmu, iova);
if (!info)
return -EFAULT;
if (!info->vaddr)
return -EFAULT;
if (vma->vm_start & ~PAGE_MASK)
return -EINVAL;
if (size > info->size)
return -EFAULT;
for (i = 0; i < count; i++) {
ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i),
info->pages[i]);
if (ret < 0)
return ret;
}
return 0;
}
void ipu6_dma_unmap_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents, enum dma_data_direction dir,
unsigned long attrs)
{
struct device *dev = &sys->auxdev.dev;
struct ipu6_mmu *mmu = sys->mmu;
struct iova *iova = find_iova(&mmu->dmap->iovad,
PHYS_PFN(sg_dma_address(sglist)));
struct scatterlist *sg;
dma_addr_t pci_dma_addr;
unsigned int i;
if (!nents)
return;
if (WARN_ON(!iova))
return;
/*
* Before IPU6 mmu unmap, return the pci dma address back to sg
* assume the nents is less than orig_nents as the least granule
* is 1 SZ_4K page
*/
dev_dbg(dev, "trying to unmap concatenated %u ents\n", nents);
for_each_sg(sglist, sg, nents, i) {
dev_dbg(dev, "unmap sg[%d] %pad size %u\n", i,
&sg_dma_address(sg), sg_dma_len(sg));
pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info,
sg_dma_address(sg));
dev_dbg(dev, "return pci_dma_addr %pad back to sg[%d]\n",
&pci_dma_addr, i);
sg_dma_address(sg) = pci_dma_addr;
}
dev_dbg(dev, "ipu6_mmu_unmap low pfn %lu high pfn %lu\n",
iova->pfn_lo, iova->pfn_hi);
ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo),
PFN_PHYS(iova_size(iova)));
mmu->tlb_invalidate(mmu);
__free_iova(&mmu->dmap->iovad, iova);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, "INTEL_IPU6");
int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents, enum dma_data_direction dir,
unsigned long attrs)
{
struct device *dev = &sys->auxdev.dev;
struct ipu6_mmu *mmu = sys->mmu;
struct scatterlist *sg;
struct iova *iova;
size_t npages = 0;
unsigned long iova_addr;
int i;
for_each_sg(sglist, sg, nents, i) {
if (sg->offset) {
dev_err(dev, "Unsupported non-zero sg[%d].offset %x\n",
i, sg->offset);
return -EFAULT;
}
}
for_each_sg(sglist, sg, nents, i)
npages += PFN_UP(sg_dma_len(sg));
dev_dbg(dev, "dmamap trying to map %d ents %zu pages\n",
nents, npages);
iova = alloc_iova(&mmu->dmap->iovad, npages,
PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0);
if (!iova)
return 0;
dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo,
iova->pfn_hi);
iova_addr = iova->pfn_lo;
for_each_sg(sglist, sg, nents, i) {
phys_addr_t iova_pa;
int ret;
iova_pa = PFN_PHYS(iova_addr);
dev_dbg(dev, "mapping entry %d: iova %pap phy %pap size %d\n",
i, &iova_pa, &sg_dma_address(sg), sg_dma_len(sg));
ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr),
sg_dma_address(sg),
PAGE_ALIGN(sg_dma_len(sg)));
if (ret)
goto out_fail;
sg_dma_address(sg) = PFN_PHYS(iova_addr);
iova_addr += PFN_UP(sg_dma_len(sg));
}
dev_dbg(dev, "dmamap %d ents %zu pages mapped\n", nents, npages);
return nents;
out_fail:
ipu6_dma_unmap_sg(sys, sglist, i, dir, attrs);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sg, "INTEL_IPU6");
int ipu6_dma_map_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt,
enum dma_data_direction dir, unsigned long attrs)
{
int nents;
nents = ipu6_dma_map_sg(sys, sgt->sgl, sgt->nents, dir, attrs);
if (nents < 0)
return nents;
sgt->nents = nents;
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sgtable, "INTEL_IPU6");
void ipu6_dma_unmap_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt,
enum dma_data_direction dir, unsigned long attrs)
{
ipu6_dma_unmap_sg(sys, sgt->sgl, sgt->nents, dir, attrs);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sgtable, "INTEL_IPU6");
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_DMA_H
#define IPU6_DMA_H
#include <linux/iova.h>
#include <linux/scatterlist.h>
#include <linux/types.h>
#include "ipu6-bus.h"
struct ipu6_mmu_info;
struct ipu6_dma_mapping {
struct ipu6_mmu_info *mmu_info;
struct iova_domain iovad;
};
void ipu6_dma_sync_single(struct ipu6_bus_device *sys, dma_addr_t dma_handle,
size_t size);
void ipu6_dma_sync_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents);
void ipu6_dma_sync_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt);
void *ipu6_dma_alloc(struct ipu6_bus_device *sys, size_t size,
dma_addr_t *dma_handle, gfp_t gfp,
unsigned long attrs);
void ipu6_dma_free(struct ipu6_bus_device *sys, size_t size, void *vaddr,
dma_addr_t dma_handle, unsigned long attrs);
int ipu6_dma_mmap(struct ipu6_bus_device *sys, struct vm_area_struct *vma,
void *addr, dma_addr_t iova, size_t size,
unsigned long attrs);
int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents, enum dma_data_direction dir,
unsigned long attrs);
void ipu6_dma_unmap_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents, enum dma_data_direction dir,
unsigned long attrs);
int ipu6_dma_map_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt,
enum dma_data_direction dir, unsigned long attrs);
void ipu6_dma_unmap_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt,
enum dma_data_direction dir, unsigned long attrs);
#endif /* IPU6_DMA_H */
@@ -0,0 +1,413 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/math.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "ipu6-bus.h"
#include "ipu6-dma.h"
#include "ipu6-fw-com.h"
/*
* FWCOM layer is a shared resource between FW and driver. It consist
* of token queues to both send and receive directions. Queue is simply
* an array of structures with read and write indexes to the queue.
* There are 1...n queues to both directions. Queues locates in
* system RAM and are mapped to ISP MMU so that both CPU and ISP can
* see the same buffer. Indexes are located in ISP DMEM so that FW code
* can poll those with very low latency and cost. CPU access to indexes is
* more costly but that happens only at message sending time and
* interrupt triggered message handling. CPU doesn't need to poll indexes.
* wr_reg / rd_reg are offsets to those dmem location. They are not
* the indexes itself.
*/
/* Shared structure between driver and FW - do not modify */
struct ipu6_fw_sys_queue {
u64 host_address;
u32 vied_address;
u32 size;
u32 token_size;
u32 wr_reg; /* reg number in subsystem's regmem */
u32 rd_reg;
u32 _align;
} __packed;
struct ipu6_fw_sys_queue_res {
u64 host_address;
u32 vied_address;
u32 reg;
} __packed;
enum syscom_state {
/* Program load or explicit host setting should init to this */
SYSCOM_STATE_UNINIT = 0x57a7e000,
/* SP Syscom sets this when it is ready for use */
SYSCOM_STATE_READY = 0x57a7e001,
/* SP Syscom sets this when no more syscom accesses will happen */
SYSCOM_STATE_INACTIVE = 0x57a7e002,
};
enum syscom_cmd {
/* Program load or explicit host setting should init to this */
SYSCOM_COMMAND_UNINIT = 0x57a7f000,
/* Host Syscom requests syscom to become inactive */
SYSCOM_COMMAND_INACTIVE = 0x57a7f001,
};
/* firmware config: data that sent from the host to SP via DDR */
/* Cell copies data into a context */
struct ipu6_fw_syscom_config {
u32 firmware_address;
u32 num_input_queues;
u32 num_output_queues;
/* ISP pointers to an array of ipu6_fw_sys_queue structures */
u32 input_queue;
u32 output_queue;
/* ISYS / PSYS private data */
u32 specific_addr;
u32 specific_size;
};
struct ipu6_fw_com_context {
struct ipu6_bus_device *adev;
void __iomem *dmem_addr;
int (*cell_ready)(struct ipu6_bus_device *adev);
void (*cell_start)(struct ipu6_bus_device *adev);
void *dma_buffer;
dma_addr_t dma_addr;
unsigned int dma_size;
struct ipu6_fw_sys_queue *input_queue; /* array of host to SP queues */
struct ipu6_fw_sys_queue *output_queue; /* array of SP to host */
u32 config_vied_addr;
unsigned int buttress_boot_offset;
void __iomem *base_addr;
};
#define FW_COM_WR_REG 0
#define FW_COM_RD_REG 4
#define REGMEM_OFFSET 0
#define TUNIT_MAGIC_PATTERN 0x5a5a5a5a
enum regmem_id {
/* pass pkg_dir address to SPC in non-secure mode */
PKG_DIR_ADDR_REG = 0,
/* Tunit CFG blob for secure - provided by host.*/
TUNIT_CFG_DWR_REG = 1,
/* syscom commands - modified by the host */
SYSCOM_COMMAND_REG = 2,
/* Store interrupt status - updated by SP */
SYSCOM_IRQ_REG = 3,
/* first syscom queue pointer register */
SYSCOM_QPR_BASE_REG = 4
};
#define BUTTRESS_FW_BOOT_PARAMS_0 0x4000
#define BUTTRESS_FW_BOOT_PARAM_REG(base, offset, id) \
((base) + BUTTRESS_FW_BOOT_PARAMS_0 + ((offset) + (id)) * 4)
enum buttress_syscom_id {
/* pass syscom configuration to SPC */
SYSCOM_CONFIG_ID = 0,
/* syscom state - modified by SP */
SYSCOM_STATE_ID = 1,
/* syscom vtl0 addr mask */
SYSCOM_VTL0_ADDR_MASK_ID = 2,
SYSCOM_ID_MAX
};
static void ipu6_sys_queue_init(struct ipu6_fw_sys_queue *q, unsigned int size,
unsigned int token_size,
struct ipu6_fw_sys_queue_res *res)
{
unsigned int buf_size = (size + 1) * token_size;
q->size = size + 1;
q->token_size = token_size;
/* acquire the shared buffer space */
q->host_address = res->host_address;
res->host_address += buf_size;
q->vied_address = res->vied_address;
res->vied_address += buf_size;
/* acquire the shared read and writer pointers */
q->wr_reg = res->reg;
res->reg++;
q->rd_reg = res->reg;
res->reg++;
}
void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg,
struct ipu6_bus_device *adev, void __iomem *base)
{
size_t conf_size, inq_size, outq_size, specific_size;
struct ipu6_fw_syscom_config *config_host_addr;
unsigned int sizeinput = 0, sizeoutput = 0;
struct ipu6_fw_sys_queue_res res;
struct ipu6_fw_com_context *ctx;
struct device *dev = &adev->auxdev.dev;
size_t sizeall, offset;
void *specific_host_addr;
unsigned int i;
if (!cfg || !cfg->cell_start || !cfg->cell_ready)
return NULL;
ctx = kzalloc_obj(*ctx);
if (!ctx)
return NULL;
ctx->dmem_addr = base + cfg->dmem_addr + REGMEM_OFFSET;
ctx->adev = adev;
ctx->cell_start = cfg->cell_start;
ctx->cell_ready = cfg->cell_ready;
ctx->buttress_boot_offset = cfg->buttress_boot_offset;
ctx->base_addr = base;
/*
* Allocate DMA mapped memory. Allocate one big chunk.
*/
/* Base cfg for FW */
conf_size = roundup(sizeof(struct ipu6_fw_syscom_config), 8);
/* Descriptions of the queues */
inq_size = size_mul(cfg->num_input_queues,
sizeof(struct ipu6_fw_sys_queue));
outq_size = size_mul(cfg->num_output_queues,
sizeof(struct ipu6_fw_sys_queue));
/* FW specific information structure */
specific_size = roundup(cfg->specific_size, 8);
sizeall = conf_size + inq_size + outq_size + specific_size;
for (i = 0; i < cfg->num_input_queues; i++)
sizeinput += size_mul(cfg->input[i].queue_size + 1,
cfg->input[i].token_size);
for (i = 0; i < cfg->num_output_queues; i++)
sizeoutput += size_mul(cfg->output[i].queue_size + 1,
cfg->output[i].token_size);
sizeall += sizeinput + sizeoutput;
ctx->dma_buffer = ipu6_dma_alloc(adev, sizeall, &ctx->dma_addr,
GFP_KERNEL, 0);
if (!ctx->dma_buffer) {
dev_err(dev, "failed to allocate dma memory\n");
kfree(ctx);
return NULL;
}
ctx->dma_size = sizeall;
config_host_addr = ctx->dma_buffer;
ctx->config_vied_addr = ctx->dma_addr;
offset = conf_size;
ctx->input_queue = ctx->dma_buffer + offset;
config_host_addr->input_queue = ctx->dma_addr + offset;
config_host_addr->num_input_queues = cfg->num_input_queues;
offset += inq_size;
ctx->output_queue = ctx->dma_buffer + offset;
config_host_addr->output_queue = ctx->dma_addr + offset;
config_host_addr->num_output_queues = cfg->num_output_queues;
/* copy firmware specific data */
offset += outq_size;
specific_host_addr = ctx->dma_buffer + offset;
config_host_addr->specific_addr = ctx->dma_addr + offset;
config_host_addr->specific_size = cfg->specific_size;
if (cfg->specific_addr && cfg->specific_size)
memcpy(specific_host_addr, cfg->specific_addr,
cfg->specific_size);
ipu6_dma_sync_single(adev, ctx->config_vied_addr, sizeall);
/* initialize input queues */
offset += specific_size;
res.reg = SYSCOM_QPR_BASE_REG;
res.host_address = (uintptr_t)(ctx->dma_buffer + offset);
res.vied_address = ctx->dma_addr + offset;
for (i = 0; i < cfg->num_input_queues; i++)
ipu6_sys_queue_init(ctx->input_queue + i,
cfg->input[i].queue_size,
cfg->input[i].token_size, &res);
/* initialize output queues */
offset += sizeinput;
res.host_address = (uintptr_t)(ctx->dma_buffer + offset);
res.vied_address = ctx->dma_addr + offset;
for (i = 0; i < cfg->num_output_queues; i++) {
ipu6_sys_queue_init(ctx->output_queue + i,
cfg->output[i].queue_size,
cfg->output[i].token_size, &res);
}
return ctx;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, "INTEL_IPU6");
int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx)
{
/* write magic pattern to disable the tunit trace */
writel(TUNIT_MAGIC_PATTERN, ctx->dmem_addr + TUNIT_CFG_DWR_REG * 4);
/* Check if SP is in valid state */
if (!ctx->cell_ready(ctx->adev))
return -EIO;
/* store syscom uninitialized command */
writel(SYSCOM_COMMAND_UNINIT, ctx->dmem_addr + SYSCOM_COMMAND_REG * 4);
/* store syscom uninitialized state */
writel(SYSCOM_STATE_UNINIT,
BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
ctx->buttress_boot_offset,
SYSCOM_STATE_ID));
/* store firmware configuration address */
writel(ctx->config_vied_addr,
BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
ctx->buttress_boot_offset,
SYSCOM_CONFIG_ID));
ctx->cell_start(ctx->adev);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, "INTEL_IPU6");
int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx)
{
int state;
state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
ctx->buttress_boot_offset,
SYSCOM_STATE_ID));
if (state != SYSCOM_STATE_READY)
return -EBUSY;
/* set close request flag */
writel(SYSCOM_COMMAND_INACTIVE, ctx->dmem_addr +
SYSCOM_COMMAND_REG * 4);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, "INTEL_IPU6");
int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force)
{
/* check if release is forced, an verify cell state if it is not */
if (!force && !ctx->cell_ready(ctx->adev))
return -EBUSY;
ipu6_dma_free(ctx->adev, ctx->dma_size,
ctx->dma_buffer, ctx->dma_addr, 0);
kfree(ctx);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, "INTEL_IPU6");
bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx)
{
int state;
state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
ctx->buttress_boot_offset,
SYSCOM_STATE_ID));
return state == SYSCOM_STATE_READY;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, "INTEL_IPU6");
void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr)
{
struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr];
void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
unsigned int wr, rd;
unsigned int packets;
unsigned int index;
wr = readl(q_dmem + FW_COM_WR_REG);
rd = readl(q_dmem + FW_COM_RD_REG);
if (WARN_ON_ONCE(wr >= q->size || rd >= q->size))
return NULL;
if (wr < rd)
packets = rd - wr - 1;
else
packets = q->size - (wr - rd + 1);
if (!packets)
return NULL;
index = readl(q_dmem + FW_COM_WR_REG);
return (void *)((uintptr_t)q->host_address + index * q->token_size);
}
EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, "INTEL_IPU6");
void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr)
{
struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr];
void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
unsigned int wr = readl(q_dmem + FW_COM_WR_REG) + 1;
if (wr >= q->size)
wr = 0;
writel(wr, q_dmem + FW_COM_WR_REG);
}
EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, "INTEL_IPU6");
void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr)
{
struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr];
void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
unsigned int wr, rd;
unsigned int packets;
wr = readl(q_dmem + FW_COM_WR_REG);
rd = readl(q_dmem + FW_COM_RD_REG);
if (WARN_ON_ONCE(wr >= q->size || rd >= q->size))
return NULL;
if (wr < rd)
wr += q->size;
packets = wr - rd;
if (!packets)
return NULL;
return (void *)((uintptr_t)q->host_address + rd * q->token_size);
}
EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, "INTEL_IPU6");
void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr)
{
struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr];
void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
unsigned int rd = readl(q_dmem + FW_COM_RD_REG) + 1;
if (rd >= q->size)
rd = 0;
writel(rd, q_dmem + FW_COM_RD_REG);
}
EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, "INTEL_IPU6");
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_FW_COM_H
#define IPU6_FW_COM_H
struct ipu6_fw_com_context;
struct ipu6_bus_device;
struct ipu6_fw_syscom_queue_config {
unsigned int queue_size; /* tokens per queue */
unsigned int token_size; /* bytes per token */
};
#define SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET 0
struct ipu6_fw_com_cfg {
unsigned int num_input_queues;
unsigned int num_output_queues;
struct ipu6_fw_syscom_queue_config *input;
struct ipu6_fw_syscom_queue_config *output;
unsigned int dmem_addr;
/* firmware-specific configuration data */
void *specific_addr;
unsigned int specific_size;
int (*cell_ready)(struct ipu6_bus_device *adev);
void (*cell_start)(struct ipu6_bus_device *adev);
unsigned int buttress_boot_offset;
};
void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg,
struct ipu6_bus_device *adev, void __iomem *base);
int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx);
bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx);
int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx);
int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force);
void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr);
void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr);
void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr);
void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr);
#endif
@@ -0,0 +1,487 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/cacheflush.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include "ipu6-bus.h"
#include "ipu6-fw-com.h"
#include "ipu6-isys.h"
#include "ipu6-platform-isys-csi2-reg.h"
#include "ipu6-platform-regs.h"
static const char send_msg_types[N_IPU6_FW_ISYS_SEND_TYPE][32] = {
"STREAM_OPEN",
"STREAM_START",
"STREAM_START_AND_CAPTURE",
"STREAM_CAPTURE",
"STREAM_STOP",
"STREAM_FLUSH",
"STREAM_CLOSE"
};
static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id)
{
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_fw_isys_proxy_resp_info_abi *resp;
int ret;
resp = ipu6_recv_get_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES);
if (!resp)
return 1;
dev_dbg(dev, "Proxy response: id %u, error %u, details %u\n",
resp->request_id, resp->error_info.error,
resp->error_info.error_details);
ret = req_id == resp->request_id ? 0 : -EIO;
ipu6_recv_put_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES);
return ret;
}
int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys,
unsigned int req_id,
unsigned int index,
unsigned int offset, u32 value)
{
struct ipu6_fw_com_context *ctx = isys->fwcom;
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_fw_proxy_send_queue_token *token;
unsigned int timeout = 1000;
int ret;
dev_dbg(dev,
"proxy send: req_id 0x%x, index %d, offset 0x%x, value 0x%x\n",
req_id, index, offset, value);
token = ipu6_send_get_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES);
if (!token)
return -EBUSY;
token->request_id = req_id;
token->region_index = index;
token->offset = offset;
token->value = value;
ipu6_send_put_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES);
do {
usleep_range(100, 110);
ret = handle_proxy_response(isys, req_id);
if (!ret)
break;
if (ret == -EIO) {
dev_err(dev, "Proxy respond with unexpected id\n");
break;
}
timeout--;
} while (ret && timeout);
if (!timeout)
dev_err(dev, "Proxy response timed out\n");
return ret;
}
int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys,
const unsigned int stream_handle,
void *cpu_mapped_buf,
dma_addr_t dma_mapped_buf,
size_t size, u16 send_type)
{
struct ipu6_fw_com_context *ctx = isys->fwcom;
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_fw_send_queue_token *token;
if (send_type >= N_IPU6_FW_ISYS_SEND_TYPE)
return -EINVAL;
dev_dbg(dev, "send_token: %s\n", send_msg_types[send_type]);
/*
* Time to flush cache in case we have some payload. Not all messages
* have that
*/
if (cpu_mapped_buf)
clflush_cache_range(cpu_mapped_buf, size);
token = ipu6_send_get_token(ctx,
stream_handle + IPU6_BASE_MSG_SEND_QUEUES);
if (!token)
return -EBUSY;
token->payload = dma_mapped_buf;
token->buf_handle = (unsigned long)cpu_mapped_buf;
token->send_type = send_type;
ipu6_send_put_token(ctx, stream_handle + IPU6_BASE_MSG_SEND_QUEUES);
return 0;
}
int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys,
const unsigned int stream_handle, u16 send_type)
{
return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0,
send_type);
}
int ipu6_fw_isys_close(struct ipu6_isys *isys)
{
struct device *dev = &isys->adev->auxdev.dev;
int retry = IPU6_ISYS_CLOSE_RETRY;
unsigned long flags;
void *fwcom;
int ret;
/*
* Stop the isys fw. Actual close takes
* some time as the FW must stop its actions including code fetch
* to SP icache.
* spinlock to wait the interrupt handler to be finished
*/
spin_lock_irqsave(&isys->power_lock, flags);
ret = ipu6_fw_com_close(isys->fwcom);
fwcom = isys->fwcom;
isys->fwcom = NULL;
spin_unlock_irqrestore(&isys->power_lock, flags);
if (ret)
dev_err(dev, "Device close failure: %d\n", ret);
/* release probably fails if the close failed. Let's try still */
do {
usleep_range(400, 500);
ret = ipu6_fw_com_release(fwcom, 0);
retry--;
} while (ret && retry);
if (ret) {
dev_err(dev, "Device release time out %d\n", ret);
spin_lock_irqsave(&isys->power_lock, flags);
isys->fwcom = fwcom;
spin_unlock_irqrestore(&isys->power_lock, flags);
}
return ret;
}
void ipu6_fw_isys_cleanup(struct ipu6_isys *isys)
{
int ret;
ret = ipu6_fw_com_release(isys->fwcom, 1);
if (ret < 0)
dev_warn(&isys->adev->auxdev.dev,
"Device busy, fw_com release failed.");
isys->fwcom = NULL;
}
static void start_sp(struct ipu6_bus_device *adev)
{
struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
void __iomem *spc_regs_base = isys->pdata->base +
isys->pdata->ipdata->hw_variant.spc_offset;
u32 val = IPU6_ISYS_SPC_STATUS_START |
IPU6_ISYS_SPC_STATUS_RUN |
IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE;
val |= isys->icache_prefetch ? IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH : 0;
writel(val, spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL);
}
static int query_sp(struct ipu6_bus_device *adev)
{
struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
void __iomem *spc_regs_base = isys->pdata->base +
isys->pdata->ipdata->hw_variant.spc_offset;
u32 val;
val = readl(spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL);
/* return true when READY == 1, START == 0 */
val &= IPU6_ISYS_SPC_STATUS_READY | IPU6_ISYS_SPC_STATUS_START;
return val == IPU6_ISYS_SPC_STATUS_READY;
}
static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys,
struct ipu6_fw_com_cfg *fwcom,
unsigned int num_streams)
{
unsigned int max_send_queues, max_sram_blocks, max_devq_size;
struct ipu6_fw_syscom_queue_config *input_queue_cfg;
struct ipu6_fw_syscom_queue_config *output_queue_cfg;
struct device *dev = &isys->adev->auxdev.dev;
int type_proxy = IPU6_FW_ISYS_QUEUE_TYPE_PROXY;
int type_dev = IPU6_FW_ISYS_QUEUE_TYPE_DEV;
int type_msg = IPU6_FW_ISYS_QUEUE_TYPE_MSG;
int base_dev_send = IPU6_BASE_DEV_SEND_QUEUES;
int base_msg_send = IPU6_BASE_MSG_SEND_QUEUES;
int base_msg_recv = IPU6_BASE_MSG_RECV_QUEUES;
struct ipu6_fw_isys_fw_config *isys_fw_cfg;
u32 num_in_message_queues;
unsigned int max_streams;
unsigned int size;
unsigned int i;
max_streams = isys->pdata->ipdata->max_streams;
max_send_queues = isys->pdata->ipdata->max_send_queues;
max_sram_blocks = isys->pdata->ipdata->max_sram_blocks;
max_devq_size = isys->pdata->ipdata->max_devq_size;
num_in_message_queues = clamp(num_streams, 1U, max_streams);
isys_fw_cfg = devm_kzalloc(dev, sizeof(*isys_fw_cfg), GFP_KERNEL);
if (!isys_fw_cfg)
return -ENOMEM;
isys_fw_cfg->num_send_queues[type_proxy] = IPU6_N_MAX_PROXY_SEND_QUEUES;
isys_fw_cfg->num_send_queues[type_dev] = IPU6_N_MAX_DEV_SEND_QUEUES;
isys_fw_cfg->num_send_queues[type_msg] = num_in_message_queues;
isys_fw_cfg->num_recv_queues[type_proxy] = IPU6_N_MAX_PROXY_RECV_QUEUES;
/* Common msg/dev return queue */
isys_fw_cfg->num_recv_queues[type_dev] = 0;
isys_fw_cfg->num_recv_queues[type_msg] = 1;
size = sizeof(*input_queue_cfg) * max_send_queues;
input_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL);
if (!input_queue_cfg)
return -ENOMEM;
size = sizeof(*output_queue_cfg) * IPU6_N_MAX_RECV_QUEUES;
output_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL);
if (!output_queue_cfg)
return -ENOMEM;
fwcom->input = input_queue_cfg;
fwcom->output = output_queue_cfg;
fwcom->num_input_queues = isys_fw_cfg->num_send_queues[type_proxy] +
isys_fw_cfg->num_send_queues[type_dev] +
isys_fw_cfg->num_send_queues[type_msg];
fwcom->num_output_queues = isys_fw_cfg->num_recv_queues[type_proxy] +
isys_fw_cfg->num_recv_queues[type_dev] +
isys_fw_cfg->num_recv_queues[type_msg];
/* SRAM partitioning. Equal partitioning is set. */
for (i = 0; i < max_sram_blocks; i++) {
if (i < num_in_message_queues)
isys_fw_cfg->buffer_partition.num_gda_pages[i] =
(IPU6_DEVICE_GDA_NR_PAGES *
IPU6_DEVICE_GDA_VIRT_FACTOR) /
num_in_message_queues;
else
isys_fw_cfg->buffer_partition.num_gda_pages[i] = 0;
}
/* FW assumes proxy interface at fwcom queue 0 */
for (i = 0; i < isys_fw_cfg->num_send_queues[type_proxy]; i++) {
input_queue_cfg[i].token_size =
sizeof(struct ipu6_fw_proxy_send_queue_token);
input_queue_cfg[i].queue_size = IPU6_ISYS_SIZE_PROXY_SEND_QUEUE;
}
for (i = 0; i < isys_fw_cfg->num_send_queues[type_dev]; i++) {
input_queue_cfg[base_dev_send + i].token_size =
sizeof(struct ipu6_fw_send_queue_token);
input_queue_cfg[base_dev_send + i].queue_size = max_devq_size;
}
for (i = 0; i < isys_fw_cfg->num_send_queues[type_msg]; i++) {
input_queue_cfg[base_msg_send + i].token_size =
sizeof(struct ipu6_fw_send_queue_token);
input_queue_cfg[base_msg_send + i].queue_size =
IPU6_ISYS_SIZE_SEND_QUEUE;
}
for (i = 0; i < isys_fw_cfg->num_recv_queues[type_proxy]; i++) {
output_queue_cfg[i].token_size =
sizeof(struct ipu6_fw_proxy_resp_queue_token);
output_queue_cfg[i].queue_size =
IPU6_ISYS_SIZE_PROXY_RECV_QUEUE;
}
/* There is no recv DEV queue */
for (i = 0; i < isys_fw_cfg->num_recv_queues[type_msg]; i++) {
output_queue_cfg[base_msg_recv + i].token_size =
sizeof(struct ipu6_fw_resp_queue_token);
output_queue_cfg[base_msg_recv + i].queue_size =
IPU6_ISYS_SIZE_RECV_QUEUE;
}
fwcom->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset;
fwcom->specific_addr = isys_fw_cfg;
fwcom->specific_size = sizeof(*isys_fw_cfg);
return 0;
}
int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams)
{
struct device *dev = &isys->adev->auxdev.dev;
int retry = IPU6_ISYS_OPEN_RETRY;
struct ipu6_fw_com_cfg fwcom = {
.cell_start = start_sp,
.cell_ready = query_sp,
.buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET,
};
int ret;
ipu6_isys_fwcom_cfg_init(isys, &fwcom, num_streams);
isys->fwcom = ipu6_fw_com_prepare(&fwcom, isys->adev,
isys->pdata->base);
if (!isys->fwcom) {
dev_err(dev, "isys fw com prepare failed\n");
return -EIO;
}
ret = ipu6_fw_com_open(isys->fwcom);
if (ret) {
dev_err(dev, "isys fw com open failed %d\n", ret);
return ret;
}
do {
usleep_range(400, 500);
if (ipu6_fw_com_ready(isys->fwcom))
break;
retry--;
} while (retry > 0);
if (!retry) {
dev_err(dev, "isys port open ready failed %d\n", ret);
ipu6_fw_isys_close(isys);
ret = -EIO;
}
return ret;
}
struct ipu6_fw_isys_resp_info_abi *
ipu6_fw_isys_get_resp(void *context, unsigned int queue)
{
return ipu6_recv_get_token(context, queue);
}
void ipu6_fw_isys_put_resp(void *context, unsigned int queue)
{
ipu6_recv_put_token(context, queue);
}
void ipu6_fw_isys_dump_stream_cfg(struct device *dev,
struct ipu6_fw_isys_stream_cfg_data_abi *cfg)
{
unsigned int i;
dev_dbg(dev, "-----------------------------------------------------\n");
dev_dbg(dev, "IPU6_FW_ISYS_STREAM_CFG_DATA\n");
dev_dbg(dev, "compfmt = %d\n", cfg->vc);
dev_dbg(dev, "src = %d\n", cfg->src);
dev_dbg(dev, "vc = %d\n", cfg->vc);
dev_dbg(dev, "isl_use = %d\n", cfg->isl_use);
dev_dbg(dev, "sensor_type = %d\n", cfg->sensor_type);
dev_dbg(dev, "send_irq_sof_discarded = %d\n",
cfg->send_irq_sof_discarded);
dev_dbg(dev, "send_irq_eof_discarded = %d\n",
cfg->send_irq_eof_discarded);
dev_dbg(dev, "send_resp_sof_discarded = %d\n",
cfg->send_resp_sof_discarded);
dev_dbg(dev, "send_resp_eof_discarded = %d\n",
cfg->send_resp_eof_discarded);
dev_dbg(dev, "crop:\n");
dev_dbg(dev, "\t.left_top = [%d, %d]\n", cfg->crop.left_offset,
cfg->crop.top_offset);
dev_dbg(dev, "\t.right_bottom = [%d, %d]\n", cfg->crop.right_offset,
cfg->crop.bottom_offset);
dev_dbg(dev, "nof_input_pins = %d\n", cfg->nof_input_pins);
for (i = 0; i < cfg->nof_input_pins; i++) {
dev_dbg(dev, "input pin[%d]:\n", i);
dev_dbg(dev, "\t.dt = 0x%0x\n", cfg->input_pins[i].dt);
dev_dbg(dev, "\t.mipi_store_mode = %d\n",
cfg->input_pins[i].mipi_store_mode);
dev_dbg(dev, "\t.bits_per_pix = %d\n",
cfg->input_pins[i].bits_per_pix);
dev_dbg(dev, "\t.mapped_dt = 0x%0x\n",
cfg->input_pins[i].mapped_dt);
dev_dbg(dev, "\t.input_res = %dx%d\n",
cfg->input_pins[i].input_res.width,
cfg->input_pins[i].input_res.height);
dev_dbg(dev, "\t.mipi_decompression = %d\n",
cfg->input_pins[i].mipi_decompression);
dev_dbg(dev, "\t.capture_mode = %d\n",
cfg->input_pins[i].capture_mode);
}
dev_dbg(dev, "nof_output_pins = %d\n", cfg->nof_output_pins);
for (i = 0; i < cfg->nof_output_pins; i++) {
dev_dbg(dev, "output_pin[%d]:\n", i);
dev_dbg(dev, "\t.input_pin_id = %d\n",
cfg->output_pins[i].input_pin_id);
dev_dbg(dev, "\t.output_res = %dx%d\n",
cfg->output_pins[i].output_res.width,
cfg->output_pins[i].output_res.height);
dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride);
dev_dbg(dev, "\t.pt = %d\n", cfg->output_pins[i].pt);
dev_dbg(dev, "\t.payload_buf_size = %d\n",
cfg->output_pins[i].payload_buf_size);
dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft);
dev_dbg(dev, "\t.watermark_in_lines = %d\n",
cfg->output_pins[i].watermark_in_lines);
dev_dbg(dev, "\t.send_irq = %d\n",
cfg->output_pins[i].send_irq);
dev_dbg(dev, "\t.reserve_compression = %d\n",
cfg->output_pins[i].reserve_compression);
dev_dbg(dev, "\t.snoopable = %d\n",
cfg->output_pins[i].snoopable);
dev_dbg(dev, "\t.error_handling_enable = %d\n",
cfg->output_pins[i].error_handling_enable);
dev_dbg(dev, "\t.sensor_type = %d\n",
cfg->output_pins[i].sensor_type);
}
dev_dbg(dev, "-----------------------------------------------------\n");
}
void
ipu6_fw_isys_dump_frame_buff_set(struct device *dev,
struct ipu6_fw_isys_frame_buff_set_abi *buf,
unsigned int outputs)
{
unsigned int i;
dev_dbg(dev, "-----------------------------------------------------\n");
dev_dbg(dev, "IPU6_FW_ISYS_FRAME_BUFF_SET\n");
for (i = 0; i < outputs; i++) {
dev_dbg(dev, "output_pin[%d]:\n", i);
dev_dbg(dev, "\t.out_buf_id = %llu\n",
buf->output_pins[i].out_buf_id);
dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr);
dev_dbg(dev, "\t.compress = %d\n",
buf->output_pins[i].compress);
}
dev_dbg(dev, "send_irq_sof = 0x%x\n", buf->send_irq_sof);
dev_dbg(dev, "send_irq_eof = 0x%x\n", buf->send_irq_eof);
dev_dbg(dev, "send_resp_sof = 0x%x\n", buf->send_resp_sof);
dev_dbg(dev, "send_resp_eof = 0x%x\n", buf->send_resp_eof);
dev_dbg(dev, "send_irq_capture_ack = 0x%x\n",
buf->send_irq_capture_ack);
dev_dbg(dev, "send_irq_capture_done = 0x%x\n",
buf->send_irq_capture_done);
dev_dbg(dev, "send_resp_capture_ack = 0x%x\n",
buf->send_resp_capture_ack);
dev_dbg(dev, "send_resp_capture_done = 0x%x\n",
buf->send_resp_capture_done);
dev_dbg(dev, "-----------------------------------------------------\n");
}
@@ -0,0 +1,596 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_FW_ISYS_H
#define IPU6_FW_ISYS_H
#include <linux/types.h>
struct device;
struct ipu6_isys;
/* Max number of Input/Output Pins */
#define IPU6_MAX_IPINS 4
#define IPU6_MAX_OPINS ((IPU6_MAX_IPINS) + 1)
#define IPU6_STREAM_ID_MAX 16
#define IPU6_NONSECURE_STREAM_ID_MAX 12
#define IPU6_DEV_SEND_QUEUE_SIZE (IPU6_STREAM_ID_MAX)
#define IPU6_NOF_SRAM_BLOCKS_MAX (IPU6_STREAM_ID_MAX)
#define IPU6_N_MAX_MSG_SEND_QUEUES (IPU6_STREAM_ID_MAX)
#define IPU6SE_STREAM_ID_MAX 8
#define IPU6SE_NONSECURE_STREAM_ID_MAX 4
#define IPU6SE_DEV_SEND_QUEUE_SIZE (IPU6SE_STREAM_ID_MAX)
#define IPU6SE_NOF_SRAM_BLOCKS_MAX (IPU6SE_STREAM_ID_MAX)
#define IPU6SE_N_MAX_MSG_SEND_QUEUES (IPU6SE_STREAM_ID_MAX)
/* Single return queue for all streams/commands type */
#define IPU6_N_MAX_MSG_RECV_QUEUES 1
/* Single device queue for high priority commands (bypass in-order queue) */
#define IPU6_N_MAX_DEV_SEND_QUEUES 1
/* Single dedicated send queue for proxy interface */
#define IPU6_N_MAX_PROXY_SEND_QUEUES 1
/* Single dedicated recv queue for proxy interface */
#define IPU6_N_MAX_PROXY_RECV_QUEUES 1
/* Send queues layout */
#define IPU6_BASE_PROXY_SEND_QUEUES 0
#define IPU6_BASE_DEV_SEND_QUEUES \
(IPU6_BASE_PROXY_SEND_QUEUES + IPU6_N_MAX_PROXY_SEND_QUEUES)
#define IPU6_BASE_MSG_SEND_QUEUES \
(IPU6_BASE_DEV_SEND_QUEUES + IPU6_N_MAX_DEV_SEND_QUEUES)
/* Recv queues layout */
#define IPU6_BASE_PROXY_RECV_QUEUES 0
#define IPU6_BASE_MSG_RECV_QUEUES \
(IPU6_BASE_PROXY_RECV_QUEUES + IPU6_N_MAX_PROXY_RECV_QUEUES)
#define IPU6_N_MAX_RECV_QUEUES \
(IPU6_BASE_MSG_RECV_QUEUES + IPU6_N_MAX_MSG_RECV_QUEUES)
#define IPU6_N_MAX_SEND_QUEUES \
(IPU6_BASE_MSG_SEND_QUEUES + IPU6_N_MAX_MSG_SEND_QUEUES)
#define IPU6SE_N_MAX_SEND_QUEUES \
(IPU6_BASE_MSG_SEND_QUEUES + IPU6SE_N_MAX_MSG_SEND_QUEUES)
/* Max number of planes for frame formats supported by the FW */
#define IPU6_PIN_PLANES_MAX 4
#define IPU6_FW_ISYS_SENSOR_TYPE_START 14
#define IPU6_FW_ISYS_SENSOR_TYPE_END 19
#define IPU6SE_FW_ISYS_SENSOR_TYPE_START 6
#define IPU6SE_FW_ISYS_SENSOR_TYPE_END 11
/*
* Device close takes some time from last ack message to actual stopping
* of the SP processor. As long as the SP processor runs we can't proceed with
* clean up of resources.
*/
#define IPU6_ISYS_OPEN_RETRY 2000
#define IPU6_ISYS_CLOSE_RETRY 2000
#define IPU6_FW_CALL_TIMEOUT_JIFFIES msecs_to_jiffies(2000)
enum ipu6_fw_isys_resp_type {
IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE = 0,
IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK,
IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY,
IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_WATERMARK,
IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF,
IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF,
IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE,
IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE,
IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_SKIPPED,
IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_SKIPPED,
IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF_DISCARDED,
IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF_DISCARDED,
IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY,
N_IPU6_FW_ISYS_RESP_TYPE
};
enum ipu6_fw_isys_send_type {
IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN = 0,
IPU6_FW_ISYS_SEND_TYPE_STREAM_START,
IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE,
IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE,
IPU6_FW_ISYS_SEND_TYPE_STREAM_STOP,
IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH,
IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE,
N_IPU6_FW_ISYS_SEND_TYPE
};
enum ipu6_fw_isys_queue_type {
IPU6_FW_ISYS_QUEUE_TYPE_PROXY = 0,
IPU6_FW_ISYS_QUEUE_TYPE_DEV,
IPU6_FW_ISYS_QUEUE_TYPE_MSG,
N_IPU6_FW_ISYS_QUEUE_TYPE
};
enum ipu6_fw_isys_stream_source {
IPU6_FW_ISYS_STREAM_SRC_PORT_0 = 0,
IPU6_FW_ISYS_STREAM_SRC_PORT_1,
IPU6_FW_ISYS_STREAM_SRC_PORT_2,
IPU6_FW_ISYS_STREAM_SRC_PORT_3,
IPU6_FW_ISYS_STREAM_SRC_PORT_4,
IPU6_FW_ISYS_STREAM_SRC_PORT_5,
IPU6_FW_ISYS_STREAM_SRC_PORT_6,
IPU6_FW_ISYS_STREAM_SRC_PORT_7,
IPU6_FW_ISYS_STREAM_SRC_PORT_8,
IPU6_FW_ISYS_STREAM_SRC_PORT_9,
IPU6_FW_ISYS_STREAM_SRC_PORT_10,
IPU6_FW_ISYS_STREAM_SRC_PORT_11,
IPU6_FW_ISYS_STREAM_SRC_PORT_12,
IPU6_FW_ISYS_STREAM_SRC_PORT_13,
IPU6_FW_ISYS_STREAM_SRC_PORT_14,
IPU6_FW_ISYS_STREAM_SRC_PORT_15,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_2,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_3,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_4,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_5,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_6,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_7,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_8,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_9,
N_IPU6_FW_ISYS_STREAM_SRC
};
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 IPU6_FW_ISYS_STREAM_SRC_PORT_0
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT1 IPU6_FW_ISYS_STREAM_SRC_PORT_1
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT2 IPU6_FW_ISYS_STREAM_SRC_PORT_2
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT3 IPU6_FW_ISYS_STREAM_SRC_PORT_3
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTA IPU6_FW_ISYS_STREAM_SRC_PORT_4
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTB IPU6_FW_ISYS_STREAM_SRC_PORT_5
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT0 \
IPU6_FW_ISYS_STREAM_SRC_PORT_6
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT1 \
IPU6_FW_ISYS_STREAM_SRC_PORT_7
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT2 \
IPU6_FW_ISYS_STREAM_SRC_PORT_8
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT3 \
IPU6_FW_ISYS_STREAM_SRC_PORT_9
#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT0 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0
#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT1 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1
/*
* enum ipu6_fw_isys_mipi_vc: MIPI csi2 spec
* supports up to 4 virtual per physical channel
*/
enum ipu6_fw_isys_mipi_vc {
IPU6_FW_ISYS_MIPI_VC_0 = 0,
IPU6_FW_ISYS_MIPI_VC_1,
IPU6_FW_ISYS_MIPI_VC_2,
IPU6_FW_ISYS_MIPI_VC_3,
N_IPU6_FW_ISYS_MIPI_VC
};
enum ipu6_fw_isys_frame_format_type {
IPU6_FW_ISYS_FRAME_FORMAT_NV11 = 0, /* 12 bit YUV 411, Y, UV plane */
IPU6_FW_ISYS_FRAME_FORMAT_NV12, /* 12 bit YUV 420, Y, UV plane */
IPU6_FW_ISYS_FRAME_FORMAT_NV12_16, /* 16 bit YUV 420, Y, UV plane */
/* 12 bit YUV 420, Intel proprietary tiled format */
IPU6_FW_ISYS_FRAME_FORMAT_NV12_TILEY,
IPU6_FW_ISYS_FRAME_FORMAT_NV16, /* 16 bit YUV 422, Y, UV plane */
IPU6_FW_ISYS_FRAME_FORMAT_NV21, /* 12 bit YUV 420, Y, VU plane */
IPU6_FW_ISYS_FRAME_FORMAT_NV61, /* 16 bit YUV 422, Y, VU plane */
IPU6_FW_ISYS_FRAME_FORMAT_YV12, /* 12 bit YUV 420, Y, V, U plane */
IPU6_FW_ISYS_FRAME_FORMAT_YV16, /* 16 bit YUV 422, Y, V, U plane */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420, /* 12 bit YUV 420, Y, U, V plane */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420_10, /* yuv420, 10 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420_12, /* yuv420, 12 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420_14, /* yuv420, 14 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420_16, /* yuv420, 16 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_YUV422, /* 16 bit YUV 422, Y, U, V plane */
IPU6_FW_ISYS_FRAME_FORMAT_YUV422_16, /* yuv422, 16 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_UYVY, /* 16 bit YUV 422, UYVY interleaved */
IPU6_FW_ISYS_FRAME_FORMAT_YUYV, /* 16 bit YUV 422, YUYV interleaved */
IPU6_FW_ISYS_FRAME_FORMAT_YUV444, /* 24 bit YUV 444, Y, U, V plane */
/* Internal format, 2 y lines followed by a uvinterleaved line */
IPU6_FW_ISYS_FRAME_FORMAT_YUV_LINE,
IPU6_FW_ISYS_FRAME_FORMAT_RAW8, /* RAW8, 1 plane */
IPU6_FW_ISYS_FRAME_FORMAT_RAW10, /* RAW10, 1 plane */
IPU6_FW_ISYS_FRAME_FORMAT_RAW12, /* RAW12, 1 plane */
IPU6_FW_ISYS_FRAME_FORMAT_RAW14, /* RAW14, 1 plane */
IPU6_FW_ISYS_FRAME_FORMAT_RAW16, /* RAW16, 1 plane */
/**
* 16 bit RGB, 1 plane. Each 3 sub pixels are packed into one 16 bit
* value, 5 bits for R, 6 bits for G and 5 bits for B.
*/
IPU6_FW_ISYS_FRAME_FORMAT_RGB565,
IPU6_FW_ISYS_FRAME_FORMAT_PLANAR_RGB888, /* 24 bit RGB, 3 planes */
IPU6_FW_ISYS_FRAME_FORMAT_RGBA888, /* 32 bit RGBA, 1 plane, A=Alpha */
IPU6_FW_ISYS_FRAME_FORMAT_QPLANE6, /* Internal, for advanced ISP */
IPU6_FW_ISYS_FRAME_FORMAT_BINARY_8, /* byte stream, used for jpeg. */
N_IPU6_FW_ISYS_FRAME_FORMAT
};
enum ipu6_fw_isys_pin_type {
/* captured as MIPI packets */
IPU6_FW_ISYS_PIN_TYPE_MIPI = 0,
/* captured through the SoC path */
IPU6_FW_ISYS_PIN_TYPE_RAW_SOC = 3,
};
/*
* enum ipu6_fw_isys_mipi_store_mode. Describes if long MIPI packets reach
* MIPI SRAM with the long packet header or
* if not, then only option is to capture it with pin type MIPI.
*/
enum ipu6_fw_isys_mipi_store_mode {
IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL = 0,
IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER,
N_IPU6_FW_ISYS_MIPI_STORE_MODE
};
enum ipu6_fw_isys_capture_mode {
IPU6_FW_ISYS_CAPTURE_MODE_REGULAR = 0,
IPU6_FW_ISYS_CAPTURE_MODE_BURST,
N_IPU6_FW_ISYS_CAPTURE_MODE,
};
enum ipu6_fw_isys_sensor_mode {
IPU6_FW_ISYS_SENSOR_MODE_NORMAL = 0,
IPU6_FW_ISYS_SENSOR_MODE_TOBII,
N_IPU6_FW_ISYS_SENSOR_MODE,
};
enum ipu6_fw_isys_error {
IPU6_FW_ISYS_ERROR_NONE = 0,
IPU6_FW_ISYS_ERROR_FW_INTERNAL_CONSISTENCY,
IPU6_FW_ISYS_ERROR_HW_CONSISTENCY,
IPU6_FW_ISYS_ERROR_DRIVER_INVALID_COMMAND_SEQUENCE,
IPU6_FW_ISYS_ERROR_DRIVER_INVALID_DEVICE_CONFIGURATION,
IPU6_FW_ISYS_ERROR_DRIVER_INVALID_STREAM_CONFIGURATION,
IPU6_FW_ISYS_ERROR_DRIVER_INVALID_FRAME_CONFIGURATION,
IPU6_FW_ISYS_ERROR_INSUFFICIENT_RESOURCES,
IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO,
IPU6_FW_ISYS_ERROR_HW_REPORTED_SIG2CIO,
IPU6_FW_ISYS_ERROR_SENSOR_FW_SYNC,
IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION,
IPU6_FW_ISYS_ERROR_RESPONSE_QUEUE_FULL,
N_IPU6_FW_ISYS_ERROR
};
enum ipu6_fw_proxy_error {
IPU6_FW_PROXY_ERROR_NONE = 0,
IPU6_FW_PROXY_ERROR_INVALID_WRITE_REGION,
IPU6_FW_PROXY_ERROR_INVALID_WRITE_OFFSET,
N_IPU6_FW_PROXY_ERROR
};
/* firmware ABI structure below are aligned in firmware, no need pack */
struct ipu6_fw_isys_buffer_partition_abi {
u32 num_gda_pages[IPU6_STREAM_ID_MAX];
};
struct ipu6_fw_isys_fw_config {
struct ipu6_fw_isys_buffer_partition_abi buffer_partition;
u32 num_send_queues[N_IPU6_FW_ISYS_QUEUE_TYPE];
u32 num_recv_queues[N_IPU6_FW_ISYS_QUEUE_TYPE];
};
/*
* struct ipu6_fw_isys_resolution_abi: Generic resolution structure.
*/
struct ipu6_fw_isys_resolution_abi {
u32 width;
u32 height;
};
/**
* struct ipu6_fw_isys_output_pin_payload_abi - ISYS output pin buffer
* @out_buf_id: Points to output pin buffer - buffer identifier
* @addr: Points to output pin buffer - CSS Virtual Address
* @compress: Request frame compression (1), or not (0)
*/
struct ipu6_fw_isys_output_pin_payload_abi {
u64 out_buf_id;
u32 addr;
u32 compress;
};
/**
* struct ipu6_fw_isys_output_pin_info_abi - ISYS output pin info
* @output_res: output pin resolution
* @stride: output stride in Bytes (not valid for statistics)
* @watermark_in_lines: pin watermark level in lines
* @payload_buf_size: minimum size in Bytes of all buffers that will be
* supplied for capture on this pin
* @ts_offsets: ts_offsets
* @s2m_pixel_soc_pixel_remapping: pixel soc remapping (see the definition of
* S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING)
* @csi_be_soc_pixel_remapping: see s2m_pixel_soc_pixel_remapping
* @send_irq: assert if pin event should trigger irq
* @input_pin_id: related input pin id
* @pt: pin type -real format "enum ipu6_fw_isys_pin_type"
* @ft: frame format type -real format "enum ipu6_fw_isys_frame_format_type"
* @reserved: a reserved field
* @reserve_compression: reserve compression resources for pin
* @snoopable: snoopable
* @error_handling_enable: enable error handling
* @sensor_type: sensor_type
*/
struct ipu6_fw_isys_output_pin_info_abi {
struct ipu6_fw_isys_resolution_abi output_res;
u32 stride;
u32 watermark_in_lines;
u32 payload_buf_size;
u32 ts_offsets[IPU6_PIN_PLANES_MAX];
u32 s2m_pixel_soc_pixel_remapping;
u32 csi_be_soc_pixel_remapping;
u8 send_irq;
u8 input_pin_id;
u8 pt;
u8 ft;
u8 reserved;
u8 reserve_compression;
u8 snoopable;
u8 error_handling_enable;
u32 sensor_type;
};
/**
* struct ipu6_fw_isys_input_pin_info_abi - ISYS input pin info
* @input_res: input resolution
* @dt: mipi data type ((enum ipu6_fw_isys_mipi_data_type)
* @mipi_store_mode: defines if legacy long packet header will be stored or
* discarded if discarded, output pin type for this
* input pin can only be MIPI
* (enum ipu6_fw_isys_mipi_store_mode)
* @bits_per_pix: native bits per pixel
* @mapped_dt: actual data type from sensor
* @mipi_decompression: defines which compression will be in mipi backend
* @crop_first_and_last_lines: Control whether to crop the first and last line
* of the input image. Crop done by HW device.
* @capture_mode: mode of capture, regular or burst, default value is regular
* @reserved: a reserved field
*/
struct ipu6_fw_isys_input_pin_info_abi {
struct ipu6_fw_isys_resolution_abi input_res;
u8 dt;
u8 mipi_store_mode;
u8 bits_per_pix;
u8 mapped_dt;
u8 mipi_decompression;
u8 crop_first_and_last_lines;
u8 capture_mode;
u8 reserved;
};
/**
* struct ipu6_fw_isys_cropping_abi - ISYS cropping coordinates
* @top_offset: Top offset
* @left_offset: Left offset
* @bottom_offset: Bottom offset
* @right_offset: Right offset
*/
struct ipu6_fw_isys_cropping_abi {
s32 top_offset;
s32 left_offset;
s32 bottom_offset;
s32 right_offset;
};
/**
* struct ipu6_fw_isys_stream_cfg_data_abi - ISYS stream configuration data
* ISYS stream configuration data structure
* @crop: for extended use and is not used in FW currently
* @input_pins: input pin descriptors
* @output_pins: output pin descriptors
* @compfmt: de-compression setting for User Defined Data
* @nof_input_pins: number of input pins
* @nof_output_pins: number of output pins
* @send_irq_sof_discarded: send irq on discarded frame sof response
* - if '1' it will override the send_resp_sof_discarded
* and send the response
* - if '0' the send_resp_sof_discarded will determine
* whether to send the response
* @send_irq_eof_discarded: send irq on discarded frame eof response
* - if '1' it will override the send_resp_eof_discarded
* and send the response
* - if '0' the send_resp_eof_discarded will determine
* whether to send the response
* @send_resp_sof_discarded: send response for discarded frame sof detected,
* used only when send_irq_sof_discarded is '0'
* @send_resp_eof_discarded: send response for discarded frame eof detected,
* used only when send_irq_eof_discarded is '0'
* @src: Stream source index e.g. MIPI_generator_0, CSI2-rx_1
* @vc: MIPI Virtual Channel (up to 4 virtual per physical channel)
* @isl_use: indicates whether stream requires ISL and how
* @sensor_type: type of connected sensor, tobii or others, default is 0
* @reserved: a reserved field
* @reserved2: a reserved field
*/
struct ipu6_fw_isys_stream_cfg_data_abi {
struct ipu6_fw_isys_cropping_abi crop;
struct ipu6_fw_isys_input_pin_info_abi input_pins[IPU6_MAX_IPINS];
struct ipu6_fw_isys_output_pin_info_abi output_pins[IPU6_MAX_OPINS];
u32 compfmt;
u8 nof_input_pins;
u8 nof_output_pins;
u8 send_irq_sof_discarded;
u8 send_irq_eof_discarded;
u8 send_resp_sof_discarded;
u8 send_resp_eof_discarded;
u8 src;
u8 vc;
u8 isl_use;
u8 sensor_type;
u8 reserved;
u8 reserved2;
};
/**
* struct ipu6_fw_isys_frame_buff_set_abi - ISYS frame buffer set (request)
* @output_pins: output pin addresses
* @send_irq_sof: send irq on frame sof response
* - if '1' it will override the send_resp_sof and
* send the response
* - if '0' the send_resp_sof will determine whether to
* send the response
* @send_irq_eof: send irq on frame eof response
* - if '1' it will override the send_resp_eof and
* send the response
* - if '0' the send_resp_eof will determine whether to
* send the response
* @send_irq_capture_ack: send irq on capture ack
* @send_irq_capture_done: send irq on capture done
* @send_resp_sof: send response for frame sof detected,
* used only when send_irq_sof is '0'
* @send_resp_eof: send response for frame eof detected,
* used only when send_irq_eof is '0'
* @send_resp_capture_ack: send response for capture ack event
* @send_resp_capture_done: send response for capture done event
* @reserved: a reserved field
*/
struct ipu6_fw_isys_frame_buff_set_abi {
struct ipu6_fw_isys_output_pin_payload_abi output_pins[IPU6_MAX_OPINS];
u8 send_irq_sof;
u8 send_irq_eof;
u8 send_irq_capture_ack;
u8 send_irq_capture_done;
u8 send_resp_sof;
u8 send_resp_eof;
u8 send_resp_capture_ack;
u8 send_resp_capture_done;
u8 reserved[8];
};
/**
* struct ipu6_fw_isys_error_info_abi - ISYS error information
* @error: error code if something went wrong
* @error_details: depending on error code, it may contain additional error info
*/
struct ipu6_fw_isys_error_info_abi {
u32 error;
u32 error_details;
};
/**
* struct ipu6_fw_isys_resp_info_abi - ISYS firmware response
* @buf_id: buffer ID
* @pin: this var is only valid for pin event related responses,
* contains pin addresses
* @error_info: error information from the FW
* @timestamp: Time information for event if available
* @stream_handle: stream id the response corresponds to
* @type: response type (enum ipu6_fw_isys_resp_type)
* @pin_id: pin id that the pin payload corresponds to
* @reserved: a reserved field
* @reserved2: a reserved field
*/
struct ipu6_fw_isys_resp_info_abi {
u64 buf_id;
struct ipu6_fw_isys_output_pin_payload_abi pin;
struct ipu6_fw_isys_error_info_abi error_info;
u32 timestamp[2];
u8 stream_handle;
u8 type;
u8 pin_id;
u8 reserved;
u32 reserved2;
};
/**
* struct ipu6_fw_isys_proxy_error_info_abi - ISYS proxy error
* @error: error code if something went wrong
* @error_details: depending on error code, it may contain additional error info
*/
struct ipu6_fw_isys_proxy_error_info_abi {
u32 error;
u32 error_details;
};
struct ipu6_fw_isys_proxy_resp_info_abi {
u32 request_id;
struct ipu6_fw_isys_proxy_error_info_abi error_info;
};
/**
* struct ipu6_fw_proxy_write_queue_token - ISYS proxy write queue token
* @request_id: update id for the specific proxy write request
* @region_index: Region id for the proxy write request
* @offset: Offset of the write request according to the base address
* of the region
* @value: Value that is requested to be written with the proxy write request
*/
struct ipu6_fw_proxy_write_queue_token {
u32 request_id;
u32 region_index;
u32 offset;
u32 value;
};
/**
* struct ipu6_fw_resp_queue_token - ISYS response queue token
* @resp_info: response info
*/
struct ipu6_fw_resp_queue_token {
struct ipu6_fw_isys_resp_info_abi resp_info;
};
/**
* struct ipu6_fw_send_queue_token - ISYS send queue token
* @buf_handle: buffer handle
* @payload: payload
* @send_type: send_type
* @stream_id: stream_id
*/
struct ipu6_fw_send_queue_token {
u64 buf_handle;
u32 payload;
u16 send_type;
u16 stream_id;
};
/**
* struct ipu6_fw_proxy_resp_queue_token - ISYS proxy response queue token
* @proxy_resp_info: proxy response info
*/
struct ipu6_fw_proxy_resp_queue_token {
struct ipu6_fw_isys_proxy_resp_info_abi proxy_resp_info;
};
/**
* struct ipu6_fw_proxy_send_queue_token - SYS proxy send queue token
* @request_id: request_id
* @region_index: region_index
* @offset: offset
* @value: value
*/
struct ipu6_fw_proxy_send_queue_token {
u32 request_id;
u32 region_index;
u32 offset;
u32 value;
};
void
ipu6_fw_isys_dump_stream_cfg(struct device *dev,
struct ipu6_fw_isys_stream_cfg_data_abi *cfg);
void
ipu6_fw_isys_dump_frame_buff_set(struct device *dev,
struct ipu6_fw_isys_frame_buff_set_abi *buf,
unsigned int outputs);
int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams);
int ipu6_fw_isys_close(struct ipu6_isys *isys);
int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys,
const unsigned int stream_handle, u16 send_type);
int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys,
const unsigned int stream_handle,
void *cpu_mapped_buf, dma_addr_t dma_mapped_buf,
size_t size, u16 send_type);
int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys,
unsigned int req_id,
unsigned int index,
unsigned int offset, u32 value);
void ipu6_fw_isys_cleanup(struct ipu6_isys *isys);
struct ipu6_fw_isys_resp_info_abi *
ipu6_fw_isys_get_resp(void *context, unsigned int queue);
void ipu6_fw_isys_put_resp(void *context, unsigned int queue);
#endif
@@ -0,0 +1,647 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/atomic.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/minmax.h>
#include <linux/sprintf.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-subdev.h>
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-isys-csi2.h"
#include "ipu6-isys-subdev.h"
#include "ipu6-platform-isys-csi2-reg.h"
static const u32 csi2_supported_codes[] = {
MEDIA_BUS_FMT_RGB565_1X16,
MEDIA_BUS_FMT_RGB888_1X24,
MEDIA_BUS_FMT_UYVY8_1X16,
MEDIA_BUS_FMT_YUYV8_1X16,
MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_SGBRG10_1X10,
MEDIA_BUS_FMT_SGRBG10_1X10,
MEDIA_BUS_FMT_SRGGB10_1X10,
MEDIA_BUS_FMT_SBGGR12_1X12,
MEDIA_BUS_FMT_SGBRG12_1X12,
MEDIA_BUS_FMT_SGRBG12_1X12,
MEDIA_BUS_FMT_SRGGB12_1X12,
MEDIA_BUS_FMT_SBGGR8_1X8,
MEDIA_BUS_FMT_SGBRG8_1X8,
MEDIA_BUS_FMT_SGRBG8_1X8,
MEDIA_BUS_FMT_SRGGB8_1X8,
MEDIA_BUS_FMT_Y8_1X8,
MEDIA_BUS_FMT_Y10_1X10,
MEDIA_BUS_FMT_Y12_1X12,
MEDIA_BUS_FMT_Y16_1X16,
MEDIA_BUS_FMT_META_8,
MEDIA_BUS_FMT_META_10,
MEDIA_BUS_FMT_META_12,
MEDIA_BUS_FMT_META_16,
MEDIA_BUS_FMT_META_24,
0
};
/*
* Strings corresponding to CSI-2 receiver errors are here.
* Corresponding macros are defined in the header file.
*/
static const struct ipu6_csi2_error dphy_rx_errors[] = {
{ "Single packet header error corrected", true },
{ "Multiple packet header errors detected", true },
{ "Payload checksum (CRC) error", true },
{ "Transfer FIFO overflow", false },
{ "Reserved short packet data type detected", true },
{ "Reserved long packet data type detected", true },
{ "Incomplete long packet detected", false },
{ "Frame sync error", false },
{ "Line sync error", false },
{ "DPHY recoverable synchronization error", true },
{ "DPHY fatal error", false },
{ "DPHY elastic FIFO overflow", false },
{ "Inter-frame short packet discarded", true },
{ "Inter-frame long packet discarded", true },
{ "MIPI pktgen overflow", false },
{ "MIPI pktgen data loss", false },
{ "FIFO overflow", false },
{ "Lane deskew", false },
{ "SOT sync error", false },
{ "HSIDLE detected", false }
};
s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2)
{
struct media_pad *src_pad;
if (!csi2)
return -EINVAL;
src_pad = media_pad_remote_pad_unique(&csi2->asd.sd.entity.pads[CSI2_PAD_SINK]);
if (IS_ERR(src_pad)) {
dev_err(&csi2->isys->adev->auxdev.dev,
"can't get source pad of %s (%pe)\n",
csi2->asd.sd.name, src_pad);
return PTR_ERR(src_pad);
}
return v4l2_get_link_freq(src_pad, 0, 0);
}
static int csi2_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd);
struct device *dev = &csi2->isys->adev->auxdev.dev;
dev_dbg(dev, "csi2 subscribe event(type %u id %u)\n",
sub->type, sub->id);
switch (sub->type) {
case V4L2_EVENT_FRAME_SYNC:
return v4l2_event_subscribe(fh, sub, 10, NULL);
case V4L2_EVENT_CTRL:
return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub);
default:
return -EINVAL;
}
}
static const struct v4l2_subdev_core_ops csi2_sd_core_ops = {
.subscribe_event = csi2_subscribe_event,
.unsubscribe_event = v4l2_event_subdev_unsubscribe,
};
/*
* The input system CSI2+ receiver has several
* parameters affecting the receiver timings. These depend
* on the MIPI bus frequency F in Hz (sensor transmitter rate)
* as follows:
* register value = (A/1e9 + B * UI) / COUNT_ACC
* where
* UI = 1 / (2 * F) in seconds
* COUNT_ACC = counter accuracy in seconds
* COUNT_ACC = 0.125 ns = 1 / 8 ns, ACCINV = 8.
*
* A and B are coefficients from the table below,
* depending whether the register minimum or maximum value is
* calculated.
* Minimum Maximum
* Clock lane A B A B
* reg_rx_csi_dly_cnt_termen_clane 0 0 38 0
* reg_rx_csi_dly_cnt_settle_clane 95 -8 300 -16
* Data lanes
* reg_rx_csi_dly_cnt_termen_dlane0 0 0 35 4
* reg_rx_csi_dly_cnt_settle_dlane0 85 -2 145 -6
* reg_rx_csi_dly_cnt_termen_dlane1 0 0 35 4
* reg_rx_csi_dly_cnt_settle_dlane1 85 -2 145 -6
* reg_rx_csi_dly_cnt_termen_dlane2 0 0 35 4
* reg_rx_csi_dly_cnt_settle_dlane2 85 -2 145 -6
* reg_rx_csi_dly_cnt_termen_dlane3 0 0 35 4
* reg_rx_csi_dly_cnt_settle_dlane3 85 -2 145 -6
*
* We use the minimum values of both A and B.
*/
#define DIV_SHIFT 8
#define CSI2_ACCINV 8
static u32 calc_timing(s32 a, s32 b, s64 link_freq, s32 accinv)
{
return accinv * a + (accinv * b * (500000000 >> DIV_SHIFT)
/ (s32)(link_freq >> DIV_SHIFT));
}
static int
ipu6_isys_csi2_calc_timing(struct ipu6_isys_csi2 *csi2,
struct ipu6_isys_csi2_timing *timing, s32 accinv)
{
struct device *dev = &csi2->isys->adev->auxdev.dev;
s64 link_freq;
link_freq = ipu6_isys_csi2_get_link_freq(csi2);
if (link_freq < 0)
return link_freq;
timing->ctermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A,
CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B,
link_freq, accinv);
timing->csettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A,
CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B,
link_freq, accinv);
timing->dtermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A,
CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B,
link_freq, accinv);
timing->dsettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A,
CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B,
link_freq, accinv);
dev_dbg(dev, "ctermen %u csettle %u dtermen %u dsettle %u\n",
timing->ctermen, timing->csettle,
timing->dtermen, timing->dsettle);
return 0;
}
void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2)
{
u32 irq = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
struct ipu6_isys *isys = csi2->isys;
u32 mask;
mask = isys->pdata->ipdata->csi2.irq_mask;
writel(irq & mask, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
csi2->receiver_errors |= irq & mask;
}
void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2)
{
struct device *dev = &csi2->isys->adev->auxdev.dev;
const struct ipu6_csi2_error *errors;
u32 status;
u32 i;
/* register errors once more in case of interrupts are disabled */
ipu6_isys_register_errors(csi2);
status = csi2->receiver_errors;
csi2->receiver_errors = 0;
errors = dphy_rx_errors;
for (i = 0; i < CSI_RX_NUM_ERRORS_IN_IRQ; i++) {
if (status & BIT(i))
dev_err_ratelimited(dev, "csi2-%i error: %s\n",
csi2->port, errors[i].error_string);
}
}
static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd,
const struct ipu6_isys_csi2_timing *timing,
unsigned int nlanes, int enable)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd);
struct ipu6_isys *isys = csi2->isys;
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_isys_csi2_config cfg;
unsigned int nports;
int ret = 0;
u32 mask = 0;
u32 i;
dev_dbg(dev, "stream %s CSI2-%u with %u lanes\n", enable ? "on" : "off",
csi2->port, nlanes);
cfg.port = csi2->port;
cfg.nlanes = nlanes;
mask = isys->pdata->ipdata->csi2.irq_mask;
nports = isys->pdata->ipdata->csi2.nports;
if (!enable) {
writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE);
writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE);
writel(0,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
writel(0,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
writel(0xffffffff,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
isys->phy_set_power(isys, &cfg, timing, false);
writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT
(isys->pdata->ipdata->csi2.fw_access_port_ofs,
csi2->port));
writel(0, isys->pdata->base +
CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port));
return ret;
}
/* reset port reset */
writel(0x1, csi2->base + CSI_REG_PORT_GPREG_SRST);
usleep_range(100, 200);
writel(0x0, csi2->base + CSI_REG_PORT_GPREG_SRST);
/* enable port clock */
for (i = 0; i < nports; i++) {
writel(1, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(i));
writel(1, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT
(isys->pdata->ipdata->csi2.fw_access_port_ofs, i));
}
/* enable all error related irq */
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_MASK_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
/*
* Using event from firmware instead of irq to handle CSI2 sync event
* which can reduce system wakeups. If CSI2 sync irq enabled, we need
* disable the firmware CSI2 sync event to avoid duplicate handling.
*/
writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_MASK_OFFSET);
writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET);
writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
/* configure to enable FE and PPI2CSI */
writel(0, csi2->base + CSI_REG_CSI_FE_MODE);
writel(CSI_SENSOR_INPUT, csi2->base + CSI_REG_CSI_FE_MUX_CTRL);
writel(CSI_CNTR_SENSOR_LINE_ID | CSI_CNTR_SENSOR_FRAME_ID,
csi2->base + CSI_REG_CSI_FE_SYNC_CNTR_SEL);
writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, nlanes - 1),
csi2->base + CSI_REG_PPI2CSI_CONFIG_PPI_INTF);
writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE);
writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE);
ret = isys->phy_set_power(isys, &cfg, timing, true);
if (ret)
dev_err(dev, "csi-%d phy power up failed %d\n", csi2->port,
ret);
return ret;
}
static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd);
struct ipu6_isys_csi2_timing timing = { };
struct v4l2_subdev *remote_sd;
struct media_pad *remote_pad;
u64 sink_streams;
int ret;
remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]);
remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
sink_streams =
v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK,
&streams_mask);
ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV);
if (ret)
return ret;
ret = ipu6_isys_csi2_set_stream(sd, &timing, csi2->nlanes, true);
if (ret)
return ret;
ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index,
sink_streams);
if (ret) {
ipu6_isys_csi2_set_stream(sd, NULL, 0, false);
return ret;
}
return 0;
}
static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct v4l2_subdev *remote_sd;
struct media_pad *remote_pad;
u64 sink_streams;
sink_streams =
v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK,
&streams_mask);
remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]);
remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
ipu6_isys_csi2_set_stream(sd, NULL, 0, false);
v4l2_subdev_disable_streams(remote_sd, remote_pad->index, sink_streams);
return 0;
}
static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_selection *sel)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct device *dev = &asd->isys->adev->auxdev.dev;
struct v4l2_mbus_framefmt *sink_ffmt;
struct v4l2_mbus_framefmt *src_ffmt;
struct v4l2_rect *crop;
if (sel->pad == CSI2_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP)
return -EINVAL;
sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state,
sel->pad,
sel->stream);
if (!sink_ffmt)
return -EINVAL;
src_ffmt = v4l2_subdev_state_get_format(state, sel->pad, sel->stream);
if (!src_ffmt)
return -EINVAL;
crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
if (!crop)
return -EINVAL;
/* Only vertical cropping is supported */
sel->r.left = 0;
sel->r.width = sink_ffmt->width;
/* Non-bayer formats can't be single line cropped */
if (!ipu6_isys_is_bayer_format(sink_ffmt->code))
sel->r.top &= ~1;
sel->r.height = clamp(sel->r.height & ~1, IPU6_ISYS_MIN_HEIGHT,
sink_ffmt->height - sel->r.top);
*crop = sel->r;
/* update source pad format */
src_ffmt->width = sel->r.width;
src_ffmt->height = sel->r.height;
if (ipu6_isys_is_bayer_format(sink_ffmt->code))
src_ffmt->code = ipu6_isys_convert_bayer_order(sink_ffmt->code,
sel->r.left,
sel->r.top);
dev_dbg(dev, "set crop for %s sel: %d,%d,%d,%d code: 0x%x\n",
sd->name, sel->r.left, sel->r.top, sel->r.width, sel->r.height,
src_ffmt->code);
return 0;
}
static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_selection *sel)
{
struct v4l2_mbus_framefmt *sink_ffmt;
struct v4l2_rect *crop;
int ret = 0;
if (sd->entity.pads[sel->pad].flags & MEDIA_PAD_FL_SINK)
return -EINVAL;
sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state,
sel->pad,
sel->stream);
if (!sink_ffmt)
return -EINVAL;
crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
if (!crop)
return -EINVAL;
switch (sel->target) {
case V4L2_SEL_TGT_CROP_DEFAULT:
case V4L2_SEL_TGT_CROP_BOUNDS:
sel->r.left = 0;
sel->r.top = 0;
sel->r.width = sink_ffmt->width;
sel->r.height = sink_ffmt->height;
break;
case V4L2_SEL_TGT_CROP:
sel->r = *crop;
break;
default:
ret = -EINVAL;
}
return ret;
}
static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = {
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = ipu6_isys_subdev_set_fmt,
.get_selection = ipu6_isys_csi2_get_sel,
.set_selection = ipu6_isys_csi2_set_sel,
.enum_mbus_code = ipu6_isys_subdev_enum_mbus_code,
.set_routing = ipu6_isys_subdev_set_routing,
.enable_streams = ipu6_isys_csi2_enable_streams,
.disable_streams = ipu6_isys_csi2_disable_streams,
};
static const struct v4l2_subdev_ops csi2_sd_ops = {
.core = &csi2_sd_core_ops,
.pad = &csi2_sd_pad_ops,
};
static const struct media_entity_operations csi2_entity_ops = {
.link_validate = v4l2_subdev_link_validate,
.has_pad_interdep = v4l2_subdev_has_pad_interdep,
};
void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2)
{
if (!csi2->isys)
return;
v4l2_device_unregister_subdev(&csi2->asd.sd);
v4l2_subdev_cleanup(&csi2->asd.sd);
ipu6_isys_subdev_cleanup(&csi2->asd);
csi2->isys = NULL;
}
int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2,
struct ipu6_isys *isys,
void __iomem *base, unsigned int index)
{
struct device *dev = &isys->adev->auxdev.dev;
int ret;
csi2->isys = isys;
csi2->base = base;
csi2->port = index;
csi2->asd.sd.entity.ops = &csi2_entity_ops;
csi2->asd.isys = isys;
ret = ipu6_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0,
NR_OF_CSI2_SINK_PADS, NR_OF_CSI2_SRC_PADS);
if (ret)
goto fail;
csi2->asd.source = IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 + index;
csi2->asd.supported_codes = csi2_supported_codes;
snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name),
IPU6_ISYS_ENTITY_PREFIX " CSI2 %u", index);
v4l2_set_subdevdata(&csi2->asd.sd, &csi2->asd);
ret = v4l2_subdev_init_finalize(&csi2->asd.sd);
if (ret) {
dev_err(dev, "failed to init v4l2 subdev\n");
goto fail;
}
ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd);
if (ret) {
dev_err(dev, "failed to register v4l2 subdev\n");
goto fail;
}
return 0;
fail:
ipu6_isys_csi2_cleanup(csi2);
return ret;
}
void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream)
{
struct video_device *vdev = stream->asd->sd.devnode;
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
struct v4l2_event ev = {
.type = V4L2_EVENT_FRAME_SYNC,
};
ev.u.frame_sync.frame_sequence = atomic_fetch_inc(&stream->sequence);
v4l2_event_queue(vdev, &ev);
dev_dbg(dev, "sof_event::csi2-%i sequence: %i, vc: %d\n",
csi2->port, ev.u.frame_sync.frame_sequence, stream->vc);
}
void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream)
{
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
u32 frame_sequence = atomic_read(&stream->sequence);
dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n",
csi2->port, frame_sequence);
}
int ipu6_isys_csi2_get_remote_desc(u32 source_stream,
struct ipu6_isys_csi2 *csi2,
struct media_entity *source_entity,
struct v4l2_mbus_frame_desc_entry *entry)
{
struct v4l2_mbus_frame_desc_entry *desc_entry = NULL;
struct device *dev = &csi2->isys->adev->auxdev.dev;
struct v4l2_mbus_frame_desc desc;
struct v4l2_subdev *source;
struct media_pad *pad;
unsigned int i;
int ret;
source = media_entity_to_v4l2_subdev(source_entity);
if (!source)
return -EPIPE;
pad = media_pad_remote_pad_first(&csi2->asd.pad[CSI2_PAD_SINK]);
if (!pad)
return -EPIPE;
ret = v4l2_subdev_call(source, pad, get_frame_desc, pad->index, &desc);
if (ret)
return ret;
if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) {
dev_err(dev, "Unsupported frame descriptor type\n");
return -EINVAL;
}
for (i = 0; i < desc.num_entries; i++) {
if (source_stream == desc.entry[i].stream) {
desc_entry = &desc.entry[i];
break;
}
}
if (!desc_entry) {
dev_err(dev, "Failed to find stream %u from remote subdev\n",
source_stream);
return -EINVAL;
}
if (desc_entry->bus.csi2.vc >= NR_OF_CSI2_VC) {
dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc);
return -EINVAL;
}
*entry = *desc_entry;
return 0;
}
@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_CSI2_H
#define IPU6_ISYS_CSI2_H
#include <linux/container_of.h>
#include "ipu6-isys-subdev.h"
#include "ipu6-isys-video.h"
struct media_entity;
struct v4l2_mbus_frame_desc_entry;
struct ipu6_isys_video;
struct ipu6_isys;
struct ipu6_isys_stream;
#define NR_OF_CSI2_VC 16
#define INVALID_VC_ID -1
#define NR_OF_CSI2_SINK_PADS 1
#define CSI2_PAD_SINK 0
#define NR_OF_CSI2_SRC_PADS 8
#define CSI2_PAD_SRC 1
#define NR_OF_CSI2_PADS (NR_OF_CSI2_SINK_PADS + NR_OF_CSI2_SRC_PADS)
#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A 0
#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B 0
#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A 95
#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B -8
#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A 0
#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B 0
#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A 85
#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B -2
struct ipu6_isys_csi2 {
struct ipu6_isys_subdev asd;
struct ipu6_isys *isys;
struct ipu6_isys_video av[NR_OF_CSI2_SRC_PADS];
void __iomem *base;
u32 receiver_errors;
unsigned int nlanes;
unsigned int port;
};
struct ipu6_isys_csi2_timing {
u32 ctermen;
u32 csettle;
u32 dtermen;
u32 dsettle;
};
struct ipu6_csi2_error {
const char *error_string;
bool is_info_only;
};
#define ipu6_isys_subdev_to_csi2(__sd) \
container_of(__sd, struct ipu6_isys_csi2, asd)
#define to_ipu6_isys_csi2(__asd) container_of(__asd, struct ipu6_isys_csi2, asd)
s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2);
int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, struct ipu6_isys *isys,
void __iomem *base, unsigned int index);
void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2);
void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream);
void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream);
void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2);
void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2);
int ipu6_isys_csi2_get_remote_desc(u32 source_stream,
struct ipu6_isys_csi2 *csi2,
struct media_entity *source_entity,
struct v4l2_mbus_frame_desc_entry *entry);
#endif /* IPU6_ISYS_CSI2_H */
@@ -0,0 +1,548 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/iopoll.h>
#include <linux/math64.h>
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-platform-isys-csi2-reg.h"
#define IPU6_DWC_DPHY_BASE(i) (0x238038 + 0x34 * (i))
#define IPU6_DWC_DPHY_RSTZ 0x00
#define IPU6_DWC_DPHY_SHUTDOWNZ 0x04
#define IPU6_DWC_DPHY_HSFREQRANGE 0x08
#define IPU6_DWC_DPHY_CFGCLKFREQRANGE 0x0c
#define IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE 0x10
#define IPU6_DWC_DPHY_TEST_IFC_REQ 0x14
#define IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION 0x18
#define IPU6_DWC_DPHY_DFT_CTRL0 0x28
#define IPU6_DWC_DPHY_DFT_CTRL1 0x2c
#define IPU6_DWC_DPHY_DFT_CTRL2 0x30
/*
* test IFC request definition:
* - req: 0 for read, 1 for write
* - 12 bits address
* - 8bits data (will ignore for read)
* --24----16------4-----0
* --|-data-|-addr-|-req-|
*/
#define IFC_REQ(req, addr, data) (FIELD_PREP(GENMASK(23, 16), data) | \
FIELD_PREP(GENMASK(15, 4), addr) | \
FIELD_PREP(GENMASK(1, 0), req))
#define TEST_IFC_REQ_READ 0
#define TEST_IFC_REQ_WRITE 1
#define TEST_IFC_REQ_RESET 2
#define TEST_IFC_ACCESS_MODE_FSM 0
#define TEST_IFC_ACCESS_MODE_IFC_CTL 1
enum phy_fsm_state {
PHY_FSM_STATE_POWERON = 0,
PHY_FSM_STATE_BGPON = 1,
PHY_FSM_STATE_CAL_TYPE = 2,
PHY_FSM_STATE_BURNIN_CAL = 3,
PHY_FSM_STATE_TERMCAL = 4,
PHY_FSM_STATE_OFFSETCAL = 5,
PHY_FSM_STATE_OFFSET_LANE = 6,
PHY_FSM_STATE_IDLE = 7,
PHY_FSM_STATE_ULP = 8,
PHY_FSM_STATE_DDLTUNNING = 9,
PHY_FSM_STATE_SKEW_BACKWARD = 10,
PHY_FSM_STATE_INVALID,
};
static void dwc_dphy_write(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 data)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
dev_dbg(dev, "write: reg 0x%zx = data 0x%x", base + addr - isys_base,
data);
writel(data, base + addr);
}
static u32 dwc_dphy_read(struct ipu6_isys *isys, u32 phy_id, u32 addr)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
u32 data;
data = readl(base + addr);
dev_dbg(dev, "read: reg 0x%zx = data 0x%x", base + addr - isys_base,
data);
return data;
}
static void dwc_dphy_write_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 data, u8 shift, u8 width)
{
u32 temp;
u32 mask;
mask = (1 << width) - 1;
temp = dwc_dphy_read(isys, phy_id, addr);
temp &= ~(mask << shift);
temp |= (data & mask) << shift;
dwc_dphy_write(isys, phy_id, addr, temp);
}
static u32 __maybe_unused dwc_dphy_read_mask(struct ipu6_isys *isys, u32 phy_id,
u32 addr, u8 shift, u8 width)
{
u32 val;
val = dwc_dphy_read(isys, phy_id, addr) >> shift;
return val & ((1 << width) - 1);
}
#define DWC_DPHY_TIMEOUT (5 * USEC_PER_SEC)
static int dwc_dphy_ifc_read(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 *val)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
void __iomem *reg;
u32 completion;
int ret;
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
IFC_REQ(TEST_IFC_REQ_READ, addr, 0));
reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION;
ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)),
10, DWC_DPHY_TIMEOUT);
if (ret) {
dev_err(dev, "DWC ifc request read timeout\n");
return ret;
}
*val = completion >> 8 & 0xff;
*val = FIELD_GET(GENMASK(15, 8), completion);
dev_dbg(dev, "DWC ifc read 0x%x = 0x%x", addr, *val);
return 0;
}
static int dwc_dphy_ifc_write(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 data)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
void __iomem *reg;
u32 completion;
int ret;
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
IFC_REQ(TEST_IFC_REQ_WRITE, addr, data));
completion = readl(base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION);
reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION;
ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)),
10, DWC_DPHY_TIMEOUT);
if (ret)
dev_err(dev, "DWC ifc request write timeout\n");
return ret;
}
static void dwc_dphy_ifc_write_mask(struct ipu6_isys *isys, u32 phy_id,
u32 addr, u32 data, u8 shift, u8 width)
{
u32 temp, mask;
int ret;
ret = dwc_dphy_ifc_read(isys, phy_id, addr, &temp);
if (ret)
return;
mask = (1 << width) - 1;
temp &= ~(mask << shift);
temp |= (data & mask) << shift;
dwc_dphy_ifc_write(isys, phy_id, addr, temp);
}
static u32 dwc_dphy_ifc_read_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u8 shift, u8 width)
{
int ret;
u32 val;
ret = dwc_dphy_ifc_read(isys, phy_id, addr, &val);
if (ret)
return 0;
return ((val >> shift) & ((1 << width) - 1));
}
static int dwc_dphy_pwr_up(struct ipu6_isys *isys, u32 phy_id)
{
struct device *dev = &isys->adev->auxdev.dev;
u32 fsm_state;
int ret;
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 1);
usleep_range(10, 20);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 1);
ret = read_poll_timeout(dwc_dphy_ifc_read_mask, fsm_state,
(fsm_state == PHY_FSM_STATE_IDLE ||
fsm_state == PHY_FSM_STATE_ULP),
100, DWC_DPHY_TIMEOUT, false, isys,
phy_id, 0x1e, 0, 4);
if (ret)
dev_err(dev, "Dphy %d power up failed, state 0x%x", phy_id,
fsm_state);
return ret;
}
struct dwc_dphy_freq_range {
u8 hsfreq;
u16 min;
u16 max;
u16 default_mbps;
u16 osc_freq_target;
};
#define DPHY_FREQ_RANGE_NUM (63)
#define DPHY_FREQ_RANGE_INVALID_INDEX (0xff)
static const struct dwc_dphy_freq_range freqranges[DPHY_FREQ_RANGE_NUM] = {
{0x00, 80, 97, 80, 335},
{0x10, 80, 107, 90, 335},
{0x20, 84, 118, 100, 335},
{0x30, 93, 128, 110, 335},
{0x01, 103, 139, 120, 335},
{0x11, 112, 149, 130, 335},
{0x21, 122, 160, 140, 335},
{0x31, 131, 170, 150, 335},
{0x02, 141, 181, 160, 335},
{0x12, 150, 191, 170, 335},
{0x22, 160, 202, 180, 335},
{0x32, 169, 212, 190, 335},
{0x03, 183, 228, 205, 335},
{0x13, 198, 244, 220, 335},
{0x23, 212, 259, 235, 335},
{0x33, 226, 275, 250, 335},
{0x04, 250, 301, 275, 335},
{0x14, 274, 328, 300, 335},
{0x25, 297, 354, 325, 335},
{0x35, 321, 380, 350, 335},
{0x05, 369, 433, 400, 335},
{0x16, 416, 485, 450, 335},
{0x26, 464, 538, 500, 335},
{0x37, 511, 590, 550, 335},
{0x07, 559, 643, 600, 335},
{0x18, 606, 695, 650, 335},
{0x28, 654, 748, 700, 335},
{0x39, 701, 800, 750, 335},
{0x09, 749, 853, 800, 335},
{0x19, 796, 905, 850, 335},
{0x29, 844, 958, 900, 335},
{0x3a, 891, 1010, 950, 335},
{0x0a, 939, 1063, 1000, 335},
{0x1a, 986, 1115, 1050, 335},
{0x2a, 1034, 1168, 1100, 335},
{0x3b, 1081, 1220, 1150, 335},
{0x0b, 1129, 1273, 1200, 335},
{0x1b, 1176, 1325, 1250, 335},
{0x2b, 1224, 1378, 1300, 335},
{0x3c, 1271, 1430, 1350, 335},
{0x0c, 1319, 1483, 1400, 335},
{0x1c, 1366, 1535, 1450, 335},
{0x2c, 1414, 1588, 1500, 335},
{0x3d, 1461, 1640, 1550, 208},
{0x0d, 1509, 1693, 1600, 214},
{0x1d, 1556, 1745, 1650, 221},
{0x2e, 1604, 1798, 1700, 228},
{0x3e, 1651, 1850, 1750, 234},
{0x0e, 1699, 1903, 1800, 241},
{0x1e, 1746, 1955, 1850, 248},
{0x2f, 1794, 2008, 1900, 255},
{0x3f, 1841, 2060, 1950, 261},
{0x0f, 1889, 2113, 2000, 268},
{0x40, 1936, 2165, 2050, 275},
{0x41, 1984, 2218, 2100, 281},
{0x42, 2031, 2270, 2150, 288},
{0x43, 2079, 2323, 2200, 294},
{0x44, 2126, 2375, 2250, 302},
{0x45, 2174, 2428, 2300, 308},
{0x46, 2221, 2480, 2350, 315},
{0x47, 2269, 2500, 2400, 321},
{0x48, 2316, 2500, 2450, 328},
{0x49, 2364, 2500, 2500, 335}
};
static u16 get_hsfreq_by_mbps(u32 mbps)
{
u16 best = DPHY_FREQ_RANGE_INVALID_INDEX;
unsigned int i;
for (i = 0; i < DPHY_FREQ_RANGE_NUM; i++) {
if (mbps > freqranges[i].max)
continue;
if (mbps < freqranges[i].min)
break;
if (best == DPHY_FREQ_RANGE_INVALID_INDEX ||
freqranges[i].osc_freq_target >
freqranges[best].osc_freq_target ||
(freqranges[i].osc_freq_target ==
freqranges[best].osc_freq_target &&
abs((int)mbps - (int)freqranges[i].default_mbps) <
abs((int)mbps - (int)freqranges[best].default_mbps)))
best = i;
}
return best;
}
static int ipu6_isys_dwc_phy_config(struct ipu6_isys *isys,
u32 phy_id, u32 mbps)
{
struct ipu6_bus_device *adev = isys->adev;
struct device *dev = &adev->auxdev.dev;
struct ipu6_device *isp = adev->isp;
u32 cfg_clk_freqrange;
u32 osc_freq_target;
u32 index;
dev_dbg(dev, "config Dphy %u with %u mbps", phy_id, mbps);
index = get_hsfreq_by_mbps(mbps);
if (index == DPHY_FREQ_RANGE_INVALID_INDEX) {
dev_err(dev, "link freq not found for mbps %u", mbps);
return -EINVAL;
}
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_HSFREQRANGE,
freqranges[index].hsfreq, 0, 7);
/* Force termination Calibration */
if (isys->phy_termcal_val) {
dwc_dphy_ifc_write_mask(isys, phy_id, 0x20a, 0x1, 0, 1);
dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, 0x3, 0, 2);
dwc_dphy_ifc_write_mask(isys, phy_id, 0x209,
isys->phy_termcal_val, 4, 4);
}
/*
* Enable override to configure the DDL target oscillation
* frequency on bit 0 of register 0xe4
*/
dwc_dphy_ifc_write_mask(isys, phy_id, 0xe4, 0x1, 0, 1);
/*
* configure registers 0xe2, 0xe3 with the
* appropriate DDL target oscillation frequency
* 0x1cc(460)
*/
osc_freq_target = freqranges[index].osc_freq_target;
dwc_dphy_ifc_write_mask(isys, phy_id, 0xe2,
osc_freq_target & 0xff, 0, 8);
dwc_dphy_ifc_write_mask(isys, phy_id, 0xe3,
(osc_freq_target >> 8) & 0xf, 0, 4);
if (mbps < 1500) {
/* deskew_polarity_rw, for < 1.5Gbps */
dwc_dphy_ifc_write_mask(isys, phy_id, 0x8, 0x1, 5, 1);
}
/*
* Set cfgclkfreqrange[5:0] = round[(Fcfg_clk(MHz)-17)*4]
* (38.4 - 17) * 4 = ~85 (0x55)
*/
cfg_clk_freqrange = (isp->buttress.ref_clk - 170) * 4 / 10;
dev_dbg(dev, "ref_clk = %u clk_freqrange = %u",
isp->buttress.ref_clk, cfg_clk_freqrange);
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_CFGCLKFREQRANGE,
cfg_clk_freqrange, 0, 8);
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 4, 1);
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 8, 1);
return 0;
}
static void ipu6_isys_dwc_phy_aggr_setup(struct ipu6_isys *isys, u32 master,
u32 slave, u32 mbps)
{
/* Config mastermacro */
dwc_dphy_ifc_write_mask(isys, master, 0x133, 0x1, 0, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x133, 0x0, 0, 1);
/* Config master PHY clk lane to drive long channel clk */
dwc_dphy_ifc_write_mask(isys, master, 0x307, 0x1, 2, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x0, 2, 1);
/* Config both PHYs data lanes to get clk from long channel */
dwc_dphy_ifc_write_mask(isys, master, 0x508, 0x1, 5, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x508, 0x1, 5, 1);
dwc_dphy_ifc_write_mask(isys, master, 0x708, 0x1, 5, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x708, 0x1, 5, 1);
/* Config slave PHY clk lane to bypass long channel clk to DDR clk */
dwc_dphy_ifc_write_mask(isys, master, 0x308, 0x0, 3, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x308, 0x1, 3, 1);
/* Override slave PHY clk lane enable (DPHYRXCLK_CLL_demux module) */
dwc_dphy_ifc_write_mask(isys, slave, 0xe0, 0x3, 0, 2);
/* Override slave PHY DDR clk lane enable (DPHYHSRX_div124 module) */
dwc_dphy_ifc_write_mask(isys, slave, 0xe1, 0x1, 1, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x1, 3, 1);
/* Turn off slave PHY LP-RX clk lane */
dwc_dphy_ifc_write_mask(isys, slave, 0x304, 0x1, 7, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x305, 0xa, 0, 5);
}
#define PHY_E 4
static int ipu6_isys_dwc_phy_powerup_ack(struct ipu6_isys *isys, u32 phy_id)
{
struct device *dev = &isys->adev->auxdev.dev;
u32 rescal_done;
int ret;
ret = dwc_dphy_pwr_up(isys, phy_id);
if (ret != 0) {
dev_err(dev, "Dphy %u power up failed(%d)", phy_id, ret);
return ret;
}
/* reset forcerxmode */
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 4, 1);
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 8, 1);
dev_dbg(dev, "Dphy %u is ready!", phy_id);
if (phy_id != PHY_E || isys->phy_termcal_val)
return 0;
usleep_range(100, 200);
rescal_done = dwc_dphy_ifc_read_mask(isys, phy_id, 0x221, 7, 1);
if (rescal_done) {
isys->phy_termcal_val = dwc_dphy_ifc_read_mask(isys, phy_id,
0x220, 2, 4);
dev_dbg(dev, "termcal done with value = %u",
isys->phy_termcal_val);
}
return 0;
}
static void ipu6_isys_dwc_phy_reset(struct ipu6_isys *isys, u32 phy_id)
{
dev_dbg(&isys->adev->auxdev.dev, "Reset phy %u", phy_id);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 0);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 0);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE,
TEST_IFC_ACCESS_MODE_FSM);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
TEST_IFC_REQ_RESET);
}
int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u32 phy_id, primary, secondary;
u32 nlanes, port, mbps;
s64 link_freq;
int ret;
port = cfg->port;
if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
dev_warn(dev, "invalid port ID %d\n", port);
return -EINVAL;
}
nlanes = cfg->nlanes;
/* only port 0, 2 and 4 support 4 lanes */
if (nlanes == 4 && port % 2) {
dev_err(dev, "invalid csi-port %u with %u lanes\n", port,
nlanes);
return -EINVAL;
}
link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[port]);
if (link_freq < 0) {
dev_err(dev, "get link freq failed(%lld).\n", link_freq);
return link_freq;
}
mbps = div_u64(link_freq, 500000);
phy_id = port;
primary = port & ~1;
secondary = primary + 1;
if (on) {
if (nlanes == 4) {
dev_dbg(dev, "config phy %u and %u in aggr mode\n",
primary, secondary);
ipu6_isys_dwc_phy_reset(isys, primary);
ipu6_isys_dwc_phy_reset(isys, secondary);
ipu6_isys_dwc_phy_aggr_setup(isys, primary,
secondary, mbps);
ret = ipu6_isys_dwc_phy_config(isys, primary, mbps);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_config(isys, secondary, mbps);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_powerup_ack(isys, primary);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_powerup_ack(isys, secondary);
return ret;
}
dev_dbg(dev, "config phy %u with %u lanes in non-aggr mode\n",
phy_id, nlanes);
ipu6_isys_dwc_phy_reset(isys, phy_id);
ret = ipu6_isys_dwc_phy_config(isys, phy_id, mbps);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_powerup_ack(isys, phy_id);
return ret;
}
if (nlanes == 4) {
dev_dbg(dev, "Power down phy %u and phy %u for port %u\n",
primary, secondary, port);
ipu6_isys_dwc_phy_reset(isys, secondary);
ipu6_isys_dwc_phy_reset(isys, primary);
return 0;
}
dev_dbg(dev, "Powerdown phy %u with %u lanes\n", phy_id, nlanes);
ipu6_isys_dwc_phy_reset(isys, phy_id);
return 0;
}
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/io.h>
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-isys-csi2.h"
#include "ipu6-platform-isys-csi2-reg.h"
/* only use BB0, BB2, BB4, and BB6 on PHY0 */
#define IPU6SE_ISYS_PHY_BB_NUM 4
#define IPU6SE_ISYS_PHY_0_BASE 0x10000
#define PHY_CPHY_DLL_OVRD(x) (0x100 + 0x100 * (x))
#define PHY_CPHY_RX_CONTROL1(x) (0x110 + 0x100 * (x))
#define PHY_DPHY_CFG(x) (0x148 + 0x100 * (x))
#define PHY_BB_AFE_CONFIG(x) (0x174 + 0x100 * (x))
/*
* use port_cfg to configure that which data lanes used
* +---------+ +------+ +-----+
* | port0 x4<-----| | | |
* | | | port | | |
* | port1 x2<-----| | | |
* | | | <-| PHY |
* | port2 x4<-----| | | |
* | | |config| | |
* | port3 x2<-----| | | |
* +---------+ +------+ +-----+
*/
static const unsigned int csi2_port_cfg[][3] = {
{0, 0, 0x1f}, /* no link */
{4, 0, 0x10}, /* x4 + x4 config */
{2, 0, 0x12}, /* x2 + x2 config */
{1, 0, 0x13}, /* x1 + x1 config */
{2, 1, 0x15}, /* x2x1 + x2x1 config */
{1, 1, 0x16}, /* x1x1 + x1x1 config */
{2, 2, 0x18}, /* x2x2 + x2x2 config */
{1, 2, 0x19} /* x1x2 + x1x2 config */
};
/* port, nlanes, bbindex, portcfg */
static const unsigned int phy_port_cfg[][4] = {
/* sip0 */
{0, 1, 0, 0x15},
{0, 2, 0, 0x15},
{0, 4, 0, 0x15},
{0, 4, 2, 0x22},
/* sip1 */
{2, 1, 4, 0x15},
{2, 2, 4, 0x15},
{2, 4, 4, 0x15},
{2, 4, 6, 0x22}
};
static void ipu6_isys_csi2_phy_config_by_port(struct ipu6_isys *isys,
unsigned int port,
unsigned int nlanes)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *base = isys->adev->isp->base;
unsigned int bbnum;
u32 val, reg, i;
dev_dbg(dev, "port %u with %u lanes", port, nlanes);
/* only support <1.5Gbps */
for (i = 0; i < IPU6SE_ISYS_PHY_BB_NUM; i++) {
/* cphy_dll_ovrd.crcdc_fsm_dlane0 = 13 */
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i);
val = readl(base + reg);
val |= FIELD_PREP(GENMASK(6, 1), 13);
writel(val, base + reg);
/* cphy_rx_control1.en_crc1 = 1 */
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_RX_CONTROL1(i);
val = readl(base + reg);
val |= BIT(31);
writel(val, base + reg);
/* dphy_cfg.reserved = 1, .lden_from_dll_ovrd_0 = 1 */
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_DPHY_CFG(i);
val = readl(base + reg);
val |= BIT(25) | BIT(26);
writel(val, base + reg);
/* cphy_dll_ovrd.lden_crcdc_fsm_dlane0 = 1 */
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i);
val = readl(base + reg);
val |= BIT(0);
writel(val, base + reg);
}
/* Front end config, use minimal channel loss */
for (i = 0; i < ARRAY_SIZE(phy_port_cfg); i++) {
if (phy_port_cfg[i][0] == port &&
phy_port_cfg[i][1] == nlanes) {
bbnum = phy_port_cfg[i][2] / 2;
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_BB_AFE_CONFIG(bbnum);
val = readl(base + reg);
val |= phy_port_cfg[i][3];
writel(val, base + reg);
}
}
}
static void ipu6_isys_csi2_rx_control(struct ipu6_isys *isys)
{
void __iomem *base = isys->adev->isp->base;
u32 val, reg;
reg = CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL;
val = readl(base + reg);
val |= BIT(0);
writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL);
reg = CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL;
val = readl(base + reg);
val |= BIT(0);
writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL);
reg = CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL;
val = readl(base + reg);
val |= BIT(0);
writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL);
reg = CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL;
val = readl(base + reg);
val |= BIT(0);
writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL);
}
static int ipu6_isys_csi2_set_port_cfg(struct ipu6_isys *isys,
unsigned int port, unsigned int nlanes)
{
struct device *dev = &isys->adev->auxdev.dev;
unsigned int sip = port / 2;
unsigned int index;
switch (nlanes) {
case 1:
index = 5;
break;
case 2:
index = 6;
break;
case 4:
index = 1;
break;
default:
dev_err(dev, "lanes nr %u is unsupported\n", nlanes);
return -EINVAL;
}
dev_dbg(dev, "port config for port %u with %u lanes\n", port, nlanes);
writel(csi2_port_cfg[index][2],
isys->pdata->base + CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip));
return 0;
}
static void
ipu6_isys_csi2_set_timing(struct ipu6_isys *isys,
const struct ipu6_isys_csi2_timing *timing,
unsigned int port, unsigned int nlanes)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *reg;
u32 port_base;
u32 i;
port_base = (port % 2) ? CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) :
CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port);
dev_dbg(dev, "set timing for port %u with %u lanes\n", port, nlanes);
reg = isys->pdata->base + port_base;
reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE;
writel(timing->ctermen, reg);
reg = isys->pdata->base + port_base;
reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE;
writel(timing->csettle, reg);
for (i = 0; i < nlanes; i++) {
reg = isys->pdata->base + port_base;
reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(i);
writel(timing->dtermen, reg);
reg = isys->pdata->base + port_base;
reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(i);
writel(timing->dsettle, reg);
}
}
#define DPHY_TIMER_INCR 0x28
int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
int ret = 0;
u32 nlanes;
u32 port;
if (!on)
return 0;
port = cfg->port;
nlanes = cfg->nlanes;
if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
dev_warn(dev, "invalid port ID %d\n", port);
return -EINVAL;
}
ipu6_isys_csi2_phy_config_by_port(isys, port, nlanes);
writel(DPHY_TIMER_INCR,
isys->pdata->base + CSI2_HUB_GPREG_DPHY_TIMER_INCR);
/* set port cfg and rx timing */
ipu6_isys_csi2_set_timing(isys, timing, port, nlanes);
ret = ipu6_isys_csi2_set_port_cfg(isys, port, nlanes);
if (ret)
return ret;
ipu6_isys_csi2_rx_control(isys);
return 0;
}
@@ -0,0 +1,720 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bits.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/iopoll.h>
#include <linux/list.h>
#include <linux/refcount.h>
#include <linux/time64.h>
#include <media/v4l2-async.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-isys-csi2.h"
#include "ipu6-platform-isys-csi2-reg.h"
#define CSI_REG_HUB_GPREG_PHY_CTL(id) (CSI_REG_BASE + 0x18008 + (id) * 0x8)
#define CSI_REG_HUB_GPREG_PHY_CTL_RESET BIT(4)
#define CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN BIT(0)
#define CSI_REG_HUB_GPREG_PHY_STATUS(id) (CSI_REG_BASE + 0x1800c + (id) * 0x8)
#define CSI_REG_HUB_GPREG_PHY_POWER_ACK BIT(0)
#define CSI_REG_HUB_GPREG_PHY_READY BIT(4)
#define MCD_PHY_POWER_STATUS_TIMEOUT (200 * USEC_PER_MSEC)
/*
* bridge to phy in buttress reg map, each phy has 16 kbytes
* only 2 phys for TGL U and Y
*/
#define IPU6_ISYS_MCD_PHY_BASE(i) (0x10000 + (i) * 0x4000)
/*
* There are 2 MCD DPHY instances on TGL and 1 MCD DPHY instance on ADL.
* Each MCD PHY has 12-lanes which has 8 data lanes and 4 clock lanes.
* CSI port 1, 3 (5, 7) can support max 2 data lanes.
* CSI port 0, 2 (4, 6) can support max 4 data lanes.
* PHY configurations are PPI based instead of port.
* Left:
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | PPI | PPI5 | PPI4 | PPI3 | PPI2 | PPI1 | PPI0 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x4 | unused | D3 | D2 | C0 | D0 | D1 |
* |---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x2x2 | C1 | D0 | D1 | C0 | D0 | D1 |
* ----------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x2x1 | C1 | D0 | unused | C0 | D0 | D1 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x1x1 | C1 | D0 | unused | C0 | D0 | unused |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x1x2 | C1 | D0 | D1 | C0 | D0 | unused |
* +---------+---------+---------+---------+--------+---------+----------+
*
* Right:
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | PPI | PPI6 | PPI7 | PPI8 | PPI9 | PPI10 | PPI11 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x4 | D1 | D0 | C2 | D2 | D3 | unused |
* |---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x2x2 | D1 | D0 | C2 | D1 | D0 | C3 |
* ----------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x2x1 | D1 | D0 | C2 | unused | D0 | C3 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x1x1 | unused | D0 | C2 | unused | D0 | C3 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x1x2 | unused | D0 | C2 | D1 | D0 | C3 |
* +---------+---------+---------+---------+--------+---------+----------+
*
* ppi mapping per phy :
*
* x4 + x4:
* Left : port0 - PPI range {0, 1, 2, 3, 4}
* Right: port2 - PPI range {6, 7, 8, 9, 10}
*
* x4 + x2x2:
* Left: port0 - PPI range {0, 1, 2, 3, 4}
* Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11}
*
* x2x2 + x4:
* Left: port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5}
* Right: port2 - PPI range {6, 7, 8, 9, 10}
*
* x2x2 + x2x2:
* Left : port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5}
* Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11}
*/
struct phy_reg {
u32 reg;
u32 val;
};
static const struct phy_reg common_init_regs[] = {
/* for TGL-U, use 0x80000000 */
{0x00000040, 0x80000000},
{0x00000044, 0x00a80880},
{0x00000044, 0x00b80880},
{0x00000010, 0x0000078c},
{0x00000344, 0x2f4401e2},
{0x00000544, 0x924401e2},
{0x00000744, 0x594401e2},
{0x00000944, 0x624401e2},
{0x00000b44, 0xfc4401e2},
{0x00000d44, 0xc54401e2},
{0x00000f44, 0x034401e2},
{0x00001144, 0x8f4401e2},
{0x00001344, 0x754401e2},
{0x00001544, 0xe94401e2},
{0x00001744, 0xcb4401e2},
{0x00001944, 0xfa4401e2}
};
static const struct phy_reg x1_port0_config_regs[] = {
{0x00000694, 0xc80060fa},
{0x00000680, 0x3d4f78ea},
{0x00000690, 0x10a0140b},
{0x000006a8, 0xdf04010a},
{0x00000700, 0x57050060},
{0x00000710, 0x0030001c},
{0x00000738, 0x5f004444},
{0x0000073c, 0x78464204},
{0x00000748, 0x7821f940},
{0x0000074c, 0xb2000433},
{0x00000494, 0xfe6030fa},
{0x00000480, 0x29ef5ed0},
{0x00000490, 0x10a0540b},
{0x000004a8, 0x7a01010a},
{0x00000500, 0xef053460},
{0x00000510, 0xe030101c},
{0x00000538, 0xdf808444},
{0x0000053c, 0xc8422204},
{0x00000540, 0x0180088c},
{0x00000574, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x1_port1_config_regs[] = {
{0x00000c94, 0xc80060fa},
{0x00000c80, 0xcf47abea},
{0x00000c90, 0x10a0840b},
{0x00000ca8, 0xdf04010a},
{0x00000d00, 0x57050060},
{0x00000d10, 0x0030001c},
{0x00000d38, 0x5f004444},
{0x00000d3c, 0x78464204},
{0x00000d48, 0x7821f940},
{0x00000d4c, 0xb2000433},
{0x00000a94, 0xc91030fa},
{0x00000a80, 0x5a166ed0},
{0x00000a90, 0x10a0540b},
{0x00000aa8, 0x5d060100},
{0x00000b00, 0xef053460},
{0x00000b10, 0xa030101c},
{0x00000b38, 0xdf808444},
{0x00000b3c, 0xc8422204},
{0x00000b40, 0x0180088c},
{0x00000b74, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x1_port2_config_regs[] = {
{0x00001294, 0x28f000fa},
{0x00001280, 0x08130cea},
{0x00001290, 0x10a0140b},
{0x000012a8, 0xd704010a},
{0x00001300, 0x8d050060},
{0x00001310, 0x0030001c},
{0x00001338, 0xdf008444},
{0x0000133c, 0x78422204},
{0x00001348, 0x7821f940},
{0x0000134c, 0x5a000433},
{0x00001094, 0x2d20b0fa},
{0x00001080, 0xade75dd0},
{0x00001090, 0x10a0540b},
{0x000010a8, 0xb101010a},
{0x00001100, 0x33053460},
{0x00001110, 0x0030101c},
{0x00001138, 0xdf808444},
{0x0000113c, 0xc8422204},
{0x00001140, 0x8180088c},
{0x00001174, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x1_port3_config_regs[] = {
{0x00001894, 0xc80060fa},
{0x00001880, 0x0f90fd6a},
{0x00001890, 0x10a0840b},
{0x000018a8, 0xdf04010a},
{0x00001900, 0x57050060},
{0x00001910, 0x0030001c},
{0x00001938, 0x5f004444},
{0x0000193c, 0x78464204},
{0x00001948, 0x7821f940},
{0x0000194c, 0xb2000433},
{0x00001694, 0x3050d0fa},
{0x00001680, 0x0ef6d050},
{0x00001690, 0x10a0540b},
{0x000016a8, 0xe301010a},
{0x00001700, 0x69053460},
{0x00001710, 0xa030101c},
{0x00001738, 0xdf808444},
{0x0000173c, 0xc8422204},
{0x00001740, 0x0180088c},
{0x00001774, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x2_port0_config_regs[] = {
{0x00000694, 0xc80060fa},
{0x00000680, 0x3d4f78ea},
{0x00000690, 0x10a0140b},
{0x000006a8, 0xdf04010a},
{0x00000700, 0x57050060},
{0x00000710, 0x0030001c},
{0x00000738, 0x5f004444},
{0x0000073c, 0x78464204},
{0x00000748, 0x7821f940},
{0x0000074c, 0xb2000433},
{0x00000494, 0xc80060fa},
{0x00000480, 0x29ef5ed8},
{0x00000490, 0x10a0540b},
{0x000004a8, 0x7a01010a},
{0x00000500, 0xef053460},
{0x00000510, 0xe030101c},
{0x00000538, 0xdf808444},
{0x0000053c, 0xc8422204},
{0x00000540, 0x0180088c},
{0x00000574, 0x00000000},
{0x00000294, 0xc80060fa},
{0x00000280, 0xcb45b950},
{0x00000290, 0x10a0540b},
{0x000002a8, 0x8c01010a},
{0x00000300, 0xef053460},
{0x00000310, 0x8030101c},
{0x00000338, 0x41808444},
{0x0000033c, 0x32422204},
{0x00000340, 0x0180088c},
{0x00000374, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x2_port1_config_regs[] = {
{0x00000c94, 0xc80060fa},
{0x00000c80, 0xcf47abea},
{0x00000c90, 0x10a0840b},
{0x00000ca8, 0xdf04010a},
{0x00000d00, 0x57050060},
{0x00000d10, 0x0030001c},
{0x00000d38, 0x5f004444},
{0x00000d3c, 0x78464204},
{0x00000d48, 0x7821f940},
{0x00000d4c, 0xb2000433},
{0x00000a94, 0xc80060fa},
{0x00000a80, 0x5a166ed8},
{0x00000a90, 0x10a0540b},
{0x00000aa8, 0x7a01010a},
{0x00000b00, 0xef053460},
{0x00000b10, 0xa030101c},
{0x00000b38, 0xdf808444},
{0x00000b3c, 0xc8422204},
{0x00000b40, 0x0180088c},
{0x00000b74, 0x00000000},
{0x00000894, 0xc80060fa},
{0x00000880, 0x4d4f21d0},
{0x00000890, 0x10a0540b},
{0x000008a8, 0x5601010a},
{0x00000900, 0xef053460},
{0x00000910, 0x8030101c},
{0x00000938, 0xdf808444},
{0x0000093c, 0xc8422204},
{0x00000940, 0x0180088c},
{0x00000974, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x2_port2_config_regs[] = {
{0x00001294, 0xc80060fa},
{0x00001280, 0x08130cea},
{0x00001290, 0x10a0140b},
{0x000012a8, 0xd704010a},
{0x00001300, 0x8d050060},
{0x00001310, 0x0030001c},
{0x00001338, 0xdf008444},
{0x0000133c, 0x78422204},
{0x00001348, 0x7821f940},
{0x0000134c, 0x5a000433},
{0x00001094, 0xc80060fa},
{0x00001080, 0xade75dd8},
{0x00001090, 0x10a0540b},
{0x000010a8, 0xb101010a},
{0x00001100, 0x33053460},
{0x00001110, 0x0030101c},
{0x00001138, 0xdf808444},
{0x0000113c, 0xc8422204},
{0x00001140, 0x8180088c},
{0x00001174, 0x00000000},
{0x00000e94, 0xc80060fa},
{0x00000e80, 0x0fbf16d0},
{0x00000e90, 0x10a0540b},
{0x00000ea8, 0x7a01010a},
{0x00000f00, 0xf5053460},
{0x00000f10, 0xc030101c},
{0x00000f38, 0xdf808444},
{0x00000f3c, 0xc8422204},
{0x00000f40, 0x8180088c},
{0x00000000, 0x00000000}
};
static const struct phy_reg x2_port3_config_regs[] = {
{0x00001894, 0xc80060fa},
{0x00001880, 0x0f90fd6a},
{0x00001890, 0x10a0840b},
{0x000018a8, 0xdf04010a},
{0x00001900, 0x57050060},
{0x00001910, 0x0030001c},
{0x00001938, 0x5f004444},
{0x0000193c, 0x78464204},
{0x00001948, 0x7821f940},
{0x0000194c, 0xb2000433},
{0x00001694, 0xc80060fa},
{0x00001680, 0x0ef6d058},
{0x00001690, 0x10a0540b},
{0x000016a8, 0x7a01010a},
{0x00001700, 0x69053460},
{0x00001710, 0xa030101c},
{0x00001738, 0xdf808444},
{0x0000173c, 0xc8422204},
{0x00001740, 0x0180088c},
{0x00001774, 0x00000000},
{0x00001494, 0xc80060fa},
{0x00001480, 0xf9d34bd0},
{0x00001490, 0x10a0540b},
{0x000014a8, 0x7a01010a},
{0x00001500, 0x1b053460},
{0x00001510, 0x0030101c},
{0x00001538, 0xdf808444},
{0x0000153c, 0xc8422204},
{0x00001540, 0x8180088c},
{0x00001574, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x4_port0_config_regs[] = {
{0x00000694, 0xc80060fa},
{0x00000680, 0x3d4f78fa},
{0x00000690, 0x10a0140b},
{0x000006a8, 0xdf04010a},
{0x00000700, 0x57050060},
{0x00000710, 0x0030001c},
{0x00000738, 0x5f004444},
{0x0000073c, 0x78464204},
{0x00000748, 0x7821f940},
{0x0000074c, 0xb2000433},
{0x00000494, 0xfe6030fa},
{0x00000480, 0x29ef5ed8},
{0x00000490, 0x10a0540b},
{0x000004a8, 0x7a01010a},
{0x00000500, 0xef053460},
{0x00000510, 0xe030101c},
{0x00000538, 0xdf808444},
{0x0000053c, 0xc8422204},
{0x00000540, 0x0180088c},
{0x00000574, 0x00000004},
{0x00000294, 0x23e030fa},
{0x00000280, 0xcb45b950},
{0x00000290, 0x10a0540b},
{0x000002a8, 0x8c01010a},
{0x00000300, 0xef053460},
{0x00000310, 0x8030101c},
{0x00000338, 0x41808444},
{0x0000033c, 0x32422204},
{0x00000340, 0x0180088c},
{0x00000374, 0x00000004},
{0x00000894, 0x5620b0fa},
{0x00000880, 0x4d4f21dc},
{0x00000890, 0x10a0540b},
{0x000008a8, 0x5601010a},
{0x00000900, 0xef053460},
{0x00000910, 0x8030101c},
{0x00000938, 0xdf808444},
{0x0000093c, 0xc8422204},
{0x00000940, 0x0180088c},
{0x00000974, 0x00000004},
{0x00000a94, 0xc91030fa},
{0x00000a80, 0x5a166ecc},
{0x00000a90, 0x10a0540b},
{0x00000aa8, 0x5d01010a},
{0x00000b00, 0xef053460},
{0x00000b10, 0xa030101c},
{0x00000b38, 0xdf808444},
{0x00000b3c, 0xc8422204},
{0x00000b40, 0x0180088c},
{0x00000b74, 0x00000004},
{0x00000000, 0x00000000}
};
static const struct phy_reg x4_port1_config_regs[] = {
{0x00000000, 0x00000000}
};
static const struct phy_reg x4_port2_config_regs[] = {
{0x00001294, 0x28f000fa},
{0x00001280, 0x08130cfa},
{0x00001290, 0x10c0140b},
{0x000012a8, 0xd704010a},
{0x00001300, 0x8d050060},
{0x00001310, 0x0030001c},
{0x00001338, 0xdf008444},
{0x0000133c, 0x78422204},
{0x00001348, 0x7821f940},
{0x0000134c, 0x5a000433},
{0x00001094, 0x2d20b0fa},
{0x00001080, 0xade75dd8},
{0x00001090, 0x10a0540b},
{0x000010a8, 0xb101010a},
{0x00001100, 0x33053460},
{0x00001110, 0x0030101c},
{0x00001138, 0xdf808444},
{0x0000113c, 0xc8422204},
{0x00001140, 0x8180088c},
{0x00001174, 0x00000004},
{0x00000e94, 0xd308d0fa},
{0x00000e80, 0x0fbf16d0},
{0x00000e90, 0x10a0540b},
{0x00000ea8, 0x2c01010a},
{0x00000f00, 0xf5053460},
{0x00000f10, 0xc030101c},
{0x00000f38, 0xdf808444},
{0x00000f3c, 0xc8422204},
{0x00000f40, 0x8180088c},
{0x00000f74, 0x00000004},
{0x00001494, 0x136850fa},
{0x00001480, 0xf9d34bdc},
{0x00001490, 0x10a0540b},
{0x000014a8, 0x5a01010a},
{0x00001500, 0x1b053460},
{0x00001510, 0x0030101c},
{0x00001538, 0xdf808444},
{0x0000153c, 0xc8422204},
{0x00001540, 0x8180088c},
{0x00001574, 0x00000004},
{0x00001694, 0x3050d0fa},
{0x00001680, 0x0ef6d04c},
{0x00001690, 0x10a0540b},
{0x000016a8, 0xe301010a},
{0x00001700, 0x69053460},
{0x00001710, 0xa030101c},
{0x00001738, 0xdf808444},
{0x0000173c, 0xc8422204},
{0x00001740, 0x0180088c},
{0x00001774, 0x00000004},
{0x00000000, 0x00000000}
};
static const struct phy_reg x4_port3_config_regs[] = {
{0x00000000, 0x00000000}
};
static const struct phy_reg *x1_config_regs[4] = {
x1_port0_config_regs,
x1_port1_config_regs,
x1_port2_config_regs,
x1_port3_config_regs
};
static const struct phy_reg *x2_config_regs[4] = {
x2_port0_config_regs,
x2_port1_config_regs,
x2_port2_config_regs,
x2_port3_config_regs
};
static const struct phy_reg *x4_config_regs[4] = {
x4_port0_config_regs,
x4_port1_config_regs,
x4_port2_config_regs,
x4_port3_config_regs
};
static const struct phy_reg **config_regs[3] = {
x1_config_regs,
x2_config_regs,
x4_config_regs
};
static int ipu6_isys_mcd_phy_powerup_ack(struct ipu6_isys *isys, u8 id)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u32 val;
int ret;
val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
val |= CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN;
writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id),
val, val & CSI_REG_HUB_GPREG_PHY_POWER_ACK,
200, MCD_PHY_POWER_STATUS_TIMEOUT);
if (ret)
dev_err(dev, "PHY%d powerup ack timeout", id);
return ret;
}
static int ipu6_isys_mcd_phy_powerdown_ack(struct ipu6_isys *isys, u8 id)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u32 val;
int ret;
writel(0, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id),
val, !(val & CSI_REG_HUB_GPREG_PHY_POWER_ACK),
200, MCD_PHY_POWER_STATUS_TIMEOUT);
if (ret)
dev_err(dev, "PHY%d powerdown ack timeout", id);
return ret;
}
static void ipu6_isys_mcd_phy_reset(struct ipu6_isys *isys, u8 id, bool assert)
{
void __iomem *isys_base = isys->pdata->base;
u32 val;
val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
if (assert)
val |= CSI_REG_HUB_GPREG_PHY_CTL_RESET;
else
val &= ~(CSI_REG_HUB_GPREG_PHY_CTL_RESET);
writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
}
static int ipu6_isys_mcd_phy_ready(struct ipu6_isys *isys, u8 id)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u32 val;
int ret;
ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id),
val, val & CSI_REG_HUB_GPREG_PHY_READY,
200, MCD_PHY_POWER_STATUS_TIMEOUT);
if (ret)
dev_err(dev, "PHY%d ready ack timeout", id);
return ret;
}
static void ipu6_isys_mcd_phy_common_init(struct ipu6_isys *isys)
{
struct ipu6_bus_device *adev = isys->adev;
struct ipu6_device *isp = adev->isp;
void __iomem *isp_base = isp->base;
struct sensor_async_sd *s_asd;
struct v4l2_async_connection *asc;
void __iomem *phy_base;
unsigned int i;
u8 phy_id;
list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) {
s_asd = container_of(asc, struct sensor_async_sd, asc);
phy_id = s_asd->csi2.port / 4;
phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id);
for (i = 0; i < ARRAY_SIZE(common_init_regs); i++)
writel(common_init_regs[i].val,
phy_base + common_init_regs[i].reg);
}
}
static int ipu6_isys_driver_port_to_phy_port(struct ipu6_isys_csi2_config *cfg)
{
int phy_port;
int ret;
if (!(cfg->nlanes == 4 || cfg->nlanes == 2 || cfg->nlanes == 1))
return -EINVAL;
/* B,F -> C0 A,E -> C1 C,G -> C2 D,H -> C4 */
/* normalize driver port number */
phy_port = cfg->port % 4;
/* swap port number only for A and B */
if (phy_port == 0)
phy_port = 1;
else if (phy_port == 1)
phy_port = 0;
ret = phy_port;
/* check validity per lane configuration */
if (cfg->nlanes == 4 && !(phy_port == 0 || phy_port == 2))
ret = -EINVAL;
else if ((cfg->nlanes == 2 || cfg->nlanes == 1) &&
!(phy_port >= 0 && phy_port <= 3))
ret = -EINVAL;
return ret;
}
static int ipu6_isys_mcd_phy_config(struct ipu6_isys *isys)
{
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_bus_device *adev = isys->adev;
const struct phy_reg **phy_config_regs;
struct ipu6_device *isp = adev->isp;
void __iomem *isp_base = isp->base;
struct sensor_async_sd *s_asd;
struct ipu6_isys_csi2_config cfg;
struct v4l2_async_connection *asc;
int phy_port, phy_id;
unsigned int i;
void __iomem *phy_base;
list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) {
s_asd = container_of(asc, struct sensor_async_sd, asc);
cfg.port = s_asd->csi2.port;
cfg.nlanes = s_asd->csi2.nlanes;
phy_port = ipu6_isys_driver_port_to_phy_port(&cfg);
if (phy_port < 0) {
dev_err(dev, "invalid port %d for lane %d", cfg.port,
cfg.nlanes);
return -ENXIO;
}
phy_id = cfg.port / 4;
phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id);
dev_dbg(dev, "port%d PHY%u lanes %u\n", cfg.port, phy_id,
cfg.nlanes);
phy_config_regs = config_regs[cfg.nlanes / 2];
cfg.port = phy_port;
for (i = 0; phy_config_regs[cfg.port][i].reg; i++)
writel(phy_config_regs[cfg.port][i].val,
phy_base + phy_config_regs[cfg.port][i].reg);
}
return 0;
}
#define CSI_MCD_PHY_NUM 2
static refcount_t phy_power_ref_count[CSI_MCD_PHY_NUM];
int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u8 port, phy_id;
refcount_t *ref;
int ret;
port = cfg->port;
phy_id = port / 4;
ref = &phy_power_ref_count[phy_id];
dev_dbg(dev, "for phy %d port %d, lanes: %d\n", phy_id, port,
cfg->nlanes);
if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
dev_warn(dev, "invalid port ID %d\n", port);
return -EINVAL;
}
if (on) {
if (refcount_read(ref)) {
dev_dbg(dev, "for phy %d is already UP", phy_id);
refcount_inc(ref);
return 0;
}
ret = ipu6_isys_mcd_phy_powerup_ack(isys, phy_id);
if (ret)
return ret;
ipu6_isys_mcd_phy_reset(isys, phy_id, 0);
ipu6_isys_mcd_phy_common_init(isys);
ret = ipu6_isys_mcd_phy_config(isys);
if (ret)
return ret;
ipu6_isys_mcd_phy_reset(isys, phy_id, 1);
ret = ipu6_isys_mcd_phy_ready(isys, phy_id);
if (ret)
return ret;
refcount_set(ref, 1);
return 0;
}
if (!refcount_dec_and_test(ref))
return 0;
return ipu6_isys_mcd_phy_powerdown_ack(isys, phy_id);
}
@@ -0,0 +1,857 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/atomic.h>
#include <linux/cleanup.h>
#include <linux/bug.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-dma-sg.h>
#include <media/videobuf2-v4l2.h>
#include "ipu6-bus.h"
#include "ipu6-dma.h"
#include "ipu6-fw-isys.h"
#include "ipu6-isys.h"
#include "ipu6-isys-video.h"
static int ipu6_isys_buf_init(struct vb2_buffer *vb)
{
struct ipu6_isys *isys = vb2_get_drv_priv(vb->vb2_queue);
struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0);
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_video_buffer *ivb =
vb2_buffer_to_ipu6_isys_video_buffer(vvb);
int ret;
ret = ipu6_dma_map_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0);
if (ret)
return ret;
ivb->dma_addr = sg_dma_address(sg->sgl);
return 0;
}
static void ipu6_isys_buf_cleanup(struct vb2_buffer *vb)
{
struct ipu6_isys *isys = vb2_get_drv_priv(vb->vb2_queue);
struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0);
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_video_buffer *ivb =
vb2_buffer_to_ipu6_isys_video_buffer(vvb);
ivb->dma_addr = 0;
ipu6_dma_unmap_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0);
}
static int ipu6_isys_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
unsigned int *num_planes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
u32 size = ipu6_isys_get_data_size(av);
/* num_planes == 0: we're being called through VIDIOC_REQBUFS */
if (!*num_planes) {
sizes[0] = size;
} else if (sizes[0] < size) {
dev_dbg(dev, "%s: queue setup: size %u < %u\n",
av->vdev.name, sizes[0], size);
return -EINVAL;
}
*num_planes = 1;
return 0;
}
static int ipu6_isys_buf_prepare(struct vb2_buffer *vb)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
u32 bytesperline = ipu6_isys_get_bytes_per_line(av);
u32 height = ipu6_isys_get_frame_height(av);
u32 size = ipu6_isys_get_data_size(av);
dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n",
av->vdev.name, size, vb2_plane_size(vb, 0));
if (size > vb2_plane_size(vb, 0))
return -EINVAL;
vb2_set_plane_payload(vb, 0, bytesperline * height);
return 0;
}
/*
* Queue a buffer list back to incoming or active queues. The buffers
* are removed from the buffer list.
*/
void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl,
unsigned long op_flags,
enum vb2_buffer_state state)
{
struct ipu6_isys_buffer *ib, *ib_safe;
unsigned long flags;
bool first = true;
if (!bl)
return;
WARN_ON_ONCE(!bl->nbufs);
WARN_ON_ONCE(op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE &&
op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING);
list_for_each_entry_safe(ib, ib_safe, &bl->head, head) {
struct ipu6_isys_video *av;
struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
struct ipu6_isys_queue *aq =
vb2_queue_to_isys_queue(vb->vb2_queue);
struct device *dev;
av = ipu6_isys_queue_to_video(aq);
dev = &av->isys->adev->auxdev.dev;
spin_lock_irqsave(&aq->lock, flags);
list_del(&ib->head);
if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE)
list_add(&ib->head, &aq->active);
else if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING)
list_add_tail(&ib->head, &aq->incoming);
spin_unlock_irqrestore(&aq->lock, flags);
if (first) {
dev_dbg(dev,
"queue buf list %p flags %lx, s %d, %d bufs\n",
bl, op_flags, state, bl->nbufs);
first = false;
}
bl->nbufs--;
}
WARN_ON(bl->nbufs);
}
/*
* flush_firmware_streamon_fail() - Flush in cases where requests may
* have been queued to firmware and the *firmware streamon fails for a
* reason or another.
*/
static void flush_firmware_streamon_fail(struct ipu6_isys_stream *stream)
{
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_queue *aq;
unsigned long flags;
lockdep_assert_held(&stream->mutex);
list_for_each_entry(aq, &stream->queues, node) {
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct ipu6_isys_buffer *ib, *ib_safe;
spin_lock_irqsave(&aq->lock, flags);
list_for_each_entry_safe(ib, ib_safe, &aq->active, head) {
struct vb2_buffer *vb =
ipu6_isys_buffer_to_vb2_buffer(ib);
list_del(&ib->head);
if (av->streaming) {
dev_dbg(dev,
"%s: queue buffer %u back to incoming\n",
av->vdev.name, vb->index);
/* Queue already streaming, return to driver. */
list_add(&ib->head, &aq->incoming);
continue;
}
/* Queue not yet streaming, return to user. */
dev_dbg(dev, "%s: return %u back to videobuf2\n",
av->vdev.name, vb->index);
vb2_buffer_done(ipu6_isys_buffer_to_vb2_buffer(ib),
VB2_BUF_STATE_QUEUED);
}
spin_unlock_irqrestore(&aq->lock, flags);
}
}
/*
* Attempt obtaining a buffer list from the incoming queues, a list of buffers
* that contains one entry from each video buffer queue. If a buffer can't be
* obtained from every queue, the buffers are returned back to the queue.
*/
static int buffer_list_get(struct ipu6_isys_stream *stream,
struct ipu6_isys_buffer_list *bl)
{
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_queue *aq;
unsigned long flags;
unsigned long buf_flag = IPU6_ISYS_BUFFER_LIST_FL_INCOMING;
lockdep_assert_held(&stream->mutex);
bl->nbufs = 0;
INIT_LIST_HEAD(&bl->head);
list_for_each_entry(aq, &stream->queues, node) {
struct ipu6_isys_buffer *ib;
spin_lock_irqsave(&aq->lock, flags);
if (list_empty(&aq->incoming)) {
spin_unlock_irqrestore(&aq->lock, flags);
if (!list_empty(&bl->head))
ipu6_isys_buffer_list_queue(bl, buf_flag, 0);
return -ENODATA;
}
ib = list_last_entry(&aq->incoming,
struct ipu6_isys_buffer, head);
dev_dbg(dev, "buffer: %s: buffer %u\n",
ipu6_isys_queue_to_video(aq)->vdev.name,
ipu6_isys_buffer_to_vb2_buffer(ib)->index);
list_del(&ib->head);
list_add(&ib->head, &bl->head);
spin_unlock_irqrestore(&aq->lock, flags);
bl->nbufs++;
}
dev_dbg(dev, "get buffer list %p, %u buffers\n", bl, bl->nbufs);
return 0;
}
static void
ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb,
struct ipu6_fw_isys_frame_buff_set_abi *set)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_video_buffer *ivb =
vb2_buffer_to_ipu6_isys_video_buffer(vvb);
set->output_pins[aq->fw_output].addr = ivb->dma_addr;
set->output_pins[aq->fw_output].out_buf_id = vb->index + 1;
}
/*
* Convert a buffer list to a isys fw ABI framebuffer set. The
* buffer list is not modified.
*/
#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30)
void
ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set,
struct ipu6_isys_stream *stream,
struct ipu6_isys_buffer_list *bl)
{
struct ipu6_isys_buffer *ib;
WARN_ON(!bl->nbufs);
set->send_irq_sof = 1;
set->send_resp_sof = 1;
set->send_irq_eof = 0;
set->send_resp_eof = 0;
if (stream->streaming)
set->send_irq_capture_ack = 0;
else
set->send_irq_capture_ack = 1;
set->send_irq_capture_done = 0;
set->send_resp_capture_ack = 1;
set->send_resp_capture_done = 1;
if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) {
set->send_resp_capture_ack = 0;
set->send_resp_capture_done = 0;
}
list_for_each_entry(ib, &bl->head, head) {
struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
ipu6_isys_buf_to_fw_frame_buf_pin(vb, set);
}
}
/* Start streaming for real. The buffer list must be available. */
static int ipu6_isys_stream_start(struct ipu6_isys_video *av,
struct ipu6_isys_buffer_list *bl)
{
struct ipu6_isys_stream *stream = av->stream;
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_buffer_list __bl;
int ret;
guard(mutex)(&stream->isys->stream_mutex);
ret = ipu6_isys_video_set_streaming(av, 1, bl);
if (ret)
goto out_requeue;
stream->streaming = 1;
bl = &__bl;
do {
struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL;
struct isys_fw_msgs *msg;
u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE;
ret = buffer_list_get(stream, bl);
if (ret < 0)
break;
msg = ipu6_get_fw_msg_buf(stream);
if (!msg)
return -ENOMEM;
buf = &msg->fw_msg.frame;
ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl);
ipu6_fw_isys_dump_frame_buff_set(dev, buf,
stream->nr_output_pins);
ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE,
0);
ret = ipu6_fw_isys_complex_cmd(stream->isys,
stream->stream_handle, buf,
msg->dma_addr, sizeof(*buf),
send_type);
} while (!WARN_ON(ret));
return 0;
out_requeue:
if (bl && bl->nbufs)
ipu6_isys_buffer_list_queue(bl,
IPU6_ISYS_BUFFER_LIST_FL_INCOMING,
VB2_BUF_STATE_QUEUED);
flush_firmware_streamon_fail(stream);
return ret;
}
static void buf_queue(struct vb2_buffer *vb)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_video_buffer *ivb =
vb2_buffer_to_ipu6_isys_video_buffer(vvb);
struct ipu6_isys_buffer *ib = &ivb->ib;
struct device *dev = &av->isys->adev->auxdev.dev;
struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL;
struct ipu6_isys_stream *stream = av->stream;
struct ipu6_isys_buffer_list bl;
struct isys_fw_msgs *msg;
unsigned long flags;
dma_addr_t dma;
int ret;
dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name);
dma = ivb->dma_addr;
dev_dbg(dev, "iova: iova %pad\n", &dma);
spin_lock_irqsave(&aq->lock, flags);
list_add(&ib->head, &aq->incoming);
spin_unlock_irqrestore(&aq->lock, flags);
if (!vb2_start_streaming_called(vb->vb2_queue)) {
dev_dbg(dev, "start_streaming hasn't been called yet on %s\n",
av->vdev.name);
return;
}
mutex_lock(&stream->mutex);
if (stream->nr_streaming != stream->nr_queues) {
dev_dbg(dev, "not streaming yet, adding to incoming\n");
goto out;
}
/*
* We just put one buffer to the incoming list of this queue
* (above). Let's see whether all queues in the pipeline would
* have a buffer.
*/
ret = buffer_list_get(stream, &bl);
if (ret < 0) {
dev_dbg(dev, "No buffers available\n");
goto out;
}
msg = ipu6_get_fw_msg_buf(stream);
if (!msg) {
ret = -ENOMEM;
goto out;
}
buf = &msg->fw_msg.frame;
ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl);
ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins);
/*
* We must queue the buffers in the buffer list to the
* appropriate video buffer queues BEFORE passing them to the
* firmware since we could get a buffer event back before we
* have queued them ourselves to the active queue.
*/
ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0);
ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle,
buf, msg->dma_addr, sizeof(*buf),
IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE);
if (ret < 0)
dev_err(dev, "send stream capture failed\n");
out:
mutex_unlock(&stream->mutex);
}
static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq)
{
struct v4l2_mbus_framefmt format, *__format;
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
struct media_pad *remote_pad =
media_pad_remote_pad_first(av->vdev.entity.pads);
struct v4l2_subdev *sd;
u32 r_stream, code;
if (!remote_pad)
return -ENOTCONN;
sd = media_entity_to_v4l2_subdev(remote_pad->entity);
r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, remote_pad->index);
struct v4l2_subdev_state *state =
v4l2_subdev_lock_and_get_active_state(sd);
__format = v4l2_subdev_state_get_format(state, remote_pad->index,
r_stream);
if (__format)
format = *__format;
v4l2_subdev_unlock_state(state);
if (!__format) {
dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n",
sd->entity.name, remote_pad->index, r_stream);
return -EPIPE;
}
if (format.width != ipu6_isys_get_frame_width(av) ||
format.height != ipu6_isys_get_frame_height(av)) {
dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n",
ipu6_isys_get_frame_width(av),
ipu6_isys_get_frame_height(av), format.width,
format.height);
return -EINVAL;
}
code = ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0)->code;
if (format.code != code) {
dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n",
code, format.code);
return -EINVAL;
}
return 0;
}
static void return_buffers(struct ipu6_isys_queue *aq,
enum vb2_buffer_state state)
{
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct ipu6_isys_buffer *ib;
bool need_reset = false;
unsigned long flags;
spin_lock_irqsave(&aq->lock, flags);
while (!list_empty(&aq->incoming)) {
struct vb2_buffer *vb;
ib = list_first_entry(&aq->incoming, struct ipu6_isys_buffer,
head);
vb = ipu6_isys_buffer_to_vb2_buffer(ib);
list_del(&ib->head);
spin_unlock_irqrestore(&aq->lock, flags);
vb2_buffer_done(vb, state);
spin_lock_irqsave(&aq->lock, flags);
}
/*
* Something went wrong (FW crash / HW hang / not all buffers
* returned from isys) if there are still buffers queued in active
* queue. We have to clean up places a bit.
*/
while (!list_empty(&aq->active)) {
struct vb2_buffer *vb;
ib = list_first_entry(&aq->active, struct ipu6_isys_buffer,
head);
vb = ipu6_isys_buffer_to_vb2_buffer(ib);
list_del(&ib->head);
spin_unlock_irqrestore(&aq->lock, flags);
vb2_buffer_done(vb, state);
spin_lock_irqsave(&aq->lock, flags);
need_reset = true;
}
spin_unlock_irqrestore(&aq->lock, flags);
if (need_reset) {
mutex_lock(&av->isys->mutex);
av->isys->need_reset = true;
mutex_unlock(&av->isys->mutex);
}
}
static void ipu6_isys_stream_cleanup(struct ipu6_isys_video *av)
{
video_device_pipeline_stop(&av->vdev);
ipu6_isys_put_stream(av->stream);
av->stream = NULL;
}
static int start_streaming(struct vb2_queue *q, unsigned int count)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
const struct ipu6_isys_pixelformat *pfmt =
ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0);
struct ipu6_isys_buffer_list __bl, *bl = NULL;
struct ipu6_isys_stream *stream;
struct media_pad *source_pad, *remote_pad;
int nr_queues, ret;
dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n",
av->vdev.name, ipu6_isys_get_frame_width(av),
ipu6_isys_get_frame_height(av), pfmt->css_pixelformat);
remote_pad = media_pad_remote_pad_unique(&av->pad);
if (IS_ERR(remote_pad)) {
dev_dbg(dev, "failed to get remote pad\n");
ret = PTR_ERR(remote_pad);
goto out_return_buffers;
}
source_pad = media_pad_remote_pad_unique(&remote_pad->entity->pads[0]);
if (IS_ERR(source_pad)) {
dev_dbg(dev, "No external source entity\n");
ret = PTR_ERR(source_pad);
goto out_return_buffers;
}
ret = ipu6_isys_setup_video(av, remote_pad, source_pad, &nr_queues);
if (ret < 0) {
dev_dbg(dev, "failed to setup video\n");
goto out_return_buffers;
}
ret = ipu6_isys_link_fmt_validate(aq);
if (ret) {
dev_dbg(dev,
"%s: link format validation failed (%d)\n",
av->vdev.name, ret);
goto out_pipeline_stop;
}
ret = ipu6_isys_fw_open(av->isys);
if (ret)
goto out_pipeline_stop;
stream = av->stream;
mutex_lock(&stream->mutex);
if (!stream->nr_streaming) {
ret = ipu6_isys_video_prepare_stream(av, source_pad->entity,
nr_queues);
if (ret)
goto out_fw_close;
}
stream->nr_streaming++;
dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming,
stream->nr_queues);
list_add(&aq->node, &stream->queues);
ipu6_isys_configure_stream_watermark(av, source_pad->entity);
ipu6_isys_update_stream_watermark(av, true);
if (stream->nr_streaming != stream->nr_queues)
goto out;
bl = &__bl;
ret = buffer_list_get(stream, bl);
if (ret < 0) {
dev_warn(dev, "no buffer available, DRIVER BUG?\n");
goto out;
}
ret = ipu6_isys_stream_start(av, bl);
if (ret)
goto out_stream_start;
out:
mutex_unlock(&stream->mutex);
return 0;
out_stream_start:
ipu6_isys_update_stream_watermark(av, false);
list_del(&aq->node);
stream->nr_streaming--;
out_fw_close:
mutex_unlock(&stream->mutex);
ipu6_isys_fw_close(av->isys);
out_pipeline_stop:
ipu6_isys_stream_cleanup(av);
out_return_buffers:
return_buffers(aq, VB2_BUF_STATE_QUEUED);
return ret;
}
static void stop_streaming(struct vb2_queue *q)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct ipu6_isys_stream *stream = av->stream;
mutex_lock(&stream->mutex);
ipu6_isys_update_stream_watermark(av, false);
mutex_lock(&av->isys->stream_mutex);
if (stream->nr_streaming == stream->nr_queues && stream->streaming)
ipu6_isys_video_set_streaming(av, 0, NULL);
list_del(&aq->node);
mutex_unlock(&av->isys->stream_mutex);
stream->nr_streaming--;
stream->streaming = 0;
mutex_unlock(&stream->mutex);
ipu6_isys_stream_cleanup(av);
return_buffers(aq, VB2_BUF_STATE_ERROR);
ipu6_isys_fw_close(av->isys);
}
static unsigned int
get_sof_sequence_by_timestamp(struct ipu6_isys_stream *stream, u64 time)
{
struct ipu6_isys *isys = stream->isys;
struct device *dev = &isys->adev->auxdev.dev;
unsigned int i;
/*
* The timestamp is invalid as no TSC in some FPGA platform,
* so get the sequence from pipeline directly in this case.
*/
if (time == 0)
return atomic_read(&stream->sequence) - 1;
for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++)
if (time == stream->seq[i].timestamp) {
dev_dbg(dev, "sof: using seq nr %u for ts %llu\n",
stream->seq[i].sequence, time);
return stream->seq[i].sequence;
}
for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++)
dev_dbg(dev, "sof: sequence %u, timestamp value %llu\n",
stream->seq[i].sequence, stream->seq[i].timestamp);
return 0;
}
static u64 get_sof_ns_delta(struct ipu6_isys_video *av, u64 timestamp)
{
struct ipu6_bus_device *adev = av->isys->adev;
struct ipu6_device *isp = adev->isp;
u64 delta, tsc_now;
ipu6_buttress_tsc_read(isp, &tsc_now);
if (!tsc_now)
return 0;
delta = tsc_now - timestamp;
return ipu6_buttress_tsc_ticks_to_ns(delta, isp);
}
static void
ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, u64 time)
{
struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
struct ipu6_isys_stream *stream = av->stream;
u64 ns;
u32 sequence;
ns = ktime_get_ns() - get_sof_ns_delta(av, time);
sequence = get_sof_sequence_by_timestamp(stream, time);
vbuf->vb2_buf.timestamp = ns;
vbuf->sequence = sequence;
dev_dbg(dev, "buf: %s: buffer done, CPU-timestamp:%lld, sequence:%d\n",
av->vdev.name, ktime_get_ns(), sequence);
dev_dbg(dev, "index:%d, vbuf timestamp:%lld\n", vb->index,
vbuf->vb2_buf.timestamp);
}
static void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib)
{
struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
if (atomic_read(&ib->str2mmio_flag)) {
vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
/*
* Operation on buffer is ended with error and will be reported
* to the userspace when it is de-queued
*/
atomic_set(&ib->str2mmio_flag, 0);
} else {
vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
}
}
static void
ipu6_stream_buf_ready(struct ipu6_isys_stream *stream, u8 pin_id, u32 pin_addr,
u64 time, bool error_check)
{
struct ipu6_isys_queue *aq = stream->output_pins_queue[pin_id];
struct ipu6_isys *isys = stream->isys;
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_isys_buffer *ib;
struct vb2_buffer *vb;
unsigned long flags;
bool first = true;
struct vb2_v4l2_buffer *buf;
spin_lock_irqsave(&aq->lock, flags);
if (list_empty(&aq->active)) {
spin_unlock_irqrestore(&aq->lock, flags);
dev_err(dev, "active queue empty\n");
return;
}
list_for_each_entry_reverse(ib, &aq->active, head) {
struct ipu6_isys_video_buffer *ivb;
struct vb2_v4l2_buffer *vvb;
dma_addr_t addr;
vb = ipu6_isys_buffer_to_vb2_buffer(ib);
vvb = to_vb2_v4l2_buffer(vb);
ivb = vb2_buffer_to_ipu6_isys_video_buffer(vvb);
addr = ivb->dma_addr;
if (pin_addr != addr) {
if (first)
dev_err(dev, "Unexpected buffer address %pad\n",
&addr);
first = false;
continue;
}
if (error_check) {
/*
* Check for error message:
* 'IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO'
*/
atomic_set(&ib->str2mmio_flag, 1);
}
dev_dbg(dev, "buffer: found buffer %pad\n", &addr);
buf = to_vb2_v4l2_buffer(vb);
buf->field = V4L2_FIELD_NONE;
list_del(&ib->head);
spin_unlock_irqrestore(&aq->lock, flags);
ipu6_isys_buf_calc_sequence_time(ib, time);
ipu6_isys_queue_buf_done(ib);
return;
}
dev_err(dev, "Failed to find a matching video buffer\n");
spin_unlock_irqrestore(&aq->lock, flags);
}
void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream,
struct ipu6_fw_isys_resp_info_abi *info)
{
u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0];
bool err = info->error_info.error == IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO;
ipu6_stream_buf_ready(stream, info->pin_id, info->pin.addr, time, err);
}
static const struct vb2_ops ipu6_isys_queue_ops = {
.queue_setup = ipu6_isys_queue_setup,
.buf_init = ipu6_isys_buf_init,
.buf_prepare = ipu6_isys_buf_prepare,
.buf_cleanup = ipu6_isys_buf_cleanup,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
.buf_queue = buf_queue,
};
int ipu6_isys_queue_init(struct ipu6_isys_queue *aq)
{
struct ipu6_isys *isys = ipu6_isys_queue_to_video(aq)->isys;
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct ipu6_bus_device *adev = isys->adev;
int ret;
/* no support for userptr */
if (!aq->vbq.io_modes)
aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF;
aq->vbq.drv_priv = isys;
aq->vbq.ops = &ipu6_isys_queue_ops;
aq->vbq.lock = &av->mutex;
aq->vbq.mem_ops = &vb2_dma_sg_memops;
aq->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
aq->vbq.min_queued_buffers = 1;
aq->vbq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
ret = vb2_queue_init(&aq->vbq);
if (ret)
return ret;
aq->vbq.dev = &adev->isp->pdev->dev;
spin_lock_init(&aq->lock);
INIT_LIST_HEAD(&aq->active);
INIT_LIST_HEAD(&aq->incoming);
return 0;
}
@@ -0,0 +1,70 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_QUEUE_H
#define IPU6_ISYS_QUEUE_H
#include <linux/container_of.h>
#include <linux/atomic.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/spinlock_types.h>
#include <media/videobuf2-v4l2.h>
#include "ipu6-fw-isys.h"
#include "ipu6-isys-video.h"
struct ipu6_isys_stream;
struct ipu6_isys_queue {
struct vb2_queue vbq;
struct list_head node;
spinlock_t lock; /* Protects active and incoming lists */
struct list_head active;
struct list_head incoming;
unsigned int fw_output;
};
struct ipu6_isys_buffer {
struct list_head head;
atomic_t str2mmio_flag;
};
struct ipu6_isys_video_buffer {
struct vb2_v4l2_buffer vb_v4l2;
struct ipu6_isys_buffer ib;
dma_addr_t dma_addr;
};
#define IPU6_ISYS_BUFFER_LIST_FL_INCOMING BIT(0)
#define IPU6_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1)
struct ipu6_isys_buffer_list {
struct list_head head;
unsigned int nbufs;
};
#define vb2_queue_to_isys_queue(__vb2) \
container_of(__vb2, struct ipu6_isys_queue, vbq)
#define ipu6_isys_to_isys_video_buffer(__ib) \
container_of(__ib, struct ipu6_isys_video_buffer, ib)
#define vb2_buffer_to_ipu6_isys_video_buffer(__vvb) \
container_of(__vvb, struct ipu6_isys_video_buffer, vb_v4l2)
#define ipu6_isys_buffer_to_vb2_buffer(__ib) \
(&ipu6_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf)
void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl,
unsigned long op_flags,
enum vb2_buffer_state state);
void
ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set,
struct ipu6_isys_stream *stream,
struct ipu6_isys_buffer_list *bl);
void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream,
struct ipu6_fw_isys_resp_info_abi *info);
int ipu6_isys_queue_init(struct ipu6_isys_queue *aq);
#endif /* IPU6_ISYS_QUEUE_H */
@@ -0,0 +1,389 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bug.h>
#include <linux/device.h>
#include <linux/minmax.h>
#include <media/media-entity.h>
#include <media/mipi-csi2.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-isys-subdev.h"
unsigned int ipu6_isys_mbus_code_to_bpp(u32 code)
{
switch (code) {
case MEDIA_BUS_FMT_RGB888_1X24:
case MEDIA_BUS_FMT_META_24:
return 24;
case MEDIA_BUS_FMT_RGB565_1X16:
case MEDIA_BUS_FMT_UYVY8_1X16:
case MEDIA_BUS_FMT_YUYV8_1X16:
case MEDIA_BUS_FMT_Y16_1X16:
case MEDIA_BUS_FMT_META_16:
return 16;
case MEDIA_BUS_FMT_SBGGR12_1X12:
case MEDIA_BUS_FMT_SGBRG12_1X12:
case MEDIA_BUS_FMT_SGRBG12_1X12:
case MEDIA_BUS_FMT_SRGGB12_1X12:
case MEDIA_BUS_FMT_Y12_1X12:
case MEDIA_BUS_FMT_META_12:
return 12;
case MEDIA_BUS_FMT_SBGGR10_1X10:
case MEDIA_BUS_FMT_SGBRG10_1X10:
case MEDIA_BUS_FMT_SGRBG10_1X10:
case MEDIA_BUS_FMT_SRGGB10_1X10:
case MEDIA_BUS_FMT_Y10_1X10:
case MEDIA_BUS_FMT_META_10:
return 10;
case MEDIA_BUS_FMT_SBGGR8_1X8:
case MEDIA_BUS_FMT_SGBRG8_1X8:
case MEDIA_BUS_FMT_SGRBG8_1X8:
case MEDIA_BUS_FMT_SRGGB8_1X8:
case MEDIA_BUS_FMT_Y8_1X8:
case MEDIA_BUS_FMT_META_8:
return 8;
default:
WARN_ON(1);
return 8;
}
}
unsigned int ipu6_isys_mbus_code_to_mipi(u32 code)
{
switch (code) {
case MEDIA_BUS_FMT_RGB565_1X16:
return MIPI_CSI2_DT_RGB565;
case MEDIA_BUS_FMT_RGB888_1X24:
return MIPI_CSI2_DT_RGB888;
case MEDIA_BUS_FMT_UYVY8_1X16:
case MEDIA_BUS_FMT_YUYV8_1X16:
return MIPI_CSI2_DT_YUV422_8B;
case MEDIA_BUS_FMT_SBGGR16_1X16:
case MEDIA_BUS_FMT_SGBRG16_1X16:
case MEDIA_BUS_FMT_SGRBG16_1X16:
case MEDIA_BUS_FMT_SRGGB16_1X16:
case MEDIA_BUS_FMT_Y16_1X16:
return MIPI_CSI2_DT_RAW16;
case MEDIA_BUS_FMT_SBGGR12_1X12:
case MEDIA_BUS_FMT_SGBRG12_1X12:
case MEDIA_BUS_FMT_SGRBG12_1X12:
case MEDIA_BUS_FMT_SRGGB12_1X12:
case MEDIA_BUS_FMT_Y12_1X12:
return MIPI_CSI2_DT_RAW12;
case MEDIA_BUS_FMT_SBGGR10_1X10:
case MEDIA_BUS_FMT_SGBRG10_1X10:
case MEDIA_BUS_FMT_SGRBG10_1X10:
case MEDIA_BUS_FMT_SRGGB10_1X10:
case MEDIA_BUS_FMT_Y10_1X10:
return MIPI_CSI2_DT_RAW10;
case MEDIA_BUS_FMT_SBGGR8_1X8:
case MEDIA_BUS_FMT_SGBRG8_1X8:
case MEDIA_BUS_FMT_SGRBG8_1X8:
case MEDIA_BUS_FMT_SRGGB8_1X8:
case MEDIA_BUS_FMT_Y8_1X8:
return MIPI_CSI2_DT_RAW8;
case MEDIA_BUS_FMT_META_8:
case MEDIA_BUS_FMT_META_10:
case MEDIA_BUS_FMT_META_12:
case MEDIA_BUS_FMT_META_16:
case MEDIA_BUS_FMT_META_24:
return MIPI_CSI2_DT_EMBEDDED_8B;
default:
/* return unavailable MIPI data type - 0x3f */
WARN_ON(1);
return 0x3f;
}
}
bool ipu6_isys_is_bayer_format(u32 code)
{
switch (code) {
case MEDIA_BUS_FMT_SBGGR8_1X8:
case MEDIA_BUS_FMT_SGBRG8_1X8:
case MEDIA_BUS_FMT_SGRBG8_1X8:
case MEDIA_BUS_FMT_SRGGB8_1X8:
case MEDIA_BUS_FMT_SBGGR10_1X10:
case MEDIA_BUS_FMT_SGBRG10_1X10:
case MEDIA_BUS_FMT_SGRBG10_1X10:
case MEDIA_BUS_FMT_SRGGB10_1X10:
case MEDIA_BUS_FMT_SBGGR12_1X12:
case MEDIA_BUS_FMT_SGBRG12_1X12:
case MEDIA_BUS_FMT_SGRBG12_1X12:
case MEDIA_BUS_FMT_SRGGB12_1X12:
case MEDIA_BUS_FMT_SRGGB16_1X16:
case MEDIA_BUS_FMT_SGRBG16_1X16:
case MEDIA_BUS_FMT_SGBRG16_1X16:
case MEDIA_BUS_FMT_SBGGR16_1X16:
return true;
default:
return false;
}
}
u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y)
{
static const u32 code_map[] = {
MEDIA_BUS_FMT_SRGGB8_1X8,
MEDIA_BUS_FMT_SGRBG8_1X8,
MEDIA_BUS_FMT_SGBRG8_1X8,
MEDIA_BUS_FMT_SBGGR8_1X8,
MEDIA_BUS_FMT_SRGGB10_1X10,
MEDIA_BUS_FMT_SGRBG10_1X10,
MEDIA_BUS_FMT_SGBRG10_1X10,
MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_SRGGB12_1X12,
MEDIA_BUS_FMT_SGRBG12_1X12,
MEDIA_BUS_FMT_SGBRG12_1X12,
MEDIA_BUS_FMT_SBGGR12_1X12,
MEDIA_BUS_FMT_SRGGB16_1X16,
MEDIA_BUS_FMT_SGRBG16_1X16,
MEDIA_BUS_FMT_SGBRG16_1X16,
MEDIA_BUS_FMT_SBGGR16_1X16,
};
u32 i;
for (i = 0; i < ARRAY_SIZE(code_map); i++)
if (code_map[i] == code)
break;
if (WARN_ON(i == ARRAY_SIZE(code_map)))
return code;
return code_map[i ^ (((y & 1) << 1) | (x & 1))];
}
int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *format)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct v4l2_mbus_framefmt *fmt;
struct v4l2_rect *crop;
u32 code = asd->supported_codes[0];
u32 other_pad, other_stream;
unsigned int i;
int ret;
/* No transcoding, source and sink formats must match. */
if ((sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SOURCE) &&
sd->entity.num_pads > 1)
return v4l2_subdev_get_fmt(sd, state, format);
format->format.width = clamp(format->format.width, IPU6_ISYS_MIN_WIDTH,
IPU6_ISYS_MAX_WIDTH);
format->format.height = clamp(format->format.height,
IPU6_ISYS_MIN_HEIGHT,
IPU6_ISYS_MAX_HEIGHT);
for (i = 0; asd->supported_codes[i]; i++) {
if (asd->supported_codes[i] == format->format.code) {
code = asd->supported_codes[i];
break;
}
}
format->format.code = code;
format->format.field = V4L2_FIELD_NONE;
/* Store the format and propagate it to the source pad. */
fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
if (!fmt)
return -EINVAL;
*fmt = format->format;
if (!(sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SINK))
return 0;
/* propagate format to following source pad */
fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
format->stream);
if (!fmt)
return -EINVAL;
*fmt = format->format;
ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
format->pad,
format->stream,
&other_pad,
&other_stream);
if (ret)
return -EINVAL;
crop = v4l2_subdev_state_get_crop(state, other_pad, other_stream);
/* reset crop */
crop->left = 0;
crop->top = 0;
crop->width = fmt->width;
crop->height = fmt->height;
return 0;
}
int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_mbus_code_enum *code)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
const u32 *supported_codes = asd->supported_codes;
u32 index;
for (index = 0; supported_codes[index]; index++) {
if (index == code->index) {
code->code = supported_codes[index];
return 0;
}
}
return -EINVAL;
}
static int subdev_set_routing(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_krouting *routing)
{
static const struct v4l2_mbus_framefmt format = {
.width = 4096,
.height = 3072,
.code = MEDIA_BUS_FMT_SGRBG10_1X10,
.field = V4L2_FIELD_NONE,
};
int ret;
ret = v4l2_subdev_routing_validate(sd, routing,
V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
if (ret)
return ret;
return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
}
u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad)
{
struct v4l2_subdev_state *state;
struct v4l2_subdev_route *routes;
unsigned int i;
u32 source_stream = 0;
state = v4l2_subdev_lock_and_get_active_state(sd);
if (!state)
return 0;
routes = state->routing.routes;
for (i = 0; i < state->routing.num_routes; i++) {
if (routes[i].source_pad == pad) {
source_stream = routes[i].source_stream;
break;
}
}
v4l2_subdev_unlock_state(state);
return source_stream;
}
static int ipu6_isys_subdev_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
struct v4l2_subdev_route route = {
.sink_pad = 0,
.sink_stream = 0,
.source_pad = 1,
.source_stream = 0,
.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
};
struct v4l2_subdev_krouting routing = {
.num_routes = 1,
.routes = &route,
};
return subdev_set_routing(sd, state, &routing);
}
int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
enum v4l2_subdev_format_whence which,
struct v4l2_subdev_krouting *routing)
{
return subdev_set_routing(sd, state, routing);
}
static const struct v4l2_subdev_internal_ops ipu6_isys_subdev_internal_ops = {
.init_state = ipu6_isys_subdev_init_state,
};
int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
const struct v4l2_subdev_ops *ops,
unsigned int nr_ctrls,
unsigned int num_sink_pads,
unsigned int num_source_pads)
{
unsigned int num_pads = num_sink_pads + num_source_pads;
unsigned int i;
int ret;
v4l2_subdev_init(&asd->sd, ops);
asd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
V4L2_SUBDEV_FL_HAS_EVENTS |
V4L2_SUBDEV_FL_STREAMS;
asd->sd.owner = THIS_MODULE;
asd->sd.dev = &asd->isys->adev->auxdev.dev;
asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
asd->sd.internal_ops = &ipu6_isys_subdev_internal_ops;
asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads,
sizeof(*asd->pad), GFP_KERNEL);
if (!asd->pad)
return -ENOMEM;
for (i = 0; i < num_sink_pads; i++)
asd->pad[i].flags = MEDIA_PAD_FL_SINK |
MEDIA_PAD_FL_MUST_CONNECT;
for (i = num_sink_pads; i < num_pads; i++)
asd->pad[i].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&asd->sd.entity, num_pads, asd->pad);
if (ret)
return ret;
if (asd->ctrl_init) {
ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, nr_ctrls);
if (ret)
goto out_media_entity_cleanup;
asd->ctrl_init(&asd->sd);
if (asd->ctrl_handler.error) {
ret = asd->ctrl_handler.error;
goto out_v4l2_ctrl_handler_free;
}
asd->sd.ctrl_handler = &asd->ctrl_handler;
}
asd->source = -1;
return 0;
out_v4l2_ctrl_handler_free:
v4l2_ctrl_handler_free(&asd->ctrl_handler);
out_media_entity_cleanup:
media_entity_cleanup(&asd->sd.entity);
return ret;
}
void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd)
{
media_entity_cleanup(&asd->sd.entity);
v4l2_ctrl_handler_free(&asd->ctrl_handler);
}
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_SUBDEV_H
#define IPU6_ISYS_SUBDEV_H
#include <linux/container_of.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
struct ipu6_isys;
struct ipu6_isys_subdev {
struct v4l2_subdev sd;
struct ipu6_isys *isys;
u32 const *supported_codes;
struct media_pad *pad;
struct v4l2_ctrl_handler ctrl_handler;
void (*ctrl_init)(struct v4l2_subdev *sd);
int source; /* SSI stream source; -1 if unset */
};
#define to_ipu6_isys_subdev(__sd) \
container_of(__sd, struct ipu6_isys_subdev, sd)
unsigned int ipu6_isys_mbus_code_to_bpp(u32 code);
unsigned int ipu6_isys_mbus_code_to_mipi(u32 code);
bool ipu6_isys_is_bayer_format(u32 code);
u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y);
int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *fmt);
int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_mbus_code_enum
*code);
u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad);
int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
enum v4l2_subdev_format_whence which,
struct v4l2_subdev_krouting *routing);
int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
const struct v4l2_subdev_ops *ops,
unsigned int nr_ctrls,
unsigned int num_sink_pads,
unsigned int num_source_pads);
void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd);
#endif /* IPU6_ISYS_SUBDEV_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_VIDEO_H
#define IPU6_ISYS_VIDEO_H
#include <linux/atomic.h>
#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <media/media-entity.h>
#include <media/v4l2-dev.h>
#include "ipu6-isys-queue.h"
#define IPU6_ISYS_OUTPUT_PINS 11
#define IPU6_ISYS_MAX_PARALLEL_SOF 2
struct file;
struct ipu6_isys;
struct ipu6_isys_csi2;
struct ipu6_isys_subdev;
struct ipu6_isys_pixelformat {
u32 pixelformat;
u32 bpp;
u32 bpp_packed;
u32 code;
u32 css_pixelformat;
bool is_meta;
};
struct sequence_info {
unsigned int sequence;
u64 timestamp;
};
/*
* Align with firmware stream. Each stream represents a CSI virtual channel.
* May map to multiple video devices
*/
struct ipu6_isys_stream {
struct mutex mutex;
atomic_t sequence;
unsigned int seq_index;
struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF];
int stream_source;
int stream_handle;
unsigned int nr_output_pins;
struct ipu6_isys_subdev *asd;
int nr_queues; /* Number of capture queues */
int nr_streaming;
int streaming; /* Has streaming been really started? */
struct list_head queues;
struct completion stream_open_completion;
struct completion stream_close_completion;
struct completion stream_start_completion;
struct completion stream_stop_completion;
struct ipu6_isys *isys;
struct ipu6_isys_queue *output_pins_queue[IPU6_ISYS_OUTPUT_PINS];
int error;
u8 vc;
};
struct video_stream_watermark {
u32 width;
u32 height;
u32 hblank;
u32 frame_rate;
u64 pixel_rate;
u64 stream_data_rate;
u16 sram_gran_shift;
u16 sram_gran_size;
struct list_head stream_node;
};
struct ipu6_isys_video {
struct ipu6_isys_queue aq;
/* Serialise access to other fields in the struct. */
struct mutex mutex;
struct media_pad pad;
struct video_device vdev;
struct v4l2_pix_format pix_fmt;
struct v4l2_meta_format meta_fmt;
struct ipu6_isys *isys;
struct ipu6_isys_csi2 *csi2;
struct ipu6_isys_stream *stream;
unsigned int streaming;
struct video_stream_watermark watermark;
u32 source_stream;
u8 vc;
u8 dt;
};
#define ipu6_isys_queue_to_video(__aq) \
container_of(__aq, struct ipu6_isys_video, aq)
extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts[];
extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[];
const struct ipu6_isys_pixelformat *
ipu6_isys_get_isys_format(u32 pixelformat, u32 code);
int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av,
struct media_entity *source_entity,
int nr_queues);
int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state,
struct ipu6_isys_buffer_list *bl);
int ipu6_isys_fw_open(struct ipu6_isys *isys);
void ipu6_isys_fw_close(struct ipu6_isys *isys);
int ipu6_isys_setup_video(struct ipu6_isys_video *av,
struct media_pad *remote_pad,
struct media_pad *source_pad, int *nr_queues);
int ipu6_isys_video_init(struct ipu6_isys_video *av);
void ipu6_isys_video_cleanup(struct ipu6_isys_video *av);
void ipu6_isys_put_stream(struct ipu6_isys_stream *stream);
struct ipu6_isys_stream *
ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle);
struct ipu6_isys_stream *
ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc);
void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av,
struct media_entity *source);
void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state);
u32 ipu6_isys_get_format(struct ipu6_isys_video *av);
u32 ipu6_isys_get_data_size(struct ipu6_isys_video *av);
u32 ipu6_isys_get_bytes_per_line(struct ipu6_isys_video *av);
u32 ipu6_isys_get_frame_width(struct ipu6_isys_video *av);
u32 ipu6_isys_get_frame_height(struct ipu6_isys_video *av);
#endif /* IPU6_ISYS_VIDEO_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,200 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_H
#define IPU6_ISYS_H
#include <linux/irqreturn.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/pm_qos.h>
#include <linux/spinlock_types.h>
#include <linux/types.h>
#include <media/media-device.h>
#include <media/v4l2-async.h>
#include <media/v4l2-device.h>
#include "ipu6.h"
#include "ipu6-fw-isys.h"
#include "ipu6-isys-csi2.h"
#include "ipu6-isys-video.h"
struct ipu6_bus_device;
#define IPU6_ISYS_ENTITY_PREFIX "Intel IPU6"
/* FW support max 16 streams */
#define IPU6_ISYS_MAX_STREAMS 16
#define ISYS_UNISPART_IRQS (IPU6_ISYS_UNISPART_IRQ_SW | \
IPU6_ISYS_UNISPART_IRQ_CSI0 | \
IPU6_ISYS_UNISPART_IRQ_CSI1)
/*
* Current message queue configuration. These must be big enough
* so that they never gets full. Queues are located in system memory
*/
#define IPU6_ISYS_SIZE_RECV_QUEUE 40
#define IPU6_ISYS_SIZE_SEND_QUEUE 40
#define IPU6_ISYS_SIZE_PROXY_RECV_QUEUE 5
#define IPU6_ISYS_SIZE_PROXY_SEND_QUEUE 5
#define IPU6_ISYS_NUM_RECV_QUEUE 1
#define IPU6_ISYS_MIN_WIDTH 2U
#define IPU6_ISYS_MIN_HEIGHT 1U
#define IPU6_ISYS_MAX_WIDTH 4672U
#define IPU6_ISYS_MAX_HEIGHT 3416U
/* the threshold granularity is 2KB on IPU6 */
#define IPU6_SRAM_GRANULARITY_SHIFT 11
#define IPU6_SRAM_GRANULARITY_SIZE 2048
/* the threshold granularity is 1KB on IPU6SE */
#define IPU6SE_SRAM_GRANULARITY_SHIFT 10
#define IPU6SE_SRAM_GRANULARITY_SIZE 1024
/* IS pixel buffer is 256KB, MaxSRAMSize is 200KB on IPU6 */
#define IPU6_MAX_SRAM_SIZE (200 << 10)
/* IS pixel buffer is 128KB, MaxSRAMSize is 96KB on IPU6SE */
#define IPU6SE_MAX_SRAM_SIZE (96 << 10)
#define IPU6EP_LTR_VALUE 200
#define IPU6EP_MIN_MEMOPEN_TH 0x4
#define IPU6EP_MTL_LTR_VALUE 1023
#define IPU6EP_MTL_MIN_MEMOPEN_TH 0xc
struct ltr_did {
union {
u32 value;
struct {
u8 val0;
u8 val1;
u8 val2;
u8 val3;
} bits;
} lut_ltr;
union {
u32 value;
struct {
u8 th0;
u8 th1;
u8 th2;
u8 th3;
} bits;
} lut_fill_time;
};
struct isys_iwake_watermark {
bool iwake_enabled;
bool force_iwake_disable;
u32 iwake_threshold;
u64 isys_pixelbuffer_datarate;
struct ltr_did ltrdid;
struct mutex mutex; /* protect whole struct */
struct ipu6_isys *isys;
struct list_head video_list;
};
struct ipu6_isys_csi2_config {
u32 nlanes;
u32 port;
};
struct sensor_async_sd {
struct v4l2_async_connection asc;
struct ipu6_isys_csi2_config csi2;
};
/*
* struct ipu6_isys
*
* @media_dev: Media device
* @v4l2_dev: V4L2 device
* @adev: ISYS bus device
* @power: Is ISYS powered on or not?
* @isr_bits: Which bits does the ISR handle?
* @power_lock: Serialise access to power (power state in general)
* @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers
* @streams_lock: serialise access to streams
* @streams: streams per firmware stream ID
* @fwcom: fw communication layer private pointer
* or optional external library private pointer
* @phy_termcal_val: the termination calibration value, only used for DWC PHY
* @need_reset: Isys requires d0i0->i3 transition
* @ref_count: total number of callers fw open
* @mutex: serialise access isys video open/release related operations
* @stream_mutex: serialise stream start and stop, queueing requests
* @pdata: platform data pointer
* @csi2: CSI-2 receivers
*/
struct ipu6_isys {
struct media_device media_dev;
struct v4l2_device v4l2_dev;
struct ipu6_bus_device *adev;
int power;
spinlock_t power_lock;
u32 isr_csi2_bits;
u32 csi2_rx_ctrl_cached;
spinlock_t streams_lock;
struct ipu6_isys_stream streams[IPU6_ISYS_MAX_STREAMS];
int streams_ref_count[IPU6_ISYS_MAX_STREAMS];
void *fwcom;
u32 phy_termcal_val;
bool need_reset;
bool icache_prefetch;
bool csi2_cse_ipc_not_supported;
unsigned int ref_count;
unsigned int stream_opened;
unsigned int sensor_type;
struct mutex mutex;
struct mutex stream_mutex;
struct ipu6_isys_pdata *pdata;
int (*phy_set_power)(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on);
struct ipu6_isys_csi2 *csi2;
struct pm_qos_request pm_qos;
spinlock_t listlock; /* Protect framebuflist */
struct list_head framebuflist;
struct list_head framebuflist_fw;
struct v4l2_async_notifier notifier;
struct isys_iwake_watermark iwake_watermark;
};
struct isys_fw_msgs {
union {
u64 dummy;
struct ipu6_fw_isys_frame_buff_set_abi frame;
struct ipu6_fw_isys_stream_cfg_data_abi stream;
} fw_msg;
struct list_head head;
dma_addr_t dma_addr;
};
struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream);
void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, uintptr_t data);
void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys);
extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops;
void update_watermark_setting(struct ipu6_isys *isys);
int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on);
int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on);
int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on);
#endif /* IPU6_ISYS_H */
@@ -0,0 +1,806 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <asm/barrier.h>
#include <linux/align.h>
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/bug.h>
#include <linux/cacheflush.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/iova.h>
#include <linux/math.h>
#include <linux/minmax.h>
#include <linux/mm.h>
#include <linux/pfn.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include "ipu6.h"
#include "ipu6-dma.h"
#include "ipu6-mmu.h"
#include "ipu6-platform-regs.h"
#define ISP_PAGE_SHIFT 12
#define ISP_PAGE_SIZE BIT(ISP_PAGE_SHIFT)
#define ISP_PAGE_MASK (~(ISP_PAGE_SIZE - 1))
#define ISP_L1PT_SHIFT 22
#define ISP_L1PT_MASK (~((1U << ISP_L1PT_SHIFT) - 1))
#define ISP_L2PT_SHIFT 12
#define ISP_L2PT_MASK (~(ISP_L1PT_MASK | (~(ISP_PAGE_MASK))))
#define ISP_L1PT_PTES 1024
#define ISP_L2PT_PTES 1024
#define ISP_PADDR_SHIFT 12
#define REG_TLB_INVALIDATE 0x0000
#define REG_L1_PHYS 0x0004 /* 27-bit pfn */
#define REG_INFO 0x0008
#define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT)
static void tlb_invalidate(struct ipu6_mmu *mmu)
{
unsigned long flags;
unsigned int i;
spin_lock_irqsave(&mmu->ready_lock, flags);
if (!mmu->ready) {
spin_unlock_irqrestore(&mmu->ready_lock, flags);
return;
}
for (i = 0; i < mmu->nr_mmus; i++) {
/*
* To avoid the HW bug induced dead lock in some of the IPU6
* MMUs on successive invalidate calls, we need to first do a
* read to the page table base before writing the invalidate
* register. MMUs which need to implement this WA, will have
* the insert_read_before_invalidate flags set as true.
* Disregard the return value of the read.
*/
if (mmu->mmu_hw[i].insert_read_before_invalidate)
readl(mmu->mmu_hw[i].base + REG_L1_PHYS);
writel(0xffffffff, mmu->mmu_hw[i].base +
REG_TLB_INVALIDATE);
/*
* The TLB invalidation is a "single cycle" (IOMMU clock cycles)
* When the actual MMIO write reaches the IPU6 TLB Invalidate
* register, wmb() will force the TLB invalidate out if the CPU
* attempts to update the IOMMU page table (or sooner).
*/
wmb();
}
spin_unlock_irqrestore(&mmu->ready_lock, flags);
}
#ifdef DEBUG
static void page_table_dump(struct ipu6_mmu_info *mmu_info)
{
u32 l1_idx;
dev_dbg(mmu_info->dev, "begin IOMMU page table dump\n");
for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) {
u32 l2_idx;
u32 iova = (phys_addr_t)l1_idx << ISP_L1PT_SHIFT;
phys_addr_t l2_phys;
if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval)
continue;
l2_phys = TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]);
dev_dbg(mmu_info->dev,
"l1 entry %u; iovas 0x%8.8x-0x%8.8x, at %pap\n",
l1_idx, iova, iova + ISP_PAGE_SIZE, &l2_phys);
for (l2_idx = 0; l2_idx < ISP_L2PT_PTES; l2_idx++) {
u32 *l2_pt = mmu_info->l2_pts[l1_idx];
u32 iova2 = iova + (l2_idx << ISP_L2PT_SHIFT);
if (l2_pt[l2_idx] == mmu_info->dummy_page_pteval)
continue;
dev_dbg(mmu_info->dev,
"\tl2 entry %u; iova 0x%8.8x, phys %pa\n",
l2_idx, iova2,
TBL_PHYS_ADDR(l2_pt[l2_idx]));
}
}
dev_dbg(mmu_info->dev, "end IOMMU page table dump\n");
}
#endif /* DEBUG */
static dma_addr_t map_single(struct ipu6_mmu_info *mmu_info, void *ptr)
{
dma_addr_t dma;
dma = dma_map_single(mmu_info->dev, ptr, PAGE_SIZE, DMA_BIDIRECTIONAL);
if (dma_mapping_error(mmu_info->dev, dma))
return 0;
return dma;
}
static int get_dummy_page(struct ipu6_mmu_info *mmu_info)
{
void *pt = (void *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
dma_addr_t dma;
if (!pt)
return -ENOMEM;
dev_dbg(mmu_info->dev, "dummy_page: get_zeroed_page() == %p\n", pt);
dma = map_single(mmu_info, pt);
if (!dma) {
dev_err(mmu_info->dev, "Failed to map dummy page\n");
goto err_free_page;
}
mmu_info->dummy_page = pt;
mmu_info->dummy_page_pteval = dma >> ISP_PAGE_SHIFT;
return 0;
err_free_page:
free_page((unsigned long)pt);
return -ENOMEM;
}
static void free_dummy_page(struct ipu6_mmu_info *mmu_info)
{
dma_unmap_single(mmu_info->dev,
TBL_PHYS_ADDR(mmu_info->dummy_page_pteval),
PAGE_SIZE, DMA_BIDIRECTIONAL);
free_page((unsigned long)mmu_info->dummy_page);
}
static int alloc_dummy_l2_pt(struct ipu6_mmu_info *mmu_info)
{
u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
dma_addr_t dma;
unsigned int i;
if (!pt)
return -ENOMEM;
dev_dbg(mmu_info->dev, "dummy_l2: get_zeroed_page() = %p\n", pt);
dma = map_single(mmu_info, pt);
if (!dma) {
dev_err(mmu_info->dev, "Failed to map l2pt page\n");
goto err_free_page;
}
for (i = 0; i < ISP_L2PT_PTES; i++)
pt[i] = mmu_info->dummy_page_pteval;
mmu_info->dummy_l2_pt = pt;
mmu_info->dummy_l2_pteval = dma >> ISP_PAGE_SHIFT;
return 0;
err_free_page:
free_page((unsigned long)pt);
return -ENOMEM;
}
static void free_dummy_l2_pt(struct ipu6_mmu_info *mmu_info)
{
dma_unmap_single(mmu_info->dev,
TBL_PHYS_ADDR(mmu_info->dummy_l2_pteval),
PAGE_SIZE, DMA_BIDIRECTIONAL);
free_page((unsigned long)mmu_info->dummy_l2_pt);
}
static u32 *alloc_l1_pt(struct ipu6_mmu_info *mmu_info)
{
u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
dma_addr_t dma;
unsigned int i;
if (!pt)
return NULL;
dev_dbg(mmu_info->dev, "alloc_l1: get_zeroed_page() = %p\n", pt);
for (i = 0; i < ISP_L1PT_PTES; i++)
pt[i] = mmu_info->dummy_l2_pteval;
dma = map_single(mmu_info, pt);
if (!dma) {
dev_err(mmu_info->dev, "Failed to map l1pt page\n");
goto err_free_page;
}
mmu_info->l1_pt_dma = dma >> ISP_PADDR_SHIFT;
dev_dbg(mmu_info->dev, "l1 pt %p mapped at %pad\n", pt, &dma);
return pt;
err_free_page:
free_page((unsigned long)pt);
return NULL;
}
static u32 *alloc_l2_pt(struct ipu6_mmu_info *mmu_info)
{
u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
unsigned int i;
if (!pt)
return NULL;
dev_dbg(mmu_info->dev, "alloc_l2: get_zeroed_page() = %p\n", pt);
for (i = 0; i < ISP_L2PT_PTES; i++)
pt[i] = mmu_info->dummy_page_pteval;
return pt;
}
static void l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t dummy, size_t size)
{
unsigned int l2_entries;
unsigned int l2_idx;
unsigned long flags;
u32 l1_idx;
u32 *l2_pt;
spin_lock_irqsave(&mmu_info->lock, flags);
for (l1_idx = iova >> ISP_L1PT_SHIFT;
size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) {
dev_dbg(mmu_info->dev,
"unmapping l2 pgtable (l1 index %u (iova 0x%8.8lx))\n",
l1_idx, iova);
if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) {
dev_err(mmu_info->dev,
"unmap not mapped iova 0x%8.8lx l1 index %u\n",
iova, l1_idx);
continue;
}
l2_pt = mmu_info->l2_pts[l1_idx];
l2_entries = 0;
for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
size > 0 && l2_idx < ISP_L2PT_PTES; l2_idx++) {
phys_addr_t pteval = TBL_PHYS_ADDR(l2_pt[l2_idx]);
dev_dbg(mmu_info->dev,
"unmap l2 index %u with pteval 0x%p\n",
l2_idx, &pteval);
l2_pt[l2_idx] = mmu_info->dummy_page_pteval;
iova += ISP_PAGE_SIZE;
size -= ISP_PAGE_SIZE;
l2_entries++;
}
WARN_ON_ONCE(!l2_entries);
clflush_cache_range(&l2_pt[l2_idx - l2_entries],
sizeof(l2_pt[0]) * l2_entries);
}
WARN_ON_ONCE(size);
spin_unlock_irqrestore(&mmu_info->lock, flags);
}
static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t paddr, size_t size)
{
struct device *dev = mmu_info->dev;
unsigned int l2_entries;
u32 *l2_pt, *l2_virt;
unsigned int l2_idx;
unsigned long flags;
size_t mapped = 0;
dma_addr_t dma;
u32 l1_entry;
u32 l1_idx;
int err = 0;
spin_lock_irqsave(&mmu_info->lock, flags);
paddr = ALIGN(paddr, ISP_PAGE_SIZE);
for (l1_idx = iova >> ISP_L1PT_SHIFT;
size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) {
dev_dbg(dev,
"mapping l2 page table for l1 index %u (iova %8.8x)\n",
l1_idx, (u32)iova);
l1_entry = mmu_info->l1_pt[l1_idx];
if (l1_entry == mmu_info->dummy_l2_pteval) {
l2_virt = mmu_info->l2_pts[l1_idx];
if (likely(!l2_virt)) {
l2_virt = alloc_l2_pt(mmu_info);
if (!l2_virt) {
err = -ENOMEM;
goto error;
}
}
dma = map_single(mmu_info, l2_virt);
if (!dma) {
dev_err(dev, "Failed to map l2pt page\n");
free_page((unsigned long)l2_virt);
err = -EINVAL;
goto error;
}
l1_entry = dma >> ISP_PADDR_SHIFT;
dev_dbg(dev, "page for l1_idx %u %p allocated\n",
l1_idx, l2_virt);
mmu_info->l1_pt[l1_idx] = l1_entry;
mmu_info->l2_pts[l1_idx] = l2_virt;
clflush_cache_range(&mmu_info->l1_pt[l1_idx],
sizeof(mmu_info->l1_pt[l1_idx]));
}
l2_pt = mmu_info->l2_pts[l1_idx];
l2_entries = 0;
for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
size > 0 && l2_idx < ISP_L2PT_PTES; l2_idx++) {
l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT;
dev_dbg(dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx,
l2_pt[l2_idx]);
iova += ISP_PAGE_SIZE;
paddr += ISP_PAGE_SIZE;
mapped += ISP_PAGE_SIZE;
size -= ISP_PAGE_SIZE;
l2_entries++;
}
WARN_ON_ONCE(!l2_entries);
clflush_cache_range(&l2_pt[l2_idx - l2_entries],
sizeof(l2_pt[0]) * l2_entries);
}
spin_unlock_irqrestore(&mmu_info->lock, flags);
return 0;
error:
spin_unlock_irqrestore(&mmu_info->lock, flags);
/* unroll mapping in case something went wrong */
if (mapped)
l2_unmap(mmu_info, iova - mapped, paddr - mapped, mapped);
return err;
}
static int __ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t paddr, size_t size)
{
u32 iova_start = round_down(iova, ISP_PAGE_SIZE);
u32 iova_end = ALIGN(iova + size, ISP_PAGE_SIZE);
dev_dbg(mmu_info->dev,
"mapping iova 0x%8.8x--0x%8.8x, size %zu at paddr %pap\n",
iova_start, iova_end, size, &paddr);
return l2_map(mmu_info, iova_start, paddr, size);
}
static void __ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info,
unsigned long iova, size_t size)
{
l2_unmap(mmu_info, iova, 0, size);
}
static int allocate_trash_buffer(struct ipu6_mmu *mmu)
{
unsigned int n_pages = PFN_UP(IPU6_MMUV2_TRASH_RANGE);
struct iova *iova;
unsigned int i;
dma_addr_t dma;
unsigned long iova_addr;
int ret;
/* Allocate 8MB in iova range */
iova = alloc_iova(&mmu->dmap->iovad, n_pages,
PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0);
if (!iova) {
dev_err(mmu->dev, "cannot allocate iova range for trash\n");
return -ENOMEM;
}
dma = dma_map_page(mmu->dmap->mmu_info->dev, mmu->trash_page, 0,
PAGE_SIZE, DMA_BIDIRECTIONAL);
if (dma_mapping_error(mmu->dmap->mmu_info->dev, dma)) {
dev_err(mmu->dmap->mmu_info->dev, "Failed to map trash page\n");
ret = -ENOMEM;
goto out_free_iova;
}
mmu->pci_trash_page = dma;
/*
* Map the 8MB iova address range to the same physical trash page
* mmu->trash_page which is already reserved at the probe
*/
iova_addr = iova->pfn_lo;
for (i = 0; i < n_pages; i++) {
ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr),
mmu->pci_trash_page, PAGE_SIZE);
if (ret) {
dev_err(mmu->dev,
"mapping trash buffer range failed\n");
goto out_unmap;
}
iova_addr++;
}
mmu->iova_trash_page = PFN_PHYS(iova->pfn_lo);
dev_dbg(mmu->dev, "iova trash buffer for MMUID: %d is %u\n",
mmu->mmid, (unsigned int)mmu->iova_trash_page);
return 0;
out_unmap:
ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo),
PFN_PHYS(iova_size(iova)));
dma_unmap_page(mmu->dmap->mmu_info->dev, mmu->pci_trash_page,
PAGE_SIZE, DMA_BIDIRECTIONAL);
out_free_iova:
__free_iova(&mmu->dmap->iovad, iova);
return ret;
}
int ipu6_mmu_hw_init(struct ipu6_mmu *mmu)
{
struct ipu6_mmu_info *mmu_info;
unsigned long flags;
unsigned int i;
mmu_info = mmu->dmap->mmu_info;
/* Initialise the each MMU HW block */
for (i = 0; i < mmu->nr_mmus; i++) {
struct ipu6_mmu_hw *mmu_hw = &mmu->mmu_hw[i];
unsigned int j;
u16 block_addr;
/* Write page table address per MMU */
writel((phys_addr_t)mmu_info->l1_pt_dma,
mmu->mmu_hw[i].base + REG_L1_PHYS);
/* Set info bits per MMU */
writel(mmu->mmu_hw[i].info_bits,
mmu->mmu_hw[i].base + REG_INFO);
/* Configure MMU TLB stream configuration for L1 */
for (j = 0, block_addr = 0; j < mmu_hw->nr_l1streams;
block_addr += mmu->mmu_hw[i].l1_block_sz[j], j++) {
if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) {
dev_err(mmu->dev, "invalid L1 configuration\n");
return -EINVAL;
}
/* Write block start address for each streams */
writel(block_addr, mmu_hw->base +
mmu_hw->l1_stream_id_reg_offset + 4 * j);
}
/* Configure MMU TLB stream configuration for L2 */
for (j = 0, block_addr = 0; j < mmu_hw->nr_l2streams;
block_addr += mmu->mmu_hw[i].l2_block_sz[j], j++) {
if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) {
dev_err(mmu->dev, "invalid L2 configuration\n");
return -EINVAL;
}
writel(block_addr, mmu_hw->base +
mmu_hw->l2_stream_id_reg_offset + 4 * j);
}
}
if (!mmu->trash_page) {
int ret;
mmu->trash_page = alloc_page(GFP_KERNEL);
if (!mmu->trash_page) {
dev_err(mmu->dev, "insufficient memory for trash buffer\n");
return -ENOMEM;
}
ret = allocate_trash_buffer(mmu);
if (ret) {
__free_page(mmu->trash_page);
mmu->trash_page = NULL;
dev_err(mmu->dev, "trash buffer allocation failed\n");
return ret;
}
}
spin_lock_irqsave(&mmu->ready_lock, flags);
mmu->ready = true;
spin_unlock_irqrestore(&mmu->ready_lock, flags);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, "INTEL_IPU6");
static struct ipu6_mmu_info *ipu6_mmu_alloc(struct ipu6_device *isp)
{
struct ipu6_mmu_info *mmu_info;
int ret;
mmu_info = kzalloc_obj(*mmu_info);
if (!mmu_info)
return NULL;
mmu_info->aperture_start = 0;
mmu_info->aperture_end =
(dma_addr_t)DMA_BIT_MASK(isp->secure_mode ?
IPU6_MMU_ADDR_BITS :
IPU6_MMU_ADDR_BITS_NON_SECURE);
mmu_info->pgsize_bitmap = SZ_4K;
mmu_info->dev = &isp->pdev->dev;
ret = get_dummy_page(mmu_info);
if (ret)
goto err_free_info;
ret = alloc_dummy_l2_pt(mmu_info);
if (ret)
goto err_free_dummy_page;
mmu_info->l2_pts = vzalloc(ISP_L2PT_PTES * sizeof(*mmu_info->l2_pts));
if (!mmu_info->l2_pts)
goto err_free_dummy_l2_pt;
/*
* We always map the L1 page table (a single page as well as
* the L2 page tables).
*/
mmu_info->l1_pt = alloc_l1_pt(mmu_info);
if (!mmu_info->l1_pt)
goto err_free_l2_pts;
spin_lock_init(&mmu_info->lock);
dev_dbg(mmu_info->dev, "domain initialised\n");
return mmu_info;
err_free_l2_pts:
vfree(mmu_info->l2_pts);
err_free_dummy_l2_pt:
free_dummy_l2_pt(mmu_info);
err_free_dummy_page:
free_dummy_page(mmu_info);
err_free_info:
kfree(mmu_info);
return NULL;
}
void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu)
{
unsigned long flags;
spin_lock_irqsave(&mmu->ready_lock, flags);
mmu->ready = false;
spin_unlock_irqrestore(&mmu->ready_lock, flags);
}
EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, "INTEL_IPU6");
static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp)
{
struct ipu6_dma_mapping *dmap;
dmap = kzalloc_obj(*dmap);
if (!dmap)
return NULL;
dmap->mmu_info = ipu6_mmu_alloc(isp);
if (!dmap->mmu_info) {
kfree(dmap);
return NULL;
}
init_iova_domain(&dmap->iovad, SZ_4K, 1);
dmap->mmu_info->dmap = dmap;
dev_dbg(&isp->pdev->dev, "alloc mapping\n");
iova_cache_get();
return dmap;
}
phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info,
dma_addr_t iova)
{
phys_addr_t phy_addr;
unsigned long flags;
u32 *l2_pt;
spin_lock_irqsave(&mmu_info->lock, flags);
l2_pt = mmu_info->l2_pts[iova >> ISP_L1PT_SHIFT];
phy_addr = (phys_addr_t)l2_pt[(iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT];
phy_addr <<= ISP_PAGE_SHIFT;
spin_unlock_irqrestore(&mmu_info->lock, flags);
return phy_addr;
}
void ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
size_t size)
{
unsigned int min_pagesz;
dev_dbg(mmu_info->dev, "unmapping iova 0x%lx size 0x%zx\n", iova, size);
/* find out the minimum page size supported */
min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap);
/*
* The virtual address and the size of the mapping must be
* aligned (at least) to the size of the smallest page supported
* by the hardware
*/
if (!IS_ALIGNED(iova | size, min_pagesz)) {
dev_err(NULL, "unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n",
iova, size, min_pagesz);
return;
}
__ipu6_mmu_unmap(mmu_info, iova, size);
}
int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t paddr, size_t size)
{
unsigned int min_pagesz;
if (mmu_info->pgsize_bitmap == 0UL)
return -ENODEV;
/* find out the minimum page size supported */
min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap);
/*
* both the virtual address and the physical one, as well as
* the size of the mapping, must be aligned (at least) to the
* size of the smallest page supported by the hardware
*/
if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) {
dev_err(mmu_info->dev,
"unaligned: iova %lx pa %pa size %zx min_pagesz %x\n",
iova, &paddr, size, min_pagesz);
return -EINVAL;
}
dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n",
iova, &paddr, size);
return __ipu6_mmu_map(mmu_info, iova, paddr, size);
}
static void ipu6_mmu_destroy(struct ipu6_mmu *mmu)
{
struct ipu6_dma_mapping *dmap = mmu->dmap;
struct ipu6_mmu_info *mmu_info = dmap->mmu_info;
struct iova *iova;
u32 l1_idx;
if (mmu->iova_trash_page) {
iova = find_iova(&dmap->iovad, PHYS_PFN(mmu->iova_trash_page));
if (iova) {
/* unmap and free the trash buffer iova */
ipu6_mmu_unmap(mmu_info, PFN_PHYS(iova->pfn_lo),
PFN_PHYS(iova_size(iova)));
__free_iova(&dmap->iovad, iova);
} else {
dev_err(mmu->dev, "trash buffer iova not found.\n");
}
mmu->iova_trash_page = 0;
dma_unmap_page(mmu_info->dev, mmu->pci_trash_page,
PAGE_SIZE, DMA_BIDIRECTIONAL);
mmu->pci_trash_page = 0;
__free_page(mmu->trash_page);
}
for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) {
if (mmu_info->l1_pt[l1_idx] != mmu_info->dummy_l2_pteval) {
dma_unmap_single(mmu_info->dev,
TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]),
PAGE_SIZE, DMA_BIDIRECTIONAL);
free_page((unsigned long)mmu_info->l2_pts[l1_idx]);
}
}
vfree(mmu_info->l2_pts);
free_dummy_page(mmu_info);
dma_unmap_single(mmu_info->dev, TBL_PHYS_ADDR(mmu_info->l1_pt_dma),
PAGE_SIZE, DMA_BIDIRECTIONAL);
free_page((unsigned long)mmu_info->dummy_l2_pt);
free_page((unsigned long)mmu_info->l1_pt);
kfree(mmu_info);
}
struct ipu6_mmu *ipu6_mmu_init(struct device *dev,
void __iomem *base, int mmid,
const struct ipu6_hw_variants *hw)
{
struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev));
struct ipu6_mmu_pdata *pdata;
struct ipu6_mmu *mmu;
unsigned int i;
if (hw->nr_mmus > IPU6_MMU_MAX_DEVICES)
return ERR_PTR(-EINVAL);
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
for (i = 0; i < hw->nr_mmus; i++) {
struct ipu6_mmu_hw *pdata_mmu = &pdata->mmu_hw[i];
const struct ipu6_mmu_hw *src_mmu = &hw->mmu_hw[i];
if (src_mmu->nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS ||
src_mmu->nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS)
return ERR_PTR(-EINVAL);
*pdata_mmu = *src_mmu;
pdata_mmu->base = base + src_mmu->offset;
}
mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL);
if (!mmu)
return ERR_PTR(-ENOMEM);
mmu->mmid = mmid;
mmu->mmu_hw = pdata->mmu_hw;
mmu->nr_mmus = hw->nr_mmus;
mmu->tlb_invalidate = tlb_invalidate;
mmu->ready = false;
INIT_LIST_HEAD(&mmu->vma_list);
spin_lock_init(&mmu->ready_lock);
mmu->dmap = alloc_dma_mapping(isp);
if (!mmu->dmap) {
dev_err(dev, "can't alloc dma mapping\n");
return ERR_PTR(-ENOMEM);
}
return mmu;
}
void ipu6_mmu_cleanup(struct ipu6_mmu *mmu)
{
struct ipu6_dma_mapping *dmap = mmu->dmap;
ipu6_mmu_destroy(mmu);
mmu->dmap = NULL;
iova_cache_put();
put_iova_domain(&dmap->iovad);
kfree(dmap);
}
@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_MMU_H
#define IPU6_MMU_H
#define ISYS_MMID 1
#define PSYS_MMID 0
#include <linux/list.h>
#include <linux/spinlock_types.h>
#include <linux/types.h>
struct device;
struct page;
struct ipu6_hw_variants;
struct ipu6_mmu_info {
struct device *dev;
u32 *l1_pt;
u32 l1_pt_dma;
u32 **l2_pts;
u32 *dummy_l2_pt;
u32 dummy_l2_pteval;
void *dummy_page;
u32 dummy_page_pteval;
dma_addr_t aperture_start;
dma_addr_t aperture_end;
unsigned long pgsize_bitmap;
spinlock_t lock; /* Serialize access to users */
struct ipu6_dma_mapping *dmap;
};
struct ipu6_mmu {
struct list_head node;
struct ipu6_mmu_hw *mmu_hw;
unsigned int nr_mmus;
unsigned int mmid;
phys_addr_t pgtbl;
struct device *dev;
struct ipu6_dma_mapping *dmap;
struct list_head vma_list;
struct page *trash_page;
dma_addr_t pci_trash_page; /* IOVA from PCI DMA services (parent) */
dma_addr_t iova_trash_page; /* IOVA for IPU6 child nodes to use */
bool ready;
spinlock_t ready_lock; /* Serialize access to bool ready */
void (*tlb_invalidate)(struct ipu6_mmu *mmu);
};
struct ipu6_mmu *ipu6_mmu_init(struct device *dev,
void __iomem *base, int mmid,
const struct ipu6_hw_variants *hw);
void ipu6_mmu_cleanup(struct ipu6_mmu *mmu);
int ipu6_mmu_hw_init(struct ipu6_mmu *mmu);
void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu);
int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t paddr, size_t size);
void ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
size_t size);
phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info,
dma_addr_t iova);
#endif
@@ -0,0 +1,224 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2023--2024 Intel Corporation */
#ifndef IPU6_PLATFORM_BUTTRESS_REGS_H
#define IPU6_PLATFORM_BUTTRESS_REGS_H
#include <linux/bits.h>
/* IS_WORKPOINT_REQ */
#define IPU6_BUTTRESS_REG_IS_FREQ_CTL 0x34
/* PS_WORKPOINT_REQ */
#define IPU6_BUTTRESS_REG_PS_FREQ_CTL 0x38
/* should be tuned for real silicon */
#define IPU6_IS_FREQ_CTL_DEFAULT_RATIO 0x08
#define IPU6SE_IS_FREQ_CTL_DEFAULT_RATIO 0x0a
#define IPU6_PS_FREQ_CTL_DEFAULT_RATIO 0x0d
#define IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x10
#define IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x0708
#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT 3
#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK GENMASK(4, 3)
#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT 6
#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK GENMASK(7, 6)
#define IPU6_BUTTRESS_PWR_STATE_DN_DONE 0x0
#define IPU6_BUTTRESS_PWR_STATE_UP_PROCESS 0x1
#define IPU6_BUTTRESS_PWR_STATE_DN_PROCESS 0x2
#define IPU6_BUTTRESS_PWR_STATE_UP_DONE 0x3
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_0 0x270
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_1 0x274
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_2 0x278
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_3 0x27c
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_4 0x280
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_5 0x284
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_6 0x288
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_7 0x28c
#define BUTTRESS_REG_WDT 0x8
#define BUTTRESS_REG_BTRS_CTRL 0xc
#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0 BIT(0)
#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1 BIT(1)
#define BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND GENMASK(9, 8)
#define BUTTRESS_REG_FW_RESET_CTL 0x30
#define BUTTRESS_FW_RESET_CTL_START BIT(0)
#define BUTTRESS_FW_RESET_CTL_DONE BIT(1)
#define BUTTRESS_REG_IS_FREQ_CTL 0x34
#define BUTTRESS_REG_PS_FREQ_CTL 0x38
#define BUTTRESS_FREQ_CTL_START BIT(31)
#define BUTTRESS_FREQ_CTL_ICCMAX_LEVEL GENMASK(19, 16)
#define BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK GENMASK(15, 8)
#define BUTTRESS_FREQ_CTL_RATIO_MASK GENMASK(7, 0)
#define BUTTRESS_REG_PWR_STATE 0x5c
#define BUTTRESS_PWR_STATE_RESET 0x0
#define BUTTRESS_PWR_STATE_PWR_ON_DONE 0x1
#define BUTTRESS_PWR_STATE_PWR_RDY 0x3
#define BUTTRESS_PWR_STATE_PWR_IDLE 0x4
#define BUTTRESS_PWR_STATE_HH_STATUS_MASK GENMASK(12, 11)
enum {
BUTTRESS_PWR_STATE_HH_STATE_IDLE,
BUTTRESS_PWR_STATE_HH_STATE_IN_PRGS,
BUTTRESS_PWR_STATE_HH_STATE_DONE,
BUTTRESS_PWR_STATE_HH_STATE_ERR,
};
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_MASK GENMASK(23, 19)
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IDLE 0x0
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PLL_CMP 0x1
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK 0x2
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PG_ACK 0x3
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_ASSRT_CYCLES 0x4
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES1 0x5
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES2 0x6
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DEASSRT_CYCLES 0x7
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_FUSE_WR_CMP 0x8
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_BRK_POINT 0x9
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IS_RDY 0xa
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_HALT_HALTED 0xb
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DURATION_CNT3 0xc
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK_PD 0xd
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_PD_BRK_POINT 0xe
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PD_PG_ACK0 0xf
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_MASK GENMASK(28, 24)
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_IDLE 0x0
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_IP_RDY 0x1
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_PRE_CNT_EXH 0x2
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_VGI_PWRGOOD 0x3
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_POST_CNT_EXH 0x4
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WR_PLL_RATIO 0x5
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_CMP 0x6
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_CLKACK 0x7
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_ASSRT_CYCLES 0x8
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES1 0x9
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES2 0xa
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_DEASSRT_CYCLES 0xb
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PU_BRK_PNT 0xc
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_FUSE_ACCPT 0xd
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PS_PWR_UP 0xf
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_4_HALTED 0x10
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RESET_CNT3 0x11
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_CLKACK 0x12
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_OFF_IND 0x13
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PH4 0x14
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PLL_CMP 0x15
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_CLKACK 0x16
#define BUTTRESS_REG_SECURITY_CTL 0x300
#define BUTTRESS_REG_SKU 0x314
#define BUTTRESS_REG_SECURITY_TOUCH 0x318
#define BUTTRESS_REG_CAMERA_MASK 0x84
#define BUTTRESS_SECURITY_CTL_FW_SECURE_MODE BIT(16)
#define BUTTRESS_SECURITY_CTL_FW_SETUP_MASK GENMASK(4, 0)
#define BUTTRESS_SECURITY_CTL_FW_SETUP_DONE BIT(0)
#define BUTTRESS_SECURITY_CTL_AUTH_DONE BIT(1)
#define BUTTRESS_SECURITY_CTL_AUTH_FAILED BIT(3)
#define BUTTRESS_REG_FW_SOURCE_BASE_LO 0x78
#define BUTTRESS_REG_FW_SOURCE_BASE_HI 0x7C
#define BUTTRESS_REG_FW_SOURCE_SIZE 0x80
#define BUTTRESS_REG_ISR_STATUS 0x90
#define BUTTRESS_REG_ISR_ENABLED_STATUS 0x94
#define BUTTRESS_REG_ISR_ENABLE 0x98
#define BUTTRESS_REG_ISR_CLEAR 0x9C
#define BUTTRESS_ISR_IS_IRQ BIT(0)
#define BUTTRESS_ISR_PS_IRQ BIT(1)
#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE BIT(2)
#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH BIT(3)
#define BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING BIT(4)
#define BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING BIT(5)
#define BUTTRESS_ISR_CSE_CSR_SET BIT(6)
#define BUTTRESS_ISR_ISH_CSR_SET BIT(7)
#define BUTTRESS_ISR_SPURIOUS_CMP BIT(8)
#define BUTTRESS_ISR_WATCHDOG_EXPIRED BIT(9)
#define BUTTRESS_ISR_PUNIT_2_IUNIT_IRQ BIT(10)
#define BUTTRESS_ISR_SAI_VIOLATION BIT(11)
#define BUTTRESS_ISR_HW_ASSERTION BIT(12)
#define BUTTRESS_ISR_IS_CORRECTABLE_MEM_ERR BIT(13)
#define BUTTRESS_ISR_IS_FATAL_MEM_ERR BIT(14)
#define BUTTRESS_ISR_IS_NON_FATAL_MEM_ERR BIT(15)
#define BUTTRESS_ISR_PS_CORRECTABLE_MEM_ERR BIT(16)
#define BUTTRESS_ISR_PS_FATAL_MEM_ERR BIT(17)
#define BUTTRESS_ISR_PS_NON_FATAL_MEM_ERR BIT(18)
#define BUTTRESS_ISR_PS_FAST_THROTTLE BIT(19)
#define BUTTRESS_ISR_UFI_ERROR BIT(20)
#define BUTTRESS_REG_IU2CSEDB0 0x100
#define BUTTRESS_IU2CSEDB0_BUSY BIT(31)
#define BUTTRESS_IU2CSEDB0_IPC_CLIENT_ID_VAL 2
#define BUTTRESS_REG_IU2CSEDATA0 0x104
#define BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD 1
#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN 2
#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_REPLACE 3
#define BUTTRESS_IU2CSEDATA0_IPC_UPDATE_SECURE_TOUCH 16
#define BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE BIT(0)
#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE BIT(1)
#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_REPLACE_DONE BIT(2)
#define BUTTRESS_CSE2IUDATA0_IPC_UPDATE_SECURE_TOUCH_DONE BIT(4)
#define BUTTRESS_REG_IU2CSECSR 0x108
#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 BIT(0)
#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 BIT(1)
#define BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE BIT(2)
#define BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ BIT(3)
#define BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID BIT(4)
#define BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ BIT(5)
#define BUTTRESS_REG_CSE2IUDB0 0x304
#define BUTTRESS_REG_CSE2IUCSR 0x30C
#define BUTTRESS_REG_CSE2IUDATA0 0x308
/* 0x20 == NACK, 0xf == unknown command */
#define BUTTRESS_CSE2IUDATA0_IPC_NACK 0xf20
#define BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK GENMASK(15, 0)
#define BUTTRESS_REG_ISH2IUCSR 0x50
#define BUTTRESS_REG_ISH2IUDB0 0x54
#define BUTTRESS_REG_ISH2IUDATA0 0x58
#define BUTTRESS_REG_IU2ISHDB0 0x10C
#define BUTTRESS_REG_IU2ISHDATA0 0x110
#define BUTTRESS_REG_IU2ISHDATA1 0x114
#define BUTTRESS_REG_IU2ISHCSR 0x118
#define BUTTRESS_REG_FABRIC_CMD 0x88
#define BUTTRESS_FABRIC_CMD_START_TSC_SYNC BIT(0)
#define BUTTRESS_FABRIC_CMD_IS_DRAIN BIT(4)
#define BUTTRESS_REG_TSW_CTL 0x120
#define BUTTRESS_TSW_CTL_SOFT_RESET BIT(8)
#define BUTTRESS_REG_TSC_LO 0x164
#define BUTTRESS_REG_TSC_HI 0x168
#define BUTTRESS_IRQS (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \
BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \
BUTTRESS_ISR_IS_IRQ | BUTTRESS_ISR_PS_IRQ)
#define BUTTRESS_EVENT (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \
BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \
BUTTRESS_ISR_SAI_VIOLATION)
#endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */
@@ -0,0 +1,172 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2023--2024 Intel Corporation */
#ifndef IPU6_PLATFORM_ISYS_CSI2_REG_H
#define IPU6_PLATFORM_ISYS_CSI2_REG_H
#include <linux/bits.h>
#define CSI_REG_BASE 0x220000
#define CSI_REG_PORT_BASE(id) (CSI_REG_BASE + (id) * 0x1000)
/* CSI Port Genral Purpose Registers */
#define CSI_REG_PORT_GPREG_SRST 0x0
#define CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST 0x4
#define CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL 0x8
/*
* Port IRQs mapping events:
* IRQ0 - CSI_FE event
* IRQ1 - CSI_SYNC
* IRQ2 - S2M_SIDS0TO7
* IRQ3 - S2M_SIDS8TO15
*/
#define CSI_PORT_REG_BASE_IRQ_CSI 0x80
#define CSI_PORT_REG_BASE_IRQ_CSI_SYNC 0xA0
#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS0TOS7 0xC0
#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS8TOS15 0xE0
#define CSI_PORT_REG_BASE_IRQ_EDGE_OFFSET 0x0
#define CSI_PORT_REG_BASE_IRQ_MASK_OFFSET 0x4
#define CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET 0x8
#define CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET 0xc
#define CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET 0x10
#define CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET 0x14
#define IPU6SE_CSI_RX_ERROR_IRQ_MASK GENMASK(18, 0)
#define IPU6_CSI_RX_ERROR_IRQ_MASK GENMASK(19, 0)
#define CSI_RX_NUM_ERRORS_IN_IRQ 20
#define CSI_RX_NUM_IRQ 32
#define IPU_CSI_RX_IRQ_FS_VC(chn) (1 << ((chn) * 2))
#define IPU_CSI_RX_IRQ_FE_VC(chn) (2 << ((chn) * 2))
/* PPI2CSI */
#define CSI_REG_PPI2CSI_ENABLE 0x200
#define CSI_REG_PPI2CSI_CONFIG_PPI_INTF 0x204
#define PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK GENMASK(4, 3)
#define CSI_REG_PPI2CSI_CONFIG_CSI_FEATURE 0x208
enum CSI_PPI2CSI_CTRL {
CSI_PPI2CSI_DISABLE = 0,
CSI_PPI2CSI_ENABLE = 1,
};
/* CSI_FE */
#define CSI_REG_CSI_FE_ENABLE 0x280
#define CSI_REG_CSI_FE_MODE 0x284
#define CSI_REG_CSI_FE_MUX_CTRL 0x288
#define CSI_REG_CSI_FE_SYNC_CNTR_SEL 0x290
enum CSI_FE_ENABLE_TYPE {
CSI_FE_DISABLE = 0,
CSI_FE_ENABLE = 1,
};
enum CSI_FE_MODE_TYPE {
CSI_FE_DPHY_MODE = 0,
CSI_FE_CPHY_MODE = 1,
};
enum CSI_FE_INPUT_SELECTOR {
CSI_SENSOR_INPUT = 0,
CSI_MIPIGEN_INPUT = 1,
};
enum CSI_FE_SYNC_CNTR_SEL_TYPE {
CSI_CNTR_SENSOR_LINE_ID = BIT(0),
CSI_CNTR_INT_LINE_PKT_ID = ~CSI_CNTR_SENSOR_LINE_ID,
CSI_CNTR_SENSOR_FRAME_ID = BIT(1),
CSI_CNTR_INT_FRAME_PKT_ID = ~CSI_CNTR_SENSOR_FRAME_ID,
};
/* CSI HUB General Purpose Registers */
#define CSI_REG_HUB_GPREG_SRST (CSI_REG_BASE + 0x18000)
#define CSI_REG_HUB_GPREG_SLV_REG_SRST (CSI_REG_BASE + 0x18004)
#define CSI_REG_HUB_DRV_ACCESS_PORT(id) (CSI_REG_BASE + 0x18018 + (id) * 4)
#define CSI_REG_HUB_FW_ACCESS_PORT_OFS 0x17000
#define CSI_REG_HUB_FW_ACCESS_PORT_V6OFS 0x16000
#define CSI_REG_HUB_FW_ACCESS_PORT(ofs, id) \
(CSI_REG_BASE + (ofs) + (id) * 4)
enum CSI_PORT_CLK_GATING_SWITCH {
CSI_PORT_CLK_GATING_OFF = 0,
CSI_PORT_CLK_GATING_ON = 1,
};
#define CSI_REG_BASE_HUB_IRQ 0x18200
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238200
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238204
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238208
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23820c
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238210
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238214
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238220
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238224
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238228
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23822c
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238230
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238234
/* MTL IPU6V6 irq ctrl0 & ctrl1 */
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238700
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238704
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238708
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23870c
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238710
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238714
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238720
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238724
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238728
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23872c
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238730
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238734
/*
* 3:0 CSI_PORT.irq_out[3:0] CSI_PORT_CTRL0 IRQ outputs (4bits)
* [0] CSI_PORT.IRQ_CTRL0_csi
* [1] CSI_PORT.IRQ_CTRL1_csi_sync
* [2] CSI_PORT.IRQ_CTRL2_s2m_sids0to7
* [3] CSI_PORT.IRQ_CTRL3_s2m_sids8to15
*/
#define IPU6_ISYS_UNISPART_IRQ_CSI2(port) \
(0x3 << ((port) * IPU6_CSI_IRQ_NUM_PER_PIPE))
/*
* ipu6se support 2 front ends, 2 port per front end, 4 ports 0..3
* sip0 - 0, 1
* sip1 - 2, 3
* 0 and 2 support 4 data lanes, 1 and 3 support 2 data lanes
* all offset are base from isys base address
*/
#define CSI2_HUB_GPREG_SIP_SRST(sip) (0x238038 + (sip) * 4)
#define CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip) (0x238050 + (sip) * 4)
#define CSI2_HUB_GPREG_DPHY_TIMER_INCR 0x238040
#define CSI2_HUB_GPREG_HPLL_FREQ 0x238044
#define CSI2_HUB_GPREG_IS_CLK_RATIO 0x238048
#define CSI2_HUB_GPREG_HPLL_FREQ_ISCLK_RATE_OVERRIDE 0x23804c
#define CSI2_HUB_GPREG_PORT_CLKGATING_DISABLE 0x238058
#define CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL 0x23805c
#define CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL 0x238088
#define CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL 0x2380a4
#define CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL 0x2380d0
#define CSI2_SIP_TOP_CSI_RX_BASE(sip) (0x23805c + (sip) * 0x48)
#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port) (0x23805c + ((port) / 2) * 0x48)
#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) (0x238088 + ((port) / 2) * 0x48)
/* offset from port base */
#define CSI2_SIP_TOP_CSI_RX_PORT_CONTROL 0x0
#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE 0x4
#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE 0x8
#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(lane) (0xc + (lane) * 8)
#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(lane) (0x10 + (lane) * 8)
#endif /* IPU6_ISYS_CSI2_REG_H */
@@ -0,0 +1,179 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2018 - 2024 Intel Corporation */
#ifndef IPU6_PLATFORM_REGS_H
#define IPU6_PLATFORM_REGS_H
#include <linux/bits.h>
/*
* IPU6 uses uniform address within IPU6, therefore all subsystem registers
* locates in one single space starts from 0 but in different sctions with
* different addresses, the subsystem offsets are defined to 0 as the
* register definition will have the address offset to 0.
*/
#define IPU6_UNIFIED_OFFSET 0
#define IPU6_ISYS_IOMMU0_OFFSET 0x2e0000
#define IPU6_ISYS_IOMMU1_OFFSET 0x2e0500
#define IPU6_ISYS_IOMMUI_OFFSET 0x2e0a00
#define IPU6_PSYS_IOMMU0_OFFSET 0x1b0000
#define IPU6_PSYS_IOMMU1_OFFSET 0x1b0700
#define IPU6_PSYS_IOMMU1R_OFFSET 0x1b0e00
#define IPU6_PSYS_IOMMUI_OFFSET 0x1b1500
/* the offset from IOMMU base register */
#define IPU6_MMU_L1_STREAM_ID_REG_OFFSET 0x0c
#define IPU6_MMU_L2_STREAM_ID_REG_OFFSET 0x4c
#define IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET 0x8c
#define IPU6_MMU_INFO_OFFSET 0x8
#define IPU6_ISYS_SPC_OFFSET 0x210000
#define IPU6SE_PSYS_SPC_OFFSET 0x110000
#define IPU6_PSYS_SPC_OFFSET 0x118000
#define IPU6_ISYS_DMEM_OFFSET 0x200000
#define IPU6_PSYS_DMEM_OFFSET 0x100000
#define IPU6_REG_ISYS_UNISPART_IRQ_EDGE 0x27c000
#define IPU6_REG_ISYS_UNISPART_IRQ_MASK 0x27c004
#define IPU6_REG_ISYS_UNISPART_IRQ_STATUS 0x27c008
#define IPU6_REG_ISYS_UNISPART_IRQ_CLEAR 0x27c00c
#define IPU6_REG_ISYS_UNISPART_IRQ_ENABLE 0x27c010
#define IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE 0x27c014
#define IPU6_REG_ISYS_UNISPART_SW_IRQ_REG 0x27c414
#define IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG 0x27c418
#define IPU6_ISYS_UNISPART_IRQ_CSI0 BIT(2)
#define IPU6_ISYS_UNISPART_IRQ_CSI1 BIT(3)
#define IPU6_ISYS_UNISPART_IRQ_SW BIT(22)
#define IPU6_REG_ISYS_ISL_TOP_IRQ_EDGE 0x2b0200
#define IPU6_REG_ISYS_ISL_TOP_IRQ_MASK 0x2b0204
#define IPU6_REG_ISYS_ISL_TOP_IRQ_STATUS 0x2b0208
#define IPU6_REG_ISYS_ISL_TOP_IRQ_CLEAR 0x2b020c
#define IPU6_REG_ISYS_ISL_TOP_IRQ_ENABLE 0x2b0210
#define IPU6_REG_ISYS_ISL_TOP_IRQ_LEVEL_NOT_PULSE 0x2b0214
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_EDGE 0x2d2100
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_MASK 0x2d2104
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_STATUS 0x2d2108
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_CLEAR 0x2d210c
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_ENABLE 0x2d2110
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_LEVEL_NOT_PULSE 0x2d2114
/* CDC Burst collector thresholds for isys - 3 FIFOs i = 0..2 */
#define IPU6_REG_ISYS_CDC_THRESHOLD(i) (0x27c400 + ((i) * 4))
#define IPU6_CSI_IRQ_NUM_PER_PIPE 4
#define IPU6SE_ISYS_CSI_PORT_NUM 4
#define IPU6_ISYS_CSI_PORT_NUM 8
#define IPU6_ISYS_CSI_PORT_IRQ(irq_num) BIT(irq_num)
/* PKG DIR OFFSET in IMR in secure mode */
#define IPU6_PKG_DIR_IMR_OFFSET 0x40
#define IPU6_ISYS_REG_SPC_STATUS_CTRL 0x0
#define IPU6_ISYS_SPC_STATUS_START BIT(1)
#define IPU6_ISYS_SPC_STATUS_RUN BIT(3)
#define IPU6_ISYS_SPC_STATUS_READY BIT(5)
#define IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12)
#define IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH BIT(13)
#define IPU6_PSYS_REG_SPC_STATUS_CTRL 0x0
#define IPU6_PSYS_REG_SPC_START_PC 0x4
#define IPU6_PSYS_REG_SPC_ICACHE_BASE 0x10
#define IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER 0x14
#define IPU6_PSYS_SPC_STATUS_START BIT(1)
#define IPU6_PSYS_SPC_STATUS_RUN BIT(3)
#define IPU6_PSYS_SPC_STATUS_READY BIT(5)
#define IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12)
#define IPU6_PSYS_SPC_STATUS_ICACHE_PREFETCH BIT(13)
#define IPU6_PSYS_REG_SPP0_STATUS_CTRL 0x20000
#define IPU6_INFO_ENABLE_SNOOP BIT(0)
#define IPU6_INFO_DEC_FORCE_FLUSH BIT(1)
#define IPU6_INFO_DEC_PASS_THROUGH BIT(2)
#define IPU6_INFO_ZLW BIT(3)
#define IPU6_INFO_REQUEST_DESTINATION_IOSF BIT(9)
#define IPU6_INFO_IMR_BASE BIT(10)
#define IPU6_INFO_IMR_DESTINED BIT(11)
#define IPU6_INFO_REQUEST_DESTINATION_PRIMARY IPU6_INFO_REQUEST_DESTINATION_IOSF
/*
* s2m_pixel_soc_pixel_remapping is dedicated for the enabling of the
* pixel s2m remp ability.Remap here means that s2m rearange the order
* of the pixels in each 4 pixels group.
* For examle, mirroring remping means that if input's 4 first pixels
* are 1 2 3 4 then in output we should see 4 3 2 1 in this 4 first pixels.
* 0xE4 is from s2m MAS document. It means no remapping.
*/
#define S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4
/*
* csi_be_soc_pixel_remapping is for the enabling of the pixel remapping.
* This remapping is exactly like the stream2mmio remapping.
*/
#define CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4
#define IPU6_REG_DMA_TOP_AB_GROUP1_BASE_ADDR 0x1ae000
#define IPU6_REG_DMA_TOP_AB_GROUP2_BASE_ADDR 0x1af000
#define IPU6_REG_DMA_TOP_AB_RING_MIN_OFFSET(n) (0x4 + (n) * 0xc)
#define IPU6_REG_DMA_TOP_AB_RING_MAX_OFFSET(n) (0x8 + (n) * 0xc)
#define IPU6_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(n) (0xc + (n) * 0xc)
enum ipu6_device_ab_group1_target_id {
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R0_SPC_DMEM,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R1_SPC_DMEM,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R2_SPC_DMEM,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R3_SPC_STATUS_REG,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R4_SPC_MASTER_BASE_ADDR,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R5_SPC_PC_STALL,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R6_SPC_EQ,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R7_SPC_RESERVED,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R8_SPC_RESERVED,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R9_SPP0,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R10_SPP1,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R11_CENTRAL_R1,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R12_IRQ,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R13_CENTRAL_R2,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R14_DMA,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R15_DMA,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R16_GP,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R17_ZLW_INSERTER,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R18_AB,
};
enum nci_ab_access_mode {
NCI_AB_ACCESS_MODE_RW, /* read & write */
NCI_AB_ACCESS_MODE_RO, /* read only */
NCI_AB_ACCESS_MODE_WO, /* write only */
NCI_AB_ACCESS_MODE_NA, /* No access at all */
};
/* IRQ-related registers in PSYS */
#define IPU6_REG_PSYS_GPDEV_IRQ_EDGE 0x1aa200
#define IPU6_REG_PSYS_GPDEV_IRQ_MASK 0x1aa204
#define IPU6_REG_PSYS_GPDEV_IRQ_STATUS 0x1aa208
#define IPU6_REG_PSYS_GPDEV_IRQ_CLEAR 0x1aa20c
#define IPU6_REG_PSYS_GPDEV_IRQ_ENABLE 0x1aa210
#define IPU6_REG_PSYS_GPDEV_IRQ_LEVEL_NOT_PULSE 0x1aa214
/* There are 8 FW interrupts, n = 0..7 */
#define IPU6_PSYS_GPDEV_FWIRQ0 5
#define IPU6_PSYS_GPDEV_FWIRQ1 6
#define IPU6_PSYS_GPDEV_FWIRQ2 7
#define IPU6_PSYS_GPDEV_FWIRQ3 8
#define IPU6_PSYS_GPDEV_FWIRQ4 9
#define IPU6_PSYS_GPDEV_FWIRQ5 10
#define IPU6_PSYS_GPDEV_FWIRQ6 11
#define IPU6_PSYS_GPDEV_FWIRQ7 12
#define IPU6_PSYS_GPDEV_IRQ_FWIRQ(n) BIT(n)
#define IPU6_REG_PSYS_GPDEV_FWIRQ(n) (4 * (n) + 0x1aa100)
#endif /* IPU6_PLATFORM_REGS_H */
@@ -0,0 +1,848 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/pci-ats.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <media/ipu-bridge.h>
#include <media/ipu6-pci-table.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-buttress.h"
#include "ipu6-cpd.h"
#include "ipu6-isys.h"
#include "ipu6-mmu.h"
#include "ipu6-platform-buttress-regs.h"
#include "ipu6-platform-isys-csi2-reg.h"
#include "ipu6-platform-regs.h"
#define IPU6_PCI_BAR 0
struct ipu6_cell_program {
u32 magic_number;
u32 blob_offset;
u32 blob_size;
u32 start[3];
u32 icache_source;
u32 icache_target;
u32 icache_size;
u32 pmem_source;
u32 pmem_target;
u32 pmem_size;
u32 data_source;
u32 data_target;
u32 data_size;
u32 bss_target;
u32 bss_size;
u32 cell_id;
u32 regs_addr;
u32 cell_pmem_data_bus_address;
u32 cell_dmem_data_bus_address;
u32 cell_pmem_control_bus_address;
u32 cell_dmem_control_bus_address;
u32 next;
u32 dummy[2];
};
static struct ipu6_isys_internal_pdata isys_ipdata = {
.hw_variant = {
.offset = IPU6_UNIFIED_OFFSET,
.nr_mmus = 3,
.mmu_hw = {
{
.offset = IPU6_ISYS_IOMMU0_OFFSET,
.info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF,
.nr_l1streams = 16,
.l1_block_sz = {
3, 8, 2, 2, 2, 2, 2, 2, 1, 1,
1, 1, 1, 1, 1, 1
},
.nr_l2streams = 16,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_ISYS_IOMMU1_OFFSET,
.info_bits = 0,
.nr_l1streams = 16,
.l1_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 1, 1, 4
},
.nr_l2streams = 16,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_ISYS_IOMMUI_OFFSET,
.info_bits = 0,
.nr_l1streams = 0,
.nr_l2streams = 0,
.insert_read_before_invalidate = false,
},
},
.cdc_fifos = 3,
.cdc_fifo_threshold = {6, 8, 2},
.dmem_offset = IPU6_ISYS_DMEM_OFFSET,
.spc_offset = IPU6_ISYS_SPC_OFFSET,
},
.isys_dma_overshoot = IPU6_ISYS_OVERALLOC_MIN,
};
static struct ipu6_psys_internal_pdata psys_ipdata = {
.hw_variant = {
.offset = IPU6_UNIFIED_OFFSET,
.nr_mmus = 4,
.mmu_hw = {
{
.offset = IPU6_PSYS_IOMMU0_OFFSET,
.info_bits =
IPU6_INFO_REQUEST_DESTINATION_IOSF,
.nr_l1streams = 16,
.l1_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.nr_l2streams = 16,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_PSYS_IOMMU1_OFFSET,
.info_bits = 0,
.nr_l1streams = 32,
.l1_block_sz = {
1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 10,
5, 4, 14, 6, 4, 14, 6, 4, 8,
4, 2, 1, 1, 1, 1, 14
},
.nr_l2streams = 32,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_PSYS_IOMMU1R_OFFSET,
.info_bits = 0,
.nr_l1streams = 16,
.l1_block_sz = {
1, 4, 4, 4, 4, 16, 8, 4, 32,
16, 16, 2, 2, 2, 1, 12
},
.nr_l2streams = 16,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_PSYS_IOMMUI_OFFSET,
.info_bits = 0,
.nr_l1streams = 0,
.nr_l2streams = 0,
.insert_read_before_invalidate = false,
},
},
.dmem_offset = IPU6_PSYS_DMEM_OFFSET,
},
};
static const struct ipu6_buttress_ctrl isys_buttress_ctrl = {
.ratio = IPU6_IS_FREQ_CTL_DEFAULT_RATIO,
.qos_floor = IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO,
.freq_ctl = IPU6_BUTTRESS_REG_IS_FREQ_CTL,
.pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT,
.pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK,
.pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE,
.pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE,
};
static const struct ipu6_buttress_ctrl psys_buttress_ctrl = {
.ratio = IPU6_PS_FREQ_CTL_DEFAULT_RATIO,
.qos_floor = IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO,
.freq_ctl = IPU6_BUTTRESS_REG_PS_FREQ_CTL,
.pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT,
.pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK,
.pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE,
.pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE,
};
static void
ipu6_pkg_dir_configure_spc(struct ipu6_device *isp,
const struct ipu6_hw_variants *hw_variant,
int pkg_dir_idx, void __iomem *base,
u64 *pkg_dir, dma_addr_t pkg_dir_vied_address)
{
struct ipu6_cell_program *prog;
void __iomem *spc_base;
u32 server_fw_addr;
dma_addr_t dma_addr;
u32 pg_offset;
server_fw_addr = lower_32_bits(*(pkg_dir + (pkg_dir_idx + 1) * 2));
if (pkg_dir_idx == IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX)
dma_addr = sg_dma_address(isp->isys->fw_sgt.sgl);
else
dma_addr = sg_dma_address(isp->psys->fw_sgt.sgl);
pg_offset = server_fw_addr - dma_addr;
prog = (struct ipu6_cell_program *)((uintptr_t)isp->cpd_fw->data +
pg_offset);
spc_base = base + prog->regs_addr;
if (spc_base != (base + hw_variant->spc_offset))
dev_warn(&isp->pdev->dev,
"SPC reg addr %p not matching value from CPD %p\n",
base + hw_variant->spc_offset, spc_base);
writel(server_fw_addr + prog->blob_offset +
prog->icache_source, spc_base + IPU6_PSYS_REG_SPC_ICACHE_BASE);
writel(IPU6_INFO_REQUEST_DESTINATION_IOSF,
spc_base + IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER);
writel(prog->start[1], spc_base + IPU6_PSYS_REG_SPC_START_PC);
writel(pkg_dir_vied_address, base + hw_variant->dmem_offset);
}
void ipu6_configure_spc(struct ipu6_device *isp,
const struct ipu6_hw_variants *hw_variant,
int pkg_dir_idx, void __iomem *base, u64 *pkg_dir,
dma_addr_t pkg_dir_dma_addr)
{
void __iomem *dmem_base = base + hw_variant->dmem_offset;
void __iomem *spc_regs_base = base + hw_variant->spc_offset;
u32 val;
val = readl(spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL);
val |= IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE;
writel(val, spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL);
if (isp->secure_mode)
writel(IPU6_PKG_DIR_IMR_OFFSET, dmem_base);
else
ipu6_pkg_dir_configure_spc(isp, hw_variant, pkg_dir_idx, base,
pkg_dir, pkg_dir_dma_addr);
}
EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, "INTEL_IPU6");
#define IPU6_ISYS_CSI2_NPORTS 4
#define IPU6SE_ISYS_CSI2_NPORTS 4
#define IPU6_TGL_ISYS_CSI2_NPORTS 8
#define IPU6EP_MTL_ISYS_CSI2_NPORTS 6
static void ipu6_internal_pdata_init(struct ipu6_device *isp)
{
u8 hw_ver = isp->hw_ver;
isys_ipdata.num_parallel_streams = IPU6_ISYS_NUM_STREAMS;
isys_ipdata.sram_gran_shift = IPU6_SRAM_GRANULARITY_SHIFT;
isys_ipdata.sram_gran_size = IPU6_SRAM_GRANULARITY_SIZE;
isys_ipdata.max_sram_size = IPU6_MAX_SRAM_SIZE;
isys_ipdata.sensor_type_start = IPU6_FW_ISYS_SENSOR_TYPE_START;
isys_ipdata.sensor_type_end = IPU6_FW_ISYS_SENSOR_TYPE_END;
isys_ipdata.max_streams = IPU6_ISYS_NUM_STREAMS;
isys_ipdata.max_send_queues = IPU6_N_MAX_SEND_QUEUES;
isys_ipdata.max_sram_blocks = IPU6_NOF_SRAM_BLOCKS_MAX;
isys_ipdata.max_devq_size = IPU6_DEV_SEND_QUEUE_SIZE;
isys_ipdata.csi2.nports = IPU6_ISYS_CSI2_NPORTS;
isys_ipdata.csi2.irq_mask = IPU6_CSI_RX_ERROR_IRQ_MASK;
isys_ipdata.csi2.ctrl0_irq_edge = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE;
isys_ipdata.csi2.ctrl0_irq_clear =
IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR;
isys_ipdata.csi2.ctrl0_irq_mask = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK;
isys_ipdata.csi2.ctrl0_irq_enable =
IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE;
isys_ipdata.csi2.ctrl0_irq_status =
IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS;
isys_ipdata.csi2.ctrl0_irq_lnp =
IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE;
isys_ipdata.enhanced_iwake = is_ipu6ep_mtl(hw_ver) || is_ipu6ep(hw_ver);
psys_ipdata.hw_variant.spc_offset = IPU6_PSYS_SPC_OFFSET;
isys_ipdata.csi2.fw_access_port_ofs = CSI_REG_HUB_FW_ACCESS_PORT_OFS;
if (is_ipu6ep(hw_ver)) {
isys_ipdata.ltr = IPU6EP_LTR_VALUE;
isys_ipdata.memopen_threshold = IPU6EP_MIN_MEMOPEN_TH;
}
if (is_ipu6_tgl(hw_ver))
isys_ipdata.csi2.nports = IPU6_TGL_ISYS_CSI2_NPORTS;
if (is_ipu6ep_mtl(hw_ver)) {
isys_ipdata.csi2.nports = IPU6EP_MTL_ISYS_CSI2_NPORTS;
isys_ipdata.csi2.ctrl0_irq_edge =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE;
isys_ipdata.csi2.ctrl0_irq_clear =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR;
isys_ipdata.csi2.ctrl0_irq_mask =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK;
isys_ipdata.csi2.ctrl0_irq_enable =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE;
isys_ipdata.csi2.ctrl0_irq_lnp =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE;
isys_ipdata.csi2.ctrl0_irq_status =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS;
isys_ipdata.csi2.fw_access_port_ofs =
CSI_REG_HUB_FW_ACCESS_PORT_V6OFS;
isys_ipdata.ltr = IPU6EP_MTL_LTR_VALUE;
isys_ipdata.memopen_threshold = IPU6EP_MTL_MIN_MEMOPEN_TH;
}
if (is_ipu6se(hw_ver)) {
isys_ipdata.csi2.nports = IPU6SE_ISYS_CSI2_NPORTS;
isys_ipdata.csi2.irq_mask = IPU6SE_CSI_RX_ERROR_IRQ_MASK;
isys_ipdata.num_parallel_streams = IPU6SE_ISYS_NUM_STREAMS;
isys_ipdata.sram_gran_shift = IPU6SE_SRAM_GRANULARITY_SHIFT;
isys_ipdata.sram_gran_size = IPU6SE_SRAM_GRANULARITY_SIZE;
isys_ipdata.max_sram_size = IPU6SE_MAX_SRAM_SIZE;
isys_ipdata.sensor_type_start =
IPU6SE_FW_ISYS_SENSOR_TYPE_START;
isys_ipdata.sensor_type_end = IPU6SE_FW_ISYS_SENSOR_TYPE_END;
isys_ipdata.max_streams = IPU6SE_ISYS_NUM_STREAMS;
isys_ipdata.max_send_queues = IPU6SE_N_MAX_SEND_QUEUES;
isys_ipdata.max_sram_blocks = IPU6SE_NOF_SRAM_BLOCKS_MAX;
isys_ipdata.max_devq_size = IPU6SE_DEV_SEND_QUEUE_SIZE;
psys_ipdata.hw_variant.spc_offset = IPU6SE_PSYS_SPC_OFFSET;
}
}
static struct ipu6_bus_device *
ipu6_isys_init(struct pci_dev *pdev, struct device *parent,
struct ipu6_buttress_ctrl *ctrl, void __iomem *base,
const struct ipu6_isys_internal_pdata *ipdata)
{
struct device *dev = &pdev->dev;
struct ipu6_bus_device *isys_adev;
struct ipu6_isys_pdata *pdata;
int ret;
ret = ipu_bridge_init(dev, ipu_bridge_parse_ssdb);
if (ret) {
dev_err_probe(dev, ret, "IPU6 bridge init failed\n");
return ERR_PTR(ret);
}
pdata = kzalloc_obj(*pdata);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->base = base;
pdata->ipdata = ipdata;
isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl,
IPU6_ISYS_NAME);
if (IS_ERR(isys_adev)) {
kfree(pdata);
return dev_err_cast_probe(dev, isys_adev,
"ipu6_bus_initialize_device isys failed\n");
}
isys_adev->mmu = ipu6_mmu_init(dev, base, ISYS_MMID,
&ipdata->hw_variant);
if (IS_ERR(isys_adev->mmu)) {
put_device(&isys_adev->auxdev.dev);
kfree(pdata);
return dev_err_cast_probe(dev, isys_adev->mmu,
"ipu6_mmu_init(isys_adev->mmu) failed\n");
}
isys_adev->mmu->dev = &isys_adev->auxdev.dev;
ret = ipu6_bus_add_device(isys_adev);
if (ret) {
kfree(pdata);
return ERR_PTR(ret);
}
return isys_adev;
}
static struct ipu6_bus_device *
ipu6_psys_init(struct pci_dev *pdev, struct device *parent,
struct ipu6_buttress_ctrl *ctrl, void __iomem *base,
const struct ipu6_psys_internal_pdata *ipdata)
{
struct ipu6_bus_device *psys_adev;
struct ipu6_psys_pdata *pdata;
int ret;
pdata = kzalloc_obj(*pdata);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->base = base;
pdata->ipdata = ipdata;
psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl,
IPU6_PSYS_NAME);
if (IS_ERR(psys_adev)) {
kfree(pdata);
return dev_err_cast_probe(&pdev->dev, psys_adev,
"ipu6_bus_initialize_device psys failed\n");
}
psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, PSYS_MMID,
&ipdata->hw_variant);
if (IS_ERR(psys_adev->mmu)) {
put_device(&psys_adev->auxdev.dev);
kfree(pdata);
return dev_err_cast_probe(&pdev->dev, psys_adev->mmu,
"ipu6_mmu_init(psys_adev->mmu) failed\n");
}
psys_adev->mmu->dev = &psys_adev->auxdev.dev;
ret = ipu6_bus_add_device(psys_adev);
if (ret) {
kfree(pdata);
return ERR_PTR(ret);
}
return psys_adev;
}
static int ipu6_pci_config_setup(struct pci_dev *dev, u8 hw_ver)
{
int ret;
/* No PCI msi capability for IPU6EP */
if (is_ipu6ep(hw_ver) || is_ipu6ep_mtl(hw_ver)) {
/* likely do nothing as msi not enabled by default */
pci_disable_msi(dev);
return 0;
}
ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_MSI);
if (ret < 0)
return dev_err_probe(&dev->dev, ret, "Request msi failed");
return 0;
}
static void ipu6_configure_vc_mechanism(struct ipu6_device *isp)
{
u32 val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL);
if (IPU6_BTRS_ARB_STALL_MODE_VC0 == IPU6_BTRS_ARB_MODE_TYPE_STALL)
val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0;
else
val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0;
if (IPU6_BTRS_ARB_STALL_MODE_VC1 == IPU6_BTRS_ARB_MODE_TYPE_STALL)
val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1;
else
val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1;
writel(val, isp->base + BUTTRESS_REG_BTRS_CTRL);
}
static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct ipu6_buttress_ctrl *isys_ctrl = NULL, *psys_ctrl = NULL;
struct device *dev = &pdev->dev;
void __iomem *isys_base = NULL;
void __iomem *psys_base = NULL;
struct ipu6_device *isp;
phys_addr_t phys;
u32 val, version, sku_id;
int ret;
isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
if (!isp)
return -ENOMEM;
isp->pdev = pdev;
INIT_LIST_HEAD(&isp->devices);
ret = pcim_enable_device(pdev);
if (ret)
return dev_err_probe(dev, ret, "Enable PCI device failed\n");
phys = pci_resource_start(pdev, IPU6_PCI_BAR);
dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys);
isp->base = pcim_iomap_region(pdev, IPU6_PCI_BAR, IPU6_NAME);
if (IS_ERR(isp->base))
return dev_err_probe(dev, PTR_ERR(isp->base),
"Failed to I/O mem remapping\n");
pci_set_drvdata(pdev, isp);
pci_set_master(pdev);
isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt);
switch (id->device) {
case PCI_DEVICE_ID_INTEL_IPU6:
isp->hw_ver = IPU6_VER_6;
isp->cpd_fw_name = IPU6_FIRMWARE_NAME;
break;
case PCI_DEVICE_ID_INTEL_IPU6SE:
isp->hw_ver = IPU6_VER_6SE;
isp->cpd_fw_name = IPU6SE_FIRMWARE_NAME;
isp->cpd_metadata_cmpnt_size =
sizeof(struct ipu6se_cpd_metadata_cmpnt);
break;
case PCI_DEVICE_ID_INTEL_IPU6EP_ADLP:
case PCI_DEVICE_ID_INTEL_IPU6EP_RPLP:
isp->hw_ver = IPU6_VER_6EP;
isp->cpd_fw_name = IPU6EP_FIRMWARE_NAME;
break;
case PCI_DEVICE_ID_INTEL_IPU6EP_ADLN:
isp->hw_ver = IPU6_VER_6EP;
isp->cpd_fw_name = IPU6EPADLN_FIRMWARE_NAME;
break;
case PCI_DEVICE_ID_INTEL_IPU6EP_MTL:
isp->hw_ver = IPU6_VER_6EP_MTL;
isp->cpd_fw_name = IPU6EPMTL_FIRMWARE_NAME;
break;
default:
return dev_err_probe(dev, -ENODEV,
"Unsupported IPU6 device %x\n",
id->device);
}
ipu6_internal_pdata_init(isp);
isys_base = isp->base + isys_ipdata.hw_variant.offset;
psys_base = isp->base + psys_ipdata.hw_variant.offset;
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39));
if (ret)
return dev_err_probe(dev, ret, "Failed to set DMA mask\n");
dma_set_max_seg_size(dev, UINT_MAX);
ret = ipu6_pci_config_setup(pdev, isp->hw_ver);
if (ret)
return ret;
ret = ipu6_buttress_init(isp);
if (ret)
return ret;
ret = request_firmware(&isp->cpd_fw, isp->cpd_fw_name, dev);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"Requesting signed firmware %s failed\n",
isp->cpd_fw_name);
goto buttress_exit;
}
ret = ipu6_cpd_validate_cpd_file(isp, isp->cpd_fw->data,
isp->cpd_fw->size);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"Failed to validate cpd\n");
goto out_ipu6_bus_del_devices;
}
isys_ctrl = devm_kmemdup(dev, &isys_buttress_ctrl,
sizeof(isys_buttress_ctrl), GFP_KERNEL);
if (!isys_ctrl) {
ret = -ENOMEM;
goto out_ipu6_bus_del_devices;
}
isp->isys = ipu6_isys_init(pdev, dev, isys_ctrl, isys_base,
&isys_ipdata);
if (IS_ERR(isp->isys)) {
ret = PTR_ERR(isp->isys);
goto out_ipu6_bus_del_devices;
}
psys_ctrl = devm_kmemdup(dev, &psys_buttress_ctrl,
sizeof(psys_buttress_ctrl), GFP_KERNEL);
if (!psys_ctrl) {
ret = -ENOMEM;
goto out_ipu6_bus_del_devices;
}
isp->psys = ipu6_psys_init(pdev, &isp->isys->auxdev.dev, psys_ctrl,
psys_base, &psys_ipdata);
if (IS_ERR(isp->psys)) {
ret = PTR_ERR(isp->psys);
goto out_ipu6_bus_del_devices;
}
ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev);
if (ret < 0)
goto out_ipu6_bus_del_devices;
ret = ipu6_mmu_hw_init(isp->psys->mmu);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"Failed to set MMU hardware\n");
goto out_ipu6_rpm_put;
}
ret = ipu6_buttress_map_fw_image(isp->psys, isp->cpd_fw,
&isp->psys->fw_sgt);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n");
goto out_ipu6_rpm_put;
}
ret = ipu6_cpd_create_pkg_dir(isp->psys, isp->cpd_fw->data);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"failed to create pkg dir\n");
goto out_ipu6_rpm_put;
}
ret = devm_request_threaded_irq(dev, pdev->irq, ipu6_buttress_isr,
ipu6_buttress_isr_threaded,
IRQF_SHARED, IPU6_NAME, isp);
if (ret) {
dev_err_probe(dev, ret, "Requesting irq failed\n");
goto out_ipu6_rpm_put;
}
ret = ipu6_buttress_authenticate(isp);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"FW authentication failed\n");
goto out_free_irq;
}
ipu6_mmu_hw_cleanup(isp->psys->mmu);
pm_runtime_put(&isp->psys->auxdev.dev);
/* Configure the arbitration mechanisms for VC requests */
ipu6_configure_vc_mechanism(isp);
val = readl(isp->base + BUTTRESS_REG_SKU);
sku_id = FIELD_GET(GENMASK(6, 4), val);
version = FIELD_GET(GENMASK(3, 0), val);
dev_info(dev, "IPU%u-v%u[%x] hardware version %d\n", version, sku_id,
pdev->device, isp->hw_ver);
pm_runtime_put_noidle(dev);
pm_runtime_allow(dev);
isp->bus_ready_to_probe = true;
return 0;
out_free_irq:
devm_free_irq(dev, pdev->irq, isp);
out_ipu6_rpm_put:
pm_runtime_put_sync(&isp->psys->auxdev.dev);
out_ipu6_bus_del_devices:
if (!IS_ERR_OR_NULL(isp->psys)) {
ipu6_cpd_free_pkg_dir(isp->psys);
ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt);
}
if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu))
ipu6_mmu_cleanup(isp->psys->mmu);
if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu))
ipu6_mmu_cleanup(isp->isys->mmu);
ipu6_bus_del_devices(pdev);
release_firmware(isp->cpd_fw);
buttress_exit:
ipu6_buttress_exit(isp);
return ret;
}
static void ipu6_pci_remove(struct pci_dev *pdev)
{
struct ipu6_device *isp = pci_get_drvdata(pdev);
struct ipu6_mmu *isys_mmu = isp->isys->mmu;
struct ipu6_mmu *psys_mmu = isp->psys->mmu;
devm_free_irq(&pdev->dev, pdev->irq, isp);
ipu6_cpd_free_pkg_dir(isp->psys);
ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt);
ipu6_buttress_exit(isp);
ipu6_bus_del_devices(pdev);
pm_runtime_forbid(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
release_firmware(isp->cpd_fw);
ipu6_mmu_cleanup(psys_mmu);
ipu6_mmu_cleanup(isys_mmu);
}
static void ipu6_pci_reset_prepare(struct pci_dev *pdev)
{
struct ipu6_device *isp = pci_get_drvdata(pdev);
pm_runtime_forbid(&isp->pdev->dev);
}
static void ipu6_pci_reset_done(struct pci_dev *pdev)
{
struct ipu6_device *isp = pci_get_drvdata(pdev);
ipu6_buttress_restore(isp);
if (isp->secure_mode)
ipu6_buttress_reset_authentication(isp);
isp->need_ipc_reset = true;
pm_runtime_allow(&isp->pdev->dev);
}
/*
* PCI base driver code requires driver to provide these to enable
* PCI device level PM state transitions (D0<->D3)
*/
static int ipu6_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
synchronize_irq(pdev->irq);
return 0;
}
static int ipu6_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct ipu6_device *isp = pci_get_drvdata(pdev);
struct ipu6_buttress *b = &isp->buttress;
int ret;
/* Configure the arbitration mechanisms for VC requests */
ipu6_configure_vc_mechanism(isp);
isp->secure_mode = ipu6_buttress_get_secure_mode(isp);
dev_info(dev, "IPU6 in %s mode\n",
isp->secure_mode ? "secure" : "non-secure");
ipu6_buttress_restore(isp);
ret = ipu6_buttress_ipc_reset(isp, &b->cse);
if (ret)
dev_err(&isp->pdev->dev, "IPC reset protocol failed!\n");
ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev);
if (ret < 0) {
dev_err(&isp->psys->auxdev.dev, "Failed to get runtime PM\n");
return 0;
}
ret = ipu6_buttress_authenticate(isp);
if (ret)
dev_err(&isp->pdev->dev, "FW authentication failed(%d)\n", ret);
pm_runtime_put(&isp->psys->auxdev.dev);
return 0;
}
static int ipu6_runtime_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct ipu6_device *isp = pci_get_drvdata(pdev);
int ret;
ipu6_configure_vc_mechanism(isp);
ipu6_buttress_restore(isp);
if (isp->need_ipc_reset) {
struct ipu6_buttress *b = &isp->buttress;
isp->need_ipc_reset = false;
ret = ipu6_buttress_ipc_reset(isp, &b->cse);
if (ret)
dev_err(&isp->pdev->dev, "IPC reset protocol failed\n");
}
return 0;
}
static const struct dev_pm_ops ipu6_pm_ops = {
SYSTEM_SLEEP_PM_OPS(&ipu6_suspend, &ipu6_resume)
RUNTIME_PM_OPS(&ipu6_suspend, &ipu6_runtime_resume, NULL)
};
MODULE_DEVICE_TABLE(pci, ipu6_pci_tbl);
static const struct pci_error_handlers pci_err_handlers = {
.reset_prepare = ipu6_pci_reset_prepare,
.reset_done = ipu6_pci_reset_done,
};
static struct pci_driver ipu6_pci_driver = {
.name = IPU6_NAME,
.id_table = ipu6_pci_tbl,
.probe = ipu6_pci_probe,
.remove = ipu6_pci_remove,
.driver = {
.pm = pm_ptr(&ipu6_pm_ops),
},
.err_handler = &pci_err_handlers,
};
module_pci_driver(ipu6_pci_driver);
MODULE_IMPORT_NS("INTEL_IPU_BRIDGE");
MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>");
MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>");
MODULE_AUTHOR("Qingwu Zhang <qingwu.zhang@intel.com>");
MODULE_AUTHOR("Yunliang Ding <yunliang.ding@intel.com>");
MODULE_AUTHOR("Hongju Wang");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Intel IPU6 PCI driver");
@@ -0,0 +1,342 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013 - 2024 Intel Corporation */
#ifndef IPU6_H
#define IPU6_H
#include <linux/list.h>
#include <linux/pci.h>
#include <linux/types.h>
#include "ipu6-buttress.h"
struct firmware;
struct pci_dev;
struct ipu6_bus_device;
#define IPU6_NAME "intel-ipu6"
#define IPU6_MEDIA_DEV_MODEL_NAME "ipu6"
#define IPU6SE_FIRMWARE_NAME "intel/ipu/ipu6se_fw.bin"
#define IPU6EP_FIRMWARE_NAME "intel/ipu/ipu6ep_fw.bin"
#define IPU6_FIRMWARE_NAME "intel/ipu/ipu6_fw.bin"
#define IPU6EPMTL_FIRMWARE_NAME "intel/ipu/ipu6epmtl_fw.bin"
#define IPU6EPADLN_FIRMWARE_NAME "intel/ipu/ipu6epadln_fw.bin"
enum ipu6_version {
IPU6_VER_INVALID = 0,
IPU6_VER_6 = 1,
IPU6_VER_6SE = 3,
IPU6_VER_6EP = 5,
IPU6_VER_6EP_MTL = 6,
};
/*
* IPU6 - TGL
* IPU6SE - JSL
* IPU6EP - ADL/RPL
* IPU6EP_MTL - MTL
*/
static inline bool is_ipu6se(u8 hw_ver)
{
return hw_ver == IPU6_VER_6SE;
}
static inline bool is_ipu6ep(u8 hw_ver)
{
return hw_ver == IPU6_VER_6EP;
}
static inline bool is_ipu6ep_mtl(u8 hw_ver)
{
return hw_ver == IPU6_VER_6EP_MTL;
}
static inline bool is_ipu6_tgl(u8 hw_ver)
{
return hw_ver == IPU6_VER_6;
}
/*
* ISYS DMA can overshoot. For higher resolutions over allocation is one line
* but it must be at minimum 1024 bytes. Value could be different in
* different versions / generations thus provide it via platform data.
*/
#define IPU6_ISYS_OVERALLOC_MIN 1024
/* Physical pages in GDA is 128, page size is 2K for IPU6, 1K for others */
#define IPU6_DEVICE_GDA_NR_PAGES 128
/* Virtualization factor to calculate the available virtual pages */
#define IPU6_DEVICE_GDA_VIRT_FACTOR 32
struct ipu6_device {
struct pci_dev *pdev;
struct list_head devices;
struct ipu6_bus_device *isys;
struct ipu6_bus_device *psys;
struct ipu6_buttress buttress;
const struct firmware *cpd_fw;
const char *cpd_fw_name;
u32 cpd_metadata_cmpnt_size;
void __iomem *base;
bool need_ipc_reset;
bool secure_mode;
u8 hw_ver;
bool bus_ready_to_probe;
};
#define IPU6_ISYS_NAME "isys"
#define IPU6_PSYS_NAME "psys"
#define IPU6_MMU_MAX_DEVICES 4
#define IPU6_MMU_ADDR_BITS 32
/* The firmware is accessible within the first 2 GiB only in non-secure mode. */
#define IPU6_MMU_ADDR_BITS_NON_SECURE 31
#define IPU6_MMU_MAX_TLB_L1_STREAMS 32
#define IPU6_MMU_MAX_TLB_L2_STREAMS 32
#define IPU6_MAX_LI_BLOCK_ADDR 128
#define IPU6_MAX_L2_BLOCK_ADDR 64
#define IPU6SE_ISYS_NUM_STREAMS IPU6SE_NONSECURE_STREAM_ID_MAX
#define IPU6_ISYS_NUM_STREAMS IPU6_NONSECURE_STREAM_ID_MAX
/*
* To maximize the IOSF utlization, IPU6 need to send requests in bursts.
* At the DMA interface with the buttress, there are CDC FIFOs with burst
* collection capability. CDC FIFO burst collectors have a configurable
* threshold and is configured based on the outcome of performance measurements.
*
* isys has 3 ports with IOSF interface for VC0, VC1 and VC2
* psys has 4 ports with IOSF interface for VC0, VC1w, VC1r and VC2
*
* Threshold values are pre-defined and are arrived at after performance
* evaluations on a type of IPU6
*/
#define IPU6_MAX_VC_IOSF_PORTS 4
/*
* IPU6 must configure correct arbitration mechanism related to the IOSF VC
* requests. There are two options per VC0 and VC1 - > 0 means rearbitrate on
* stall and 1 means stall until the request is completed.
*/
#define IPU6_BTRS_ARB_MODE_TYPE_REARB 0
#define IPU6_BTRS_ARB_MODE_TYPE_STALL 1
/* Currently chosen arbitration mechanism for VC0 */
#define IPU6_BTRS_ARB_STALL_MODE_VC0 \
IPU6_BTRS_ARB_MODE_TYPE_REARB
/* Currently chosen arbitration mechanism for VC1 */
#define IPU6_BTRS_ARB_STALL_MODE_VC1 \
IPU6_BTRS_ARB_MODE_TYPE_REARB
/*
* MMU Invalidation HW bug workaround by ZLW mechanism
*
* Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in
* wrong translation or replication of the translation. This will cause data
* corruption. So we cannot directly use the MMU V2 invalidation registers
* to invalidate the MMU. Instead, whenever an invalidate is called, we need to
* clear the TLB by evicting all the valid translations by filling it with trash
* buffer (which is guaranteed not to be used by any other processes). ZLW is
* used to fill the L1 and L2 caches with the trash buffer translations. ZLW
* or Zero length write, is pre-fetch mechanism to pre-fetch the pages in
* advance to the L1 and L2 caches without triggering any memory operations.
*
* In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream
* One L1 block has 16 entries, hence points to 16 * 4K pages
* L2 -> 16 streams and 32 blocks. 2 blocks per streams
* One L2 block maps to 1024 L1 entries, hence points to 4MB address range
* 2 blocks per L2 stream means, 1 stream points to 8MB range
*
* As we need to clear the caches and 8MB being the biggest cache size, we need
* to have trash buffer which points to 8MB address range. As these trash
* buffers are not used for any memory transactions, we need only the least
* amount of physical memory. So we reserve 8MB IOVA address range but only
* one page is reserved from physical memory. Each of this 8MB IOVA address
* range is then mapped to the same physical memory page.
*/
/* One L2 entry maps 1024 L1 entries and one L1 entry per page */
#define IPU6_MMUV2_L2_RANGE (1024 * PAGE_SIZE)
/* Max L2 blocks per stream */
#define IPU6_MMUV2_MAX_L2_BLOCKS 2
/* Max L1 blocks per stream */
#define IPU6_MMUV2_MAX_L1_BLOCKS 16
#define IPU6_MMUV2_TRASH_RANGE (IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS)
/* Entries per L1 block */
#define MMUV2_ENTRIES_PER_L1_BLOCK 16
#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE)
#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU6_MMUV2_L2_RANGE
/*
* In some of the IPU6 MMUs, there is provision to configure L1 and L2 page
* table caches. Both these L1 and L2 caches are divided into multiple sections
* called streams. There is maximum 16 streams for both caches. Each of these
* sections are subdivided into multiple blocks. When nr_l1streams = 0 and
* nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support
* L1/L2 page table caches.
*
* L1 stream per block sizes are configurable and varies per usecase.
* L2 has constant block sizes - 2 blocks per stream.
*
* MMU1 support pre-fetching of the pages to have less cache lookup misses. To
* enable the pre-fetching, MMU1 AT (Address Translator) device registers
* need to be configured.
*
* There are four types of memory accesses which requires ZLW configuration.
* ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU.
*
* 1. Sequential Access or 1D mode
* Set ZLW_EN -> 1
* set ZLW_PAGE_CROSS_1D -> 1
* Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where
* N is pre-defined and hardcoded in the platform data
* Set ZLW_2D -> 0
*
* 2. ZLW 2D mode
* Set ZLW_EN -> 1
* set ZLW_PAGE_CROSS_1D -> 1,
* Set ZLW_N -> 0
* Set ZLW_2D -> 1
*
* 3. ZLW Enable (no 1D or 2D mode)
* Set ZLW_EN -> 1
* set ZLW_PAGE_CROSS_1D -> 0,
* Set ZLW_N -> 0
* Set ZLW_2D -> 0
*
* 4. ZLW disable
* Set ZLW_EN -> 0
* set ZLW_PAGE_CROSS_1D -> 0,
* Set ZLW_N -> 0
* Set ZLW_2D -> 0
*
* To configure the ZLW for the above memory access, four registers are
* available. Hence to track these four settings, we have the following entries
* in the struct ipu6_mmu_hw. Each of these entries are per stream and
* available only for the L1 streams.
*
* a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN)
* b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary
* c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted
* Insert ZLW request N pages ahead address.
* d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D)
*
*
* Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined
* as per the usecase specific calculations. Any change to this pre-defined
* table has to happen in sync with IPU6 FW.
*/
struct ipu6_mmu_hw {
union {
unsigned long offset;
void __iomem *base;
};
u32 info_bits;
u8 nr_l1streams;
/*
* L1 has variable blocks per stream - total of 64 blocks and maximum of
* 16 blocks per stream. Configurable by using the block start address
* per stream. Block start address is calculated from the block size
*/
u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS];
/* Is ZLW is enabled in each stream */
bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS];
bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS];
u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS];
bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS];
u32 l1_stream_id_reg_offset;
u32 l2_stream_id_reg_offset;
u8 nr_l2streams;
/*
* L2 has fixed 2 blocks per stream. Block address is calculated
* from the block size
*/
u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS];
/* flag to track if WA is needed for successive invalidate HW bug */
bool insert_read_before_invalidate;
};
struct ipu6_mmu_pdata {
u32 nr_mmus;
struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES];
int mmid;
};
struct ipu6_isys_csi2_pdata {
void __iomem *base;
};
struct ipu6_isys_internal_csi2_pdata {
u32 nports;
u32 irq_mask;
u32 ctrl0_irq_edge;
u32 ctrl0_irq_clear;
u32 ctrl0_irq_mask;
u32 ctrl0_irq_enable;
u32 ctrl0_irq_lnp;
u32 ctrl0_irq_status;
u32 fw_access_port_ofs;
};
struct ipu6_isys_internal_tpg_pdata {
u32 ntpgs;
u32 *offsets;
u32 *sels;
};
struct ipu6_hw_variants {
unsigned long offset;
u32 nr_mmus;
struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES];
u8 cdc_fifos;
u8 cdc_fifo_threshold[IPU6_MAX_VC_IOSF_PORTS];
u32 dmem_offset;
u32 spc_offset;
};
struct ipu6_isys_internal_pdata {
struct ipu6_isys_internal_csi2_pdata csi2;
struct ipu6_hw_variants hw_variant;
u32 num_parallel_streams;
u32 isys_dma_overshoot;
u32 sram_gran_shift;
u32 sram_gran_size;
u32 max_sram_size;
u32 max_streams;
u32 max_send_queues;
u32 max_sram_blocks;
u32 max_devq_size;
u32 sensor_type_start;
u32 sensor_type_end;
u32 ltr;
u32 memopen_threshold;
bool enhanced_iwake;
};
struct ipu6_isys_pdata {
void __iomem *base;
const struct ipu6_isys_internal_pdata *ipdata;
};
struct ipu6_psys_internal_pdata {
struct ipu6_hw_variants hw_variant;
};
struct ipu6_psys_pdata {
void __iomem *base;
const struct ipu6_psys_internal_pdata *ipdata;
};
int ipu6_fw_authenticate(void *data, u64 val);
void ipu6_configure_spc(struct ipu6_device *isp,
const struct ipu6_hw_variants *hw_variant,
int pkg_dir_idx, void __iomem *base, u64 *pkg_dir,
dma_addr_t pkg_dir_dma_addr);
#endif /* IPU6_H */
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0-only
# Copyright (C) 2023, Intel Corporation. All rights reserved.
config INTEL_VSC
tristate "Intel Visual Sensing Controller"
depends on INTEL_MEI && ACPI && VIDEO_DEV
depends on IPU_BRIDGE || !IPU_BRIDGE
select MEDIA_CONTROLLER
select VIDEO_V4L2_SUBDEV_API
select V4L2_FWNODE
help
This adds support for Intel Visual Sensing Controller (IVSC).
The IVSC support is split into two devices, ACE (Algorithm
Context Engine) and CSI (Camera Serial Interface), each of which
have their own drivers. The ACE is used to select the ownership
of the sensor between the IVSC and the host CPU while the CSI is
used to both select the routing destination for the data the
sensor transmits over the CSI-2 bus between the IVSC and the
host CPU and to configure the CSI-2 bus itself.
The modules will be called ivsc-ace and ivsc-csi.
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (C) 2023, Intel Corporation. All rights reserved.
obj-$(CONFIG_INTEL_VSC) += ivsc-csi.o
ivsc-csi-y += mei_csi.o
obj-$(CONFIG_INTEL_VSC) += ivsc-ace.o
ivsc-ace-y += mei_ace.o
@@ -0,0 +1,582 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2023 Intel Corporation. All rights reserved.
* Intel Visual Sensing Controller ACE Linux driver
*/
/*
* To set ownership of camera sensor, there is specific command, which
* is sent via MEI protocol. That's a two-step scheme where the firmware
* first acks receipt of the command and later responses the command was
* executed. The command sending function uses "completion" as the
* synchronization mechanism. The notification for command is received
* via a mei callback which wakes up the caller. There can be only one
* outstanding command at a time.
*
* The power line of camera sensor is directly connected to IVSC instead
* of host, when camera sensor ownership is switched to host, sensor is
* already powered up by firmware.
*/
#include <linux/acpi.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/mei_cl_bus.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/uuid.h>
#include <linux/workqueue.h>
/* indicating driver message */
#define ACE_DRV_MSG 1
/* indicating set command */
#define ACE_CMD_SET 4
/* command timeout determined experimentally */
#define ACE_CMD_TIMEOUT (5 * HZ)
/* indicating the first command block */
#define ACE_CMD_INIT_BLOCK 1
/* indicating the last command block */
#define ACE_CMD_FINAL_BLOCK 1
/* size of camera status notification content */
#define ACE_CAMERA_STATUS_SIZE 5
/* UUID used to get firmware id */
#define ACE_GET_FW_ID_UUID UUID_LE(0x6167DCFB, 0x72F1, 0x4584, 0xBF, \
0xE3, 0x84, 0x17, 0x71, 0xAA, 0x79, 0x0B)
/* UUID used to get csi device */
#define MEI_CSI_UUID UUID_LE(0x92335FCF, 0x3203, 0x4472, \
0xAF, 0x93, 0x7b, 0x44, 0x53, 0xAC, 0x29, 0xDA)
/* identify firmware event type */
enum ace_event_type {
/* firmware ready */
ACE_FW_READY = 0x8,
/* command response */
ACE_CMD_RESPONSE = 0x10,
};
/* identify camera sensor ownership */
enum ace_camera_owner {
ACE_CAMERA_IVSC,
ACE_CAMERA_HOST,
};
/* identify the command id supported by firmware IPC */
enum ace_cmd_id {
/* used to switch camera sensor to host */
ACE_SWITCH_CAMERA_TO_HOST = 0x13,
/* used to switch camera sensor to IVSC */
ACE_SWITCH_CAMERA_TO_IVSC = 0x14,
/* used to get firmware id */
ACE_GET_FW_ID = 0x1A,
};
/* ACE command header structure */
struct ace_cmd_hdr {
u32 firmware_id : 16;
u32 instance_id : 8;
u32 type : 5;
u32 rsp : 1;
u32 msg_tgt : 1;
u32 _hw_rsvd_1 : 1;
u32 param_size : 20;
u32 cmd_id : 8;
u32 final_block : 1;
u32 init_block : 1;
u32 _hw_rsvd_2 : 2;
} __packed;
/* ACE command parameter structure */
union ace_cmd_param {
uuid_le uuid;
u32 param;
};
/* ACE command structure */
struct ace_cmd {
struct ace_cmd_hdr hdr;
union ace_cmd_param param;
} __packed;
/* ACE notification header */
union ace_notif_hdr {
struct _confirm {
u32 status : 24;
u32 type : 5;
u32 rsp : 1;
u32 msg_tgt : 1;
u32 _hw_rsvd_1 : 1;
u32 param_size : 20;
u32 cmd_id : 8;
u32 final_block : 1;
u32 init_block : 1;
u32 _hw_rsvd_2 : 2;
} __packed ack;
struct _event {
u32 rsvd1 : 16;
u32 event_type : 8;
u32 type : 5;
u32 ack : 1;
u32 msg_tgt : 1;
u32 _hw_rsvd_1 : 1;
u32 rsvd2 : 30;
u32 _hw_rsvd_2 : 2;
} __packed event;
struct _response {
u32 event_id : 16;
u32 notif_type : 8;
u32 type : 5;
u32 rsp : 1;
u32 msg_tgt : 1;
u32 _hw_rsvd_1 : 1;
u32 event_data_size : 16;
u32 request_target : 1;
u32 request_type : 5;
u32 cmd_id : 8;
u32 _hw_rsvd_2 : 2;
} __packed response;
};
/* ACE notification content */
union ace_notif_cont {
u16 firmware_id;
u8 state_notif;
u8 camera_status[ACE_CAMERA_STATUS_SIZE];
};
/* ACE notification structure */
struct ace_notif {
union ace_notif_hdr hdr;
union ace_notif_cont cont;
} __packed;
struct mei_ace {
struct mei_cl_device *cldev;
/* command ack */
struct ace_notif cmd_ack;
/* command response */
struct ace_notif cmd_response;
/* used to wait for command ack and response */
struct completion cmd_completion;
/* lock used to prevent multiple call to send command */
struct mutex lock;
/* used to construct command */
u16 firmware_id;
struct device *csi_dev;
/* runtime PM link from ace to csi */
struct device_link *csi_link;
struct work_struct work;
};
static inline void init_cmd_hdr(struct ace_cmd_hdr *hdr)
{
memset(hdr, 0, sizeof(struct ace_cmd_hdr));
hdr->type = ACE_CMD_SET;
hdr->msg_tgt = ACE_DRV_MSG;
hdr->init_block = ACE_CMD_INIT_BLOCK;
hdr->final_block = ACE_CMD_FINAL_BLOCK;
}
static int construct_command(struct mei_ace *ace, struct ace_cmd *cmd,
enum ace_cmd_id cmd_id)
{
union ace_cmd_param *param = &cmd->param;
struct ace_cmd_hdr *hdr = &cmd->hdr;
init_cmd_hdr(hdr);
hdr->cmd_id = cmd_id;
switch (cmd_id) {
case ACE_GET_FW_ID:
param->uuid = ACE_GET_FW_ID_UUID;
hdr->param_size = sizeof(param->uuid);
break;
case ACE_SWITCH_CAMERA_TO_IVSC:
param->param = 0;
hdr->firmware_id = ace->firmware_id;
hdr->param_size = sizeof(param->param);
break;
case ACE_SWITCH_CAMERA_TO_HOST:
hdr->firmware_id = ace->firmware_id;
break;
default:
return -EINVAL;
}
return hdr->param_size + sizeof(cmd->hdr);
}
/* send command to firmware */
static int mei_ace_send(struct mei_ace *ace, struct ace_cmd *cmd,
size_t len, bool only_ack)
{
union ace_notif_hdr *resp_hdr = &ace->cmd_response.hdr;
union ace_notif_hdr *ack_hdr = &ace->cmd_ack.hdr;
struct ace_cmd_hdr *cmd_hdr = &cmd->hdr;
int ret;
mutex_lock(&ace->lock);
reinit_completion(&ace->cmd_completion);
ret = mei_cldev_send(ace->cldev, (u8 *)cmd, len);
if (ret < 0)
goto out;
ret = wait_for_completion_killable_timeout(&ace->cmd_completion,
ACE_CMD_TIMEOUT);
if (ret < 0) {
goto out;
} else if (!ret) {
ret = -ETIMEDOUT;
goto out;
}
if (ack_hdr->ack.cmd_id != cmd_hdr->cmd_id) {
ret = -EINVAL;
goto out;
}
/* command ack status */
ret = ack_hdr->ack.status;
if (ret) {
ret = -EIO;
goto out;
}
if (only_ack)
goto out;
ret = wait_for_completion_killable_timeout(&ace->cmd_completion,
ACE_CMD_TIMEOUT);
if (ret < 0) {
goto out;
} else if (!ret) {
ret = -ETIMEDOUT;
goto out;
} else {
ret = 0;
}
if (resp_hdr->response.cmd_id != cmd_hdr->cmd_id)
ret = -EINVAL;
out:
mutex_unlock(&ace->lock);
return ret;
}
static int ace_set_camera_owner(struct mei_ace *ace,
enum ace_camera_owner owner)
{
enum ace_cmd_id cmd_id;
struct ace_cmd cmd;
int cmd_size;
int ret;
if (owner == ACE_CAMERA_IVSC)
cmd_id = ACE_SWITCH_CAMERA_TO_IVSC;
else
cmd_id = ACE_SWITCH_CAMERA_TO_HOST;
cmd_size = construct_command(ace, &cmd, cmd_id);
if (cmd_size >= 0)
ret = mei_ace_send(ace, &cmd, cmd_size, false);
else
ret = cmd_size;
return ret;
}
/* the first command downloaded to firmware */
static inline int ace_get_firmware_id(struct mei_ace *ace)
{
struct ace_cmd cmd;
int cmd_size;
int ret;
cmd_size = construct_command(ace, &cmd, ACE_GET_FW_ID);
if (cmd_size >= 0)
ret = mei_ace_send(ace, &cmd, cmd_size, true);
else
ret = cmd_size;
return ret;
}
static void handle_command_response(struct mei_ace *ace,
struct ace_notif *resp, int len)
{
union ace_notif_hdr *hdr = &resp->hdr;
switch (hdr->response.cmd_id) {
case ACE_SWITCH_CAMERA_TO_IVSC:
case ACE_SWITCH_CAMERA_TO_HOST:
memcpy(&ace->cmd_response, resp, len);
complete(&ace->cmd_completion);
break;
case ACE_GET_FW_ID:
break;
default:
break;
}
}
static void handle_command_ack(struct mei_ace *ace,
struct ace_notif *ack, int len)
{
union ace_notif_hdr *hdr = &ack->hdr;
switch (hdr->ack.cmd_id) {
case ACE_GET_FW_ID:
ace->firmware_id = ack->cont.firmware_id;
fallthrough;
case ACE_SWITCH_CAMERA_TO_IVSC:
case ACE_SWITCH_CAMERA_TO_HOST:
memcpy(&ace->cmd_ack, ack, len);
complete(&ace->cmd_completion);
break;
default:
break;
}
}
/* callback for receive */
static void mei_ace_rx(struct mei_cl_device *cldev)
{
struct mei_ace *ace = mei_cldev_get_drvdata(cldev);
struct ace_notif event;
union ace_notif_hdr *hdr = &event.hdr;
int ret;
ret = mei_cldev_recv(cldev, (u8 *)&event, sizeof(event));
if (ret < 0) {
dev_err(&cldev->dev, "recv error: %d\n", ret);
return;
}
if (hdr->event.ack) {
handle_command_ack(ace, &event, ret);
return;
}
switch (hdr->event.event_type) {
case ACE_CMD_RESPONSE:
handle_command_response(ace, &event, ret);
break;
case ACE_FW_READY:
/*
* firmware ready notification sent to driver
* after HECI client connected with firmware.
*/
dev_dbg(&cldev->dev, "firmware ready\n");
break;
default:
break;
}
}
static int mei_ace_setup_dev_link(struct mei_ace *ace)
{
struct device *dev = &ace->cldev->dev;
uuid_le uuid = MEI_CSI_UUID;
struct device *csi_dev;
char name[64];
int ret;
snprintf(name, sizeof(name), "%s-%pUl", dev_name(dev->parent), &uuid);
csi_dev = device_find_child_by_name(dev->parent, name);
if (!csi_dev) {
ret = -EPROBE_DEFER;
goto err;
} else if (!dev_fwnode(csi_dev)) {
ret = -EPROBE_DEFER;
goto err_put;
}
/* setup link between mei_ace and mei_csi */
ace->csi_link = device_link_add(csi_dev, dev, DL_FLAG_PM_RUNTIME |
DL_FLAG_RPM_ACTIVE | DL_FLAG_STATELESS);
put_device(csi_dev);
if (!ace->csi_link) {
ret = -EINVAL;
dev_err(dev, "failed to link to %s\n", dev_name(csi_dev));
goto err;
}
ace->csi_dev = csi_dev;
return 0;
err_put:
put_device(csi_dev);
err:
return ret;
}
/* switch camera to host before probe sensor device */
static void mei_ace_post_probe_work(struct work_struct *work)
{
struct acpi_device *adev;
struct mei_ace *ace;
struct device *dev;
int ret;
ace = container_of(work, struct mei_ace, work);
dev = &ace->cldev->dev;
ret = ace_set_camera_owner(ace, ACE_CAMERA_HOST);
if (ret) {
dev_err(dev, "switch camera to host failed: %d\n", ret);
return;
}
adev = ACPI_COMPANION(dev->parent);
if (!adev)
return;
acpi_dev_clear_dependencies(adev);
}
static int mei_ace_probe(struct mei_cl_device *cldev,
const struct mei_cl_device_id *id)
{
struct device *dev = &cldev->dev;
struct mei_ace *ace;
int ret;
ace = devm_kzalloc(dev, sizeof(struct mei_ace), GFP_KERNEL);
if (!ace)
return -ENOMEM;
ace->cldev = cldev;
mutex_init(&ace->lock);
init_completion(&ace->cmd_completion);
INIT_WORK(&ace->work, mei_ace_post_probe_work);
mei_cldev_set_drvdata(cldev, ace);
ret = mei_cldev_enable(cldev);
if (ret < 0) {
dev_err(dev, "mei_cldev_enable failed: %d\n", ret);
goto destroy_mutex;
}
ret = mei_cldev_register_rx_cb(cldev, mei_ace_rx);
if (ret) {
dev_err(dev, "event cb registration failed: %d\n", ret);
goto err_disable;
}
ret = ace_get_firmware_id(ace);
if (ret) {
dev_err(dev, "get firmware id failed: %d\n", ret);
goto err_disable;
}
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
ret = mei_ace_setup_dev_link(ace);
if (ret)
goto disable_pm;
schedule_work(&ace->work);
return 0;
disable_pm:
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
err_disable:
mei_cldev_disable(cldev);
destroy_mutex:
mutex_destroy(&ace->lock);
return ret;
}
static void mei_ace_remove(struct mei_cl_device *cldev)
{
struct mei_ace *ace = mei_cldev_get_drvdata(cldev);
cancel_work_sync(&ace->work);
device_link_del(ace->csi_link);
pm_runtime_disable(&cldev->dev);
pm_runtime_set_suspended(&cldev->dev);
ace_set_camera_owner(ace, ACE_CAMERA_IVSC);
mei_cldev_disable(cldev);
mutex_destroy(&ace->lock);
}
static int __maybe_unused mei_ace_runtime_suspend(struct device *dev)
{
struct mei_ace *ace = dev_get_drvdata(dev);
return ace_set_camera_owner(ace, ACE_CAMERA_IVSC);
}
static int __maybe_unused mei_ace_runtime_resume(struct device *dev)
{
struct mei_ace *ace = dev_get_drvdata(dev);
return ace_set_camera_owner(ace, ACE_CAMERA_HOST);
}
static const struct dev_pm_ops mei_ace_pm_ops = {
SET_RUNTIME_PM_OPS(mei_ace_runtime_suspend,
mei_ace_runtime_resume, NULL)
};
#define MEI_ACE_UUID UUID_LE(0x5DB76CF6, 0x0A68, 0x4ED6, \
0x9B, 0x78, 0x03, 0x61, 0x63, 0x5E, 0x24, 0x47)
static const struct mei_cl_device_id mei_ace_tbl[] = {
{ .uuid = MEI_ACE_UUID, .version = MEI_CL_VERSION_ANY },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(mei, mei_ace_tbl);
static struct mei_cl_driver mei_ace_driver = {
.id_table = mei_ace_tbl,
.name = KBUILD_MODNAME,
.probe = mei_ace_probe,
.remove = mei_ace_remove,
.driver = {
.pm = &mei_ace_pm_ops,
},
};
module_mei_cl_driver(mei_ace_driver);
MODULE_AUTHOR("Wentong Wu");
MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
MODULE_DESCRIPTION("Device driver for IVSC ACE");
MODULE_LICENSE("GPL");
@@ -0,0 +1,791 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2023 Intel Corporation. All rights reserved.
* Intel Visual Sensing Controller CSI Linux driver
*/
/*
* To set ownership of CSI-2 link and to configure CSI-2 link, there
* are specific commands, which are sent via MEI protocol. The send
* command function uses "completion" as a synchronization mechanism.
* The response for command is received via a mei callback which wakes
* up the caller. There can be only one outstanding command at a time.
*/
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/mei_cl_bus.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/units.h>
#include <linux/uuid.h>
#include <linux/workqueue.h>
#include <media/ipu-bridge.h>
#include <media/ipu6-pci-table.h>
#include <media/v4l2-async.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-subdev.h>
#define MEI_CSI_ENTITY_NAME "Intel IVSC CSI"
/* the 5s used here is based on experiment */
#define CSI_CMD_TIMEOUT (5 * HZ)
/* to setup CSI-2 link an extra delay needed and determined experimentally */
#define CSI_FW_READY_DELAY_MS 100
/* link frequency unit is 100kHz */
#define CSI_LINK_FREQ(x) ((u32)(div_u64(x, 100 * HZ_PER_KHZ)))
/*
* identify the command id supported by firmware
* IPC, as well as the privacy notification id
* used when processing privacy event.
*/
enum csi_cmd_id {
/* used to set csi ownership */
CSI_SET_OWNER = 0,
/* used to configure CSI-2 link */
CSI_SET_CONF = 2,
/* privacy notification id used when privacy state changes */
CSI_PRIVACY_NOTIF = 6,
};
/* CSI-2 link ownership definition */
enum csi_link_owner {
CSI_LINK_IVSC,
CSI_LINK_HOST,
};
/* privacy status definition */
enum ivsc_privacy_status {
CSI_PRIVACY_OFF,
CSI_PRIVACY_ON,
CSI_PRIVACY_MAX,
};
enum csi_pads {
CSI_PAD_SINK,
CSI_PAD_SOURCE,
CSI_NUM_PADS
};
/* configuration of the CSI-2 link between host and IVSC */
struct csi_link_cfg {
/* number of data lanes used on the CSI-2 link */
u32 nr_of_lanes;
/* frequency of the CSI-2 link */
u32 link_freq;
/* for future use */
u32 rsvd[2];
} __packed;
/* CSI command structure */
struct csi_cmd {
u32 cmd_id;
union _cmd_param {
u32 param;
struct csi_link_cfg conf;
} param;
} __packed;
/* CSI notification structure */
struct csi_notif {
u32 cmd_id;
int status;
union _resp_cont {
u32 cont;
struct csi_link_cfg conf;
} cont;
} __packed;
struct mei_csi {
struct mei_cl_device *cldev;
/* command response */
struct csi_notif cmd_response;
/* used to wait for command response from firmware */
struct completion cmd_completion;
/* protect command download */
struct mutex lock;
struct v4l2_subdev subdev;
struct media_pad *remote;
struct v4l2_async_notifier notifier;
struct v4l2_ctrl_handler ctrl_handler;
struct v4l2_ctrl *freq_ctrl;
struct v4l2_ctrl *privacy_ctrl;
/* lock for v4l2 controls */
struct mutex ctrl_lock;
/* start streaming or not */
int streaming;
struct media_pad pads[CSI_NUM_PADS];
/* number of data lanes used on the CSI-2 link */
u32 nr_of_lanes;
/* frequency of the CSI-2 link */
u64 link_freq;
};
static const struct v4l2_mbus_framefmt mei_csi_format_mbus_default = {
.width = 1,
.height = 1,
.code = MEDIA_BUS_FMT_Y8_1X8,
.field = V4L2_FIELD_NONE,
};
static inline struct mei_csi *notifier_to_csi(struct v4l2_async_notifier *n)
{
return container_of(n, struct mei_csi, notifier);
}
static inline struct mei_csi *sd_to_csi(struct v4l2_subdev *sd)
{
return container_of(sd, struct mei_csi, subdev);
}
/* send a command to firmware and mutex must be held by caller */
static int mei_csi_send(struct mei_csi *csi, u8 *buf, size_t len)
{
struct csi_cmd *cmd = (struct csi_cmd *)buf;
int ret;
reinit_completion(&csi->cmd_completion);
ret = mei_cldev_send(csi->cldev, buf, len);
if (ret < 0)
goto out;
ret = wait_for_completion_killable_timeout(&csi->cmd_completion,
CSI_CMD_TIMEOUT);
if (ret < 0) {
goto out;
} else if (!ret) {
ret = -ETIMEDOUT;
goto out;
}
/* command response status */
ret = csi->cmd_response.status;
if (ret == -1) {
/* notify privacy on instead of reporting error */
ret = 0;
v4l2_ctrl_s_ctrl(csi->privacy_ctrl, 1);
} else if (ret) {
ret = -EINVAL;
goto out;
}
if (csi->cmd_response.cmd_id != cmd->cmd_id)
ret = -EINVAL;
out:
return ret;
}
/* set CSI-2 link ownership */
static int csi_set_link_owner(struct mei_csi *csi, enum csi_link_owner owner)
{
struct csi_cmd cmd = { 0 };
size_t cmd_size;
int ret;
cmd.cmd_id = CSI_SET_OWNER;
cmd.param.param = owner;
cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.param);
mutex_lock(&csi->lock);
ret = mei_csi_send(csi, (u8 *)&cmd, cmd_size);
mutex_unlock(&csi->lock);
return ret;
}
/* configure CSI-2 link between host and IVSC */
static int csi_set_link_cfg(struct mei_csi *csi)
{
struct csi_cmd cmd = { 0 };
size_t cmd_size;
int ret;
cmd.cmd_id = CSI_SET_CONF;
cmd.param.conf.nr_of_lanes = csi->nr_of_lanes;
cmd.param.conf.link_freq = CSI_LINK_FREQ(csi->link_freq);
cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.conf);
mutex_lock(&csi->lock);
ret = mei_csi_send(csi, (u8 *)&cmd, cmd_size);
/*
* wait configuration ready if download success. placing
* delay under mutex is to make sure current command flow
* completed before starting a possible new one.
*/
if (!ret)
msleep(CSI_FW_READY_DELAY_MS);
mutex_unlock(&csi->lock);
return ret;
}
/* callback for receive */
static void mei_csi_rx(struct mei_cl_device *cldev)
{
struct mei_csi *csi = mei_cldev_get_drvdata(cldev);
struct csi_notif notif = { 0 };
int ret;
ret = mei_cldev_recv(cldev, (u8 *)&notif, sizeof(notif));
if (ret < 0) {
dev_err(&cldev->dev, "recv error: %d\n", ret);
return;
}
switch (notif.cmd_id) {
case CSI_PRIVACY_NOTIF:
if (notif.cont.cont < CSI_PRIVACY_MAX)
v4l2_ctrl_s_ctrl(csi->privacy_ctrl,
notif.cont.cont == CSI_PRIVACY_ON);
break;
case CSI_SET_OWNER:
case CSI_SET_CONF:
memcpy(&csi->cmd_response, &notif, ret);
complete(&csi->cmd_completion);
break;
default:
break;
}
}
static int mei_csi_set_stream(struct v4l2_subdev *sd, int enable)
{
struct mei_csi *csi = sd_to_csi(sd);
struct v4l2_subdev *remote_sd =
media_entity_to_v4l2_subdev(csi->remote->entity);
s64 freq;
int ret;
if (enable && csi->streaming == 0) {
freq = v4l2_get_link_freq(csi->remote, 0, 0);
if (freq < 0) {
dev_err(&csi->cldev->dev,
"error %lld, invalid link_freq\n", freq);
ret = freq;
goto err;
}
csi->link_freq = freq;
/* switch CSI-2 link to host */
ret = csi_set_link_owner(csi, CSI_LINK_HOST);
if (ret < 0)
goto err;
/* configure CSI-2 link */
ret = csi_set_link_cfg(csi);
if (ret < 0)
goto err_switch;
ret = v4l2_subdev_call(remote_sd, video, s_stream, 1);
if (ret)
goto err_switch;
} else if (!enable && csi->streaming == 1) {
v4l2_subdev_call(remote_sd, video, s_stream, 0);
/* switch CSI-2 link to IVSC */
ret = csi_set_link_owner(csi, CSI_LINK_IVSC);
if (ret < 0)
dev_warn(&csi->cldev->dev,
"failed to switch CSI2 link: %d\n", ret);
}
csi->streaming = enable;
return 0;
err_switch:
csi_set_link_owner(csi, CSI_LINK_IVSC);
err:
return ret;
}
static int mei_csi_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state)
{
struct v4l2_mbus_framefmt *mbusformat;
unsigned int i;
for (i = 0; i < sd->entity.num_pads; i++) {
mbusformat = v4l2_subdev_state_get_format(sd_state, i);
*mbusformat = mei_csi_format_mbus_default;
}
return 0;
}
static int mei_csi_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *format)
{
struct v4l2_mbus_framefmt *source_fmt;
struct v4l2_mbus_framefmt *sink_fmt;
sink_fmt = v4l2_subdev_state_get_format(sd_state, CSI_PAD_SINK);
source_fmt = v4l2_subdev_state_get_format(sd_state, CSI_PAD_SOURCE);
if (format->pad) {
*source_fmt = *sink_fmt;
return 0;
}
v4l_bound_align_image(&format->format.width, 1, 65536, 0,
&format->format.height, 1, 65536, 0, 0);
switch (format->format.code) {
case MEDIA_BUS_FMT_RGB444_1X12:
case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE:
case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE:
case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE:
case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE:
case MEDIA_BUS_FMT_RGB565_1X16:
case MEDIA_BUS_FMT_BGR565_2X8_BE:
case MEDIA_BUS_FMT_BGR565_2X8_LE:
case MEDIA_BUS_FMT_RGB565_2X8_BE:
case MEDIA_BUS_FMT_RGB565_2X8_LE:
case MEDIA_BUS_FMT_RGB666_1X18:
case MEDIA_BUS_FMT_RBG888_1X24:
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
case MEDIA_BUS_FMT_BGR888_1X24:
case MEDIA_BUS_FMT_GBR888_1X24:
case MEDIA_BUS_FMT_RGB888_1X24:
case MEDIA_BUS_FMT_RGB888_2X12_BE:
case MEDIA_BUS_FMT_RGB888_2X12_LE:
case MEDIA_BUS_FMT_ARGB8888_1X32:
case MEDIA_BUS_FMT_RGB888_1X32_PADHI:
case MEDIA_BUS_FMT_RGB101010_1X30:
case MEDIA_BUS_FMT_RGB121212_1X36:
case MEDIA_BUS_FMT_RGB161616_1X48:
case MEDIA_BUS_FMT_Y8_1X8:
case MEDIA_BUS_FMT_UV8_1X8:
case MEDIA_BUS_FMT_UYVY8_1_5X8:
case MEDIA_BUS_FMT_VYUY8_1_5X8:
case MEDIA_BUS_FMT_YUYV8_1_5X8:
case MEDIA_BUS_FMT_YVYU8_1_5X8:
case MEDIA_BUS_FMT_UYVY8_2X8:
case MEDIA_BUS_FMT_VYUY8_2X8:
case MEDIA_BUS_FMT_YUYV8_2X8:
case MEDIA_BUS_FMT_YVYU8_2X8:
case MEDIA_BUS_FMT_Y10_1X10:
case MEDIA_BUS_FMT_UYVY10_2X10:
case MEDIA_BUS_FMT_VYUY10_2X10:
case MEDIA_BUS_FMT_YUYV10_2X10:
case MEDIA_BUS_FMT_YVYU10_2X10:
case MEDIA_BUS_FMT_Y12_1X12:
case MEDIA_BUS_FMT_UYVY12_2X12:
case MEDIA_BUS_FMT_VYUY12_2X12:
case MEDIA_BUS_FMT_YUYV12_2X12:
case MEDIA_BUS_FMT_YVYU12_2X12:
case MEDIA_BUS_FMT_UYVY8_1X16:
case MEDIA_BUS_FMT_VYUY8_1X16:
case MEDIA_BUS_FMT_YUYV8_1X16:
case MEDIA_BUS_FMT_YVYU8_1X16:
case MEDIA_BUS_FMT_YDYUYDYV8_1X16:
case MEDIA_BUS_FMT_UYVY10_1X20:
case MEDIA_BUS_FMT_VYUY10_1X20:
case MEDIA_BUS_FMT_YUYV10_1X20:
case MEDIA_BUS_FMT_YVYU10_1X20:
case MEDIA_BUS_FMT_VUY8_1X24:
case MEDIA_BUS_FMT_YUV8_1X24:
case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
case MEDIA_BUS_FMT_UYVY12_1X24:
case MEDIA_BUS_FMT_VYUY12_1X24:
case MEDIA_BUS_FMT_YUYV12_1X24:
case MEDIA_BUS_FMT_YVYU12_1X24:
case MEDIA_BUS_FMT_YUV10_1X30:
case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
case MEDIA_BUS_FMT_AYUV8_1X32:
case MEDIA_BUS_FMT_UYYVYY12_0_5X36:
case MEDIA_BUS_FMT_YUV12_1X36:
case MEDIA_BUS_FMT_YUV16_1X48:
case MEDIA_BUS_FMT_UYYVYY16_0_5X48:
case MEDIA_BUS_FMT_JPEG_1X8:
case MEDIA_BUS_FMT_AHSV8888_1X32:
case MEDIA_BUS_FMT_SBGGR8_1X8:
case MEDIA_BUS_FMT_SGBRG8_1X8:
case MEDIA_BUS_FMT_SGRBG8_1X8:
case MEDIA_BUS_FMT_SRGGB8_1X8:
case MEDIA_BUS_FMT_SBGGR10_1X10:
case MEDIA_BUS_FMT_SGBRG10_1X10:
case MEDIA_BUS_FMT_SGRBG10_1X10:
case MEDIA_BUS_FMT_SRGGB10_1X10:
case MEDIA_BUS_FMT_SBGGR12_1X12:
case MEDIA_BUS_FMT_SGBRG12_1X12:
case MEDIA_BUS_FMT_SGRBG12_1X12:
case MEDIA_BUS_FMT_SRGGB12_1X12:
case MEDIA_BUS_FMT_SBGGR14_1X14:
case MEDIA_BUS_FMT_SGBRG14_1X14:
case MEDIA_BUS_FMT_SGRBG14_1X14:
case MEDIA_BUS_FMT_SRGGB14_1X14:
case MEDIA_BUS_FMT_SBGGR16_1X16:
case MEDIA_BUS_FMT_SGBRG16_1X16:
case MEDIA_BUS_FMT_SGRBG16_1X16:
case MEDIA_BUS_FMT_SRGGB16_1X16:
break;
default:
format->format.code = MEDIA_BUS_FMT_Y8_1X8;
break;
}
if (format->format.field == V4L2_FIELD_ANY)
format->format.field = V4L2_FIELD_NONE;
*sink_fmt = format->format;
*source_fmt = *sink_fmt;
return 0;
}
static int mei_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_mbus_config *mbus_config)
{
struct mei_csi *csi = sd_to_csi(sd);
unsigned int i;
s64 freq;
mbus_config->type = V4L2_MBUS_CSI2_DPHY;
for (i = 0; i < V4L2_MBUS_CSI2_MAX_DATA_LANES; i++)
mbus_config->bus.mipi_csi2.data_lanes[i] = i + 1;
mbus_config->bus.mipi_csi2.num_data_lanes = csi->nr_of_lanes;
freq = v4l2_get_link_freq(csi->remote, 0, 0);
if (freq < 0) {
dev_err(&csi->cldev->dev,
"error %lld, invalid link_freq\n", freq);
return -EINVAL;
}
csi->link_freq = freq;
mbus_config->link_freq = freq;
return 0;
}
static const struct v4l2_subdev_video_ops mei_csi_video_ops = {
.s_stream = mei_csi_set_stream,
};
static const struct v4l2_subdev_pad_ops mei_csi_pad_ops = {
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = mei_csi_set_fmt,
.get_mbus_config = mei_csi_get_mbus_config,
};
static const struct v4l2_subdev_ops mei_csi_subdev_ops = {
.video = &mei_csi_video_ops,
.pad = &mei_csi_pad_ops,
};
static const struct v4l2_subdev_internal_ops mei_csi_internal_ops = {
.init_state = mei_csi_init_state,
};
static const struct media_entity_operations mei_csi_entity_ops = {
.link_validate = v4l2_subdev_link_validate,
};
static int mei_csi_notify_bound(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *subdev,
struct v4l2_async_connection *asd)
{
struct mei_csi *csi = notifier_to_csi(notifier);
int pad;
pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
MEDIA_PAD_FL_SOURCE);
if (pad < 0)
return pad;
csi->remote = &subdev->entity.pads[pad];
return media_create_pad_link(&subdev->entity, pad,
&csi->subdev.entity, CSI_PAD_SINK,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
}
static void mei_csi_notify_unbind(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *subdev,
struct v4l2_async_connection *asd)
{
struct mei_csi *csi = notifier_to_csi(notifier);
csi->remote = NULL;
}
static const struct v4l2_async_notifier_operations mei_csi_notify_ops = {
.bound = mei_csi_notify_bound,
.unbind = mei_csi_notify_unbind,
};
static int mei_csi_init_controls(struct mei_csi *csi)
{
int ret;
mutex_init(&csi->ctrl_lock);
ret = v4l2_ctrl_handler_init(&csi->ctrl_handler, 1);
if (ret)
return ret;
csi->ctrl_handler.lock = &csi->ctrl_lock;
csi->privacy_ctrl = v4l2_ctrl_new_std(&csi->ctrl_handler, NULL,
V4L2_CID_PRIVACY, 0, 1, 1, 0);
if (csi->privacy_ctrl)
csi->privacy_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
if (csi->ctrl_handler.error)
return csi->ctrl_handler.error;
csi->subdev.ctrl_handler = &csi->ctrl_handler;
return 0;
}
static int mei_csi_parse_firmware(struct mei_csi *csi)
{
struct v4l2_fwnode_endpoint v4l2_ep = {
.bus_type = V4L2_MBUS_CSI2_DPHY,
};
struct device *dev = &csi->cldev->dev;
struct v4l2_async_connection *asd;
struct fwnode_handle *sink_ep, *source_ep;
int ret;
sink_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
if (!sink_ep) {
dev_err(dev, "can't obtain sink endpoint\n");
return -EINVAL;
}
v4l2_async_subdev_nf_init(&csi->notifier, &csi->subdev);
csi->notifier.ops = &mei_csi_notify_ops;
ret = v4l2_fwnode_endpoint_parse(sink_ep, &v4l2_ep);
if (ret) {
dev_err(dev, "could not parse v4l2 sink endpoint\n");
goto out_nf_cleanup;
}
csi->nr_of_lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
source_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 1, 0, 0);
if (!source_ep) {
ret = -ENOTCONN;
dev_err(dev, "can't obtain source endpoint\n");
goto out_nf_cleanup;
}
ret = v4l2_fwnode_endpoint_parse(source_ep, &v4l2_ep);
fwnode_handle_put(source_ep);
if (ret) {
dev_err(dev, "could not parse v4l2 source endpoint\n");
goto out_nf_cleanup;
}
if (csi->nr_of_lanes != v4l2_ep.bus.mipi_csi2.num_data_lanes) {
ret = -EINVAL;
dev_err(dev,
"the number of lanes does not match (%u vs. %u)\n",
csi->nr_of_lanes, v4l2_ep.bus.mipi_csi2.num_data_lanes);
goto out_nf_cleanup;
}
asd = v4l2_async_nf_add_fwnode_remote(&csi->notifier, sink_ep,
struct v4l2_async_connection);
if (IS_ERR(asd)) {
ret = PTR_ERR(asd);
goto out_nf_cleanup;
}
ret = v4l2_async_nf_register(&csi->notifier);
if (ret)
goto out_nf_cleanup;
fwnode_handle_put(sink_ep);
return 0;
out_nf_cleanup:
v4l2_async_nf_cleanup(&csi->notifier);
fwnode_handle_put(sink_ep);
return ret;
}
static int mei_csi_probe(struct mei_cl_device *cldev,
const struct mei_cl_device_id *id)
{
struct device *dev = &cldev->dev;
struct pci_dev *ipu;
struct mei_csi *csi;
unsigned int i;
int ret;
for (i = 0, ipu = NULL; !ipu && ipu6_pci_tbl[i].vendor; i++)
ipu = pci_get_device(ipu6_pci_tbl[i].vendor,
ipu6_pci_tbl[i].device, NULL);
if (!ipu)
return -ENODEV;
ret = ipu_bridge_init(&ipu->dev, ipu_bridge_parse_ssdb);
put_device(&ipu->dev);
if (ret < 0)
return ret;
if (!dev_fwnode(dev)) {
dev_err(dev, "mei-csi probed without device fwnode!\n");
return -ENXIO;
}
csi = devm_kzalloc(dev, sizeof(struct mei_csi), GFP_KERNEL);
if (!csi)
return -ENOMEM;
csi->cldev = cldev;
mutex_init(&csi->lock);
init_completion(&csi->cmd_completion);
mei_cldev_set_drvdata(cldev, csi);
ret = mei_cldev_enable(cldev);
if (ret < 0) {
dev_err(dev, "mei_cldev_enable failed: %d\n", ret);
goto destroy_mutex;
}
ret = mei_cldev_register_rx_cb(cldev, mei_csi_rx);
if (ret) {
dev_err(dev, "event cb registration failed: %d\n", ret);
goto err_disable;
}
ret = mei_csi_parse_firmware(csi);
if (ret)
goto err_disable;
csi->subdev.dev = &cldev->dev;
csi->subdev.state_lock = &csi->lock;
v4l2_subdev_init(&csi->subdev, &mei_csi_subdev_ops);
csi->subdev.internal_ops = &mei_csi_internal_ops;
v4l2_set_subdevdata(&csi->subdev, csi);
csi->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE |
V4L2_SUBDEV_FL_HAS_EVENTS;
csi->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
csi->subdev.entity.ops = &mei_csi_entity_ops;
snprintf(csi->subdev.name, sizeof(csi->subdev.name),
MEI_CSI_ENTITY_NAME);
ret = mei_csi_init_controls(csi);
if (ret)
goto err_ctrl_handler;
csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
ret = media_entity_pads_init(&csi->subdev.entity, CSI_NUM_PADS,
csi->pads);
if (ret)
goto err_ctrl_handler;
ret = v4l2_subdev_init_finalize(&csi->subdev);
if (ret < 0)
goto err_entity;
ret = v4l2_async_register_subdev(&csi->subdev);
if (ret < 0)
goto err_subdev;
pm_runtime_enable(&cldev->dev);
return 0;
err_subdev:
v4l2_subdev_cleanup(&csi->subdev);
err_entity:
media_entity_cleanup(&csi->subdev.entity);
err_ctrl_handler:
v4l2_ctrl_handler_free(&csi->ctrl_handler);
mutex_destroy(&csi->ctrl_lock);
v4l2_async_nf_unregister(&csi->notifier);
v4l2_async_nf_cleanup(&csi->notifier);
err_disable:
mei_cldev_disable(cldev);
destroy_mutex:
mutex_destroy(&csi->lock);
return ret;
}
static void mei_csi_remove(struct mei_cl_device *cldev)
{
struct mei_csi *csi = mei_cldev_get_drvdata(cldev);
v4l2_async_nf_unregister(&csi->notifier);
v4l2_async_nf_cleanup(&csi->notifier);
v4l2_ctrl_handler_free(&csi->ctrl_handler);
mutex_destroy(&csi->ctrl_lock);
v4l2_async_unregister_subdev(&csi->subdev);
v4l2_subdev_cleanup(&csi->subdev);
media_entity_cleanup(&csi->subdev.entity);
pm_runtime_disable(&cldev->dev);
mei_cldev_disable(cldev);
mutex_destroy(&csi->lock);
}
#define MEI_CSI_UUID UUID_LE(0x92335FCF, 0x3203, 0x4472, \
0xAF, 0x93, 0x7b, 0x44, 0x53, 0xAC, 0x29, 0xDA)
static const struct mei_cl_device_id mei_csi_tbl[] = {
{ .uuid = MEI_CSI_UUID, .version = MEI_CL_VERSION_ANY },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(mei, mei_csi_tbl);
static struct mei_cl_driver mei_csi_driver = {
.id_table = mei_csi_tbl,
.name = KBUILD_MODNAME,
.probe = mei_csi_probe,
.remove = mei_csi_remove,
};
module_mei_cl_driver(mei_csi_driver);
MODULE_IMPORT_NS("INTEL_IPU_BRIDGE");
MODULE_AUTHOR("Wentong Wu");
MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
MODULE_DESCRIPTION("Device driver for IVSC CSI");
MODULE_LICENSE("GPL");