media: i2c: imx500: input tensor injection

Input tensor injection is a debug feature that allows a user-controlled
input to be passed directly to IMX500's inference engine (bypassing the
in-built ISP).

Three new custom controls are added to ENABLE_INJECTION before streaming
begins, to provide appropriate input tensors via an INPUT_TENSOR_FD, and
to provide notification of DNN results in the sensor output via
INJECTION_CMP_FRM.

Signed-off-by: Richard Oliver <richard.oliver@raspberrypi.com>
This commit is contained in:
Richard Oliver
2025-06-16 14:01:52 +01:00
parent 1d80b65a4e
commit a0169214c1

View File

@@ -208,6 +208,25 @@
#define IMX500_REG_MAIN_FW_VERSION CCI_REG32(0xD07C)
/* Input tensor injection */
#define IMX500_REG_DD_DAT_INJECTION_CHKSUM CCI_REG32(0xD0D4)
#define IMX500_REG_DD_DAT_INJECTION_SPI_CMP_FRM CCI_REG8(0xD0DA)
#define IMX500_REG_DD_DAT_INJECTION_HNDSK CCI_REG8(0xD0DB)
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_WAIT 1
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_COMP 2
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_READY_WAIT 3
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_READY_COMP 4
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANSFERRING 5
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP_WAIT 6
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP 7
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_ERROR 8
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_ERROR_WAIT_NOTIFY 9
#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP_WAIT_CHKSUM 10
#define IMX500_REG_STRM_MODE_SEL CCI_REG8(0xD100)
#define IMX500_REG_LEV_PL_MDET_OUT CCI_REG8(0xD641)
/* Colour balance controls */
#define IMX500_REG_COLOUR_BALANCE_R CCI_REG16(0xd804)
#define IMX500_REG_COLOUR_BALANCE_GR CCI_REG16(0xd806)
@@ -240,6 +259,9 @@ enum pad_types { IMAGE_PAD, METADATA_PAD, NUM_PADS };
#define V4L2_CID_USER_IMX500_INFERENCE_WINDOW (V4L2_CID_USER_IMX500_BASE + 0)
#define V4L2_CID_USER_IMX500_NETWORK_FW_FD (V4L2_CID_USER_IMX500_BASE + 1)
#define V4L2_CID_USER_GET_IMX500_DEVICE_ID (V4L2_CID_USER_IMX500_BASE + 2)
#define V4L2_CID_USER_IMX500_ENABLE_INJECTION (V4L2_CID_USER_IMX500_BASE + 3)
#define V4L2_CID_USER_IMX500_INPUT_TENSOR_FD (V4L2_CID_USER_IMX500_BASE + 4)
#define V4L2_CID_USER_IMX500_INJECTION_CMP_FRM (V4L2_CID_USER_IMX500_BASE + 5)
#define ONE_MIB (1024 * 1024)
@@ -1367,6 +1389,9 @@ struct imx500 {
struct v4l2_ctrl *hblank;
struct v4l2_ctrl *network_fw_ctrl;
struct v4l2_ctrl *device_id;
struct v4l2_ctrl *enable_injection;
struct v4l2_ctrl *input_tensor_fd;
struct v4l2_ctrl *injection_cmp_frm;
struct v4l2_rect inference_window;
@@ -1388,6 +1413,12 @@ struct imx500 {
bool loader_and_main_written;
bool network_written;
/* Tensor injection enabled */
bool tensor_injection;
/* Current injection comparison frame value */
u8 current_injection_cmp_frm;
/* Current long exposure factor in use. Set through V4L2_CID_VBLANK */
unsigned int long_exp_shift;
@@ -1903,6 +1934,180 @@ static void imx500_clear_fw_network(struct imx500 *imx500)
v4l2_ctrl_activate(imx500->device_id, false);
}
static int __must_check imx500_spi_write(struct imx500 *state, const u8 *data,
size_t size)
{
if (size % 4 || size > ONE_MIB)
return -EINVAL;
if (!state->spi_device)
return -ENODEV;
return spi_write(state->spi_device, data, size);
}
static int imx500_injection_wait_transfer_complete(struct imx500 *imx500)
{
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
struct cci_reg_sequence hndsk_reg = {
IMX500_REG_DD_DAT_INJECTION_HNDSK,
IMX500_DD_DAT_INJECTION_HNDSK_TRANSFERRING
};
int ret;
/* Continue polling until we reach the final TRANS_COMP state */
while (hndsk_reg.val != IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP) {
ret = imx500_poll_status_reg(imx500, &hndsk_reg, 5);
if (ret) {
dev_err(&client->dev,
"Handshake register did not update from state %llu\n",
hndsk_reg.val);
return ret;
}
/* Check if we've reached the final completion state */
if (hndsk_reg.val == IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP) {
break;
} else if (hndsk_reg.val == IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP_WAIT ||
hndsk_reg.val == IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP_WAIT_CHKSUM) {
continue;
} else if (hndsk_reg.val ==
IMX500_DD_DAT_INJECTION_HNDSK_TRANS_ERROR) {
dev_err(&client->dev,
"Handshake register indicates transfer error\n");
return -EREMOTEIO;
} else if (hndsk_reg.val ==
IMX500_DD_DAT_INJECTION_HNDSK_TRANS_ERROR_WAIT_NOTIFY) {
dev_err(&client->dev,
"Handshake register indicates transfer error (waiting for notification)\n");
return -EREMOTEIO;
}
/* Unexpected state */
dev_err(&client->dev,
"Handshake register in unexpected state %llu\n",
hndsk_reg.val);
return -EREMOTEIO;
}
return 0;
}
static u32 imx500_calc_injection_checksum(const u32 *data, size_t size)
{
u32 checksum = 0;
while (size--) {
u32 val = *data++;
checksum += val;
}
return checksum;
}
static int imx500_inject_input_tensor(struct imx500 *imx500, const u32 *data,
size_t size)
{
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
int ret;
u64 cmp_frm;
u8 exp_frm;
u32 checksum = imx500_calc_injection_checksum(data, size);
ret = cci_write(imx500->regmap, IMX500_REG_DD_DAT_INJECTION_CHKSUM,
checksum, NULL);
if (ret) {
dev_err(&client->dev, "Failed to write checksum\n");
return ret;
}
ret = cci_write(imx500->regmap, IMX500_REG_STRM_MODE_SEL, 0, NULL);
if (ret) {
dev_err(&client->dev, "Failed to stop DNN processing\n");
return ret;
}
/* The handshake register (IMX500_REG_DD_DAT_INJECTION_HNDSK) is used bidirectionally:
* - We write a state to request a transition (e.g. READY_WAIT)
* - The sensor responds by updating the register with the new state (e.g. READY_COMP)
* This allows us to coordinate state transitions between the host and sensor.
*/
struct cci_reg_sequence hndsk_reg = {
IMX500_REG_DD_DAT_INJECTION_HNDSK,
IMX500_DD_DAT_INJECTION_HNDSK_TRANS_READY_WAIT
};
ret = cci_multi_reg_write(imx500->regmap, &hndsk_reg, 1, NULL);
if (ret) {
dev_err(&client->dev, "Failed to inject state transition\n");
return ret;
}
ret = imx500_poll_status_reg(imx500, &hndsk_reg, 5);
if (ret) {
dev_err(&client->dev,
"Handshake register did not update after READY_WAIT request\n");
return ret;
}
if (hndsk_reg.val != IMX500_DD_DAT_INJECTION_HNDSK_TRANS_READY_COMP) {
dev_err(&client->dev,
"Failed to transition to READY_COMP state: %llu\n",
hndsk_reg.val);
return -EREMOTEIO;
}
ret = cci_write(imx500->regmap, IMX500_REG_DD_DAT_INJECTION_HNDSK,
IMX500_DD_DAT_INJECTION_HNDSK_TRANSFERRING, NULL);
if (ret) {
dev_err(&client->dev,
"Failed to request transition to TRANSFERRING state\n");
return ret;
}
for (size_t i = 0; i < size; i++)
*(u32 *)(data + i) = ___constant_swab32(data[i]);
if (imx500->led_gpio)
gpiod_set_value_cansleep(imx500->led_gpio, 1);
ret = imx500_spi_write(imx500, (const u8 *)data, size * 4);
if (ret) {
dev_err(&client->dev, "SPI transfer of input tensor failed\n");
return ret;
}
if (imx500->led_gpio)
gpiod_set_value_cansleep(imx500->led_gpio, 0);
ret = imx500_injection_wait_transfer_complete(imx500);
if (ret) {
dev_err(&client->dev, "SPI transfer of input tensor failed\n");
return ret;
}
ret = cci_read(imx500->regmap, IMX500_REG_DD_DAT_INJECTION_SPI_CMP_FRM,
&cmp_frm, NULL);
if (ret) {
dev_err(&client->dev,
"Failed to read injection frame comparison register\n");
return ret;
}
exp_frm = cmp_frm;
exp_frm += 2 << (exp_frm > 254);
imx500->current_injection_cmp_frm = exp_frm;
dev_info(&client->dev, "injection_cmp_frm set to %u\n", exp_frm);
ret = cci_write(imx500->regmap, IMX500_REG_STRM_MODE_SEL, 4, NULL);
if (ret)
dev_err(&client->dev, "Failed to enable DNN processing\n");
return ret;
}
static int imx500_set_ctrl(struct v4l2_ctrl *ctrl)
{
struct imx500 *imx500 =
@@ -1910,6 +2115,9 @@ static int imx500_set_ctrl(struct v4l2_ctrl *ctrl)
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
int ret = 0;
const u8 *tensor_data;
size_t tensor_data_size;
if (ctrl->id == V4L2_CID_USER_IMX500_NETWORK_FW_FD) {
/* Reset state of the control. */
if (ctrl->val < 0) {
@@ -1957,6 +2165,56 @@ static int imx500_set_ctrl(struct v4l2_ctrl *ctrl)
return 0;
}
if (ctrl->id == V4L2_CID_USER_IMX500_INPUT_TENSOR_FD) {
if (!imx500->streaming || !imx500->fw_network ||
!imx500->tensor_injection) {
if (ctrl->val == -1)
return 0;
dev_err(&client->dev,
"Input tensor injection requires streaming, network and injection to be enabled\n");
return -EINVAL;
}
ret = kernel_read_file_from_fd(ctrl->val, 0,
(void **)&tensor_data, INT_MAX,
&tensor_data_size, 1);
if (ret < 0) {
dev_err(&client->dev,
"%s failed to read tensor data: %d\n", __func__,
ret);
return ret;
}
if (tensor_data_size % 4 != 0) {
dev_err(&client->dev,
"%s tensor data size must be multiple of 4, got %zu\n",
__func__, tensor_data_size);
vfree(tensor_data);
return -EINVAL;
}
ret = imx500_inject_input_tensor(imx500, (u32 *)tensor_data,
tensor_data_size / 4);
vfree(tensor_data);
ctrl->val = -1;
return ret;
}
if (ctrl->id == V4L2_CID_USER_IMX500_ENABLE_INJECTION) {
/*
* tensor_injection is latched-enabled as no deinitialisation is possible
* without reset
*/
if (ctrl->val && !imx500->tensor_injection)
imx500->tensor_injection = ctrl->val;
return 0;
}
/*
* The VBLANK control may change the limits of usable exposure, so check
* and adjust if necessary.
@@ -2036,6 +2294,12 @@ static int imx500_get_ctrl(struct v4l2_ctrl *ctrl)
ret = imx500_get_device_id(imx500, device_id);
memcpy(ctrl->p_new.p_u32, device_id, sizeof(device_id));
break;
case V4L2_CID_USER_IMX500_INJECTION_CMP_FRM:
ctrl->val = imx500->current_injection_cmp_frm;
dev_dbg(&client->dev, "Injection comparison frame: %u\n",
ctrl->val);
ret = 0;
break;
default:
dev_info(&client->dev, "ctrl(id:0x%x,val:0x%x) is not handled\n",
ctrl->id, ctrl->val);
@@ -2306,16 +2570,55 @@ static int imx500_get_selection(struct v4l2_subdev *sd,
return -EINVAL;
}
static int __must_check imx500_spi_write(struct imx500 *state, const u8 *data,
size_t size)
static int imx500_data_injection_init(struct imx500 *imx500)
{
if (size % 4 || size > ONE_MIB)
return -EINVAL;
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
if (!state->spi_device)
return -ENODEV;
int ret;
return spi_write(state->spi_device, data, size);
ret = cci_write(imx500->regmap, IMX500_REG_LEV_PL_MDET_OUT, 1, NULL);
if (ret) {
dev_err(&client->dev,
"Failed to write LEV_PL_MDET_OUT register\n");
return ret;
}
ret = cci_write(imx500->regmap, IMX500_REG_DD_DAT_INJECTION_HNDSK,
IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_WAIT, NULL);
if (ret) {
dev_err(&client->dev, "Failed to write HNDSK register\n");
return ret;
}
return 0;
}
static int imx500_data_injection_init_wait(struct imx500 *imx500)
{
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
int ret;
struct cci_reg_sequence hndsk_reg = {
IMX500_REG_DD_DAT_INJECTION_HNDSK,
IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_WAIT
};
ret = imx500_poll_status_reg(imx500, &hndsk_reg, 40);
if (ret) {
dev_err(&client->dev,
"Handshake register did not update after INIT_WAIT request\n");
return ret;
}
if (hndsk_reg.val != IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_COMP) {
dev_err(&client->dev,
"Failed to transition to INIT_COMP state: %llu\n",
hndsk_reg.val);
return -EREMOTEIO;
}
return 0;
}
/* Moves the IMX500 internal state machine between states or updates.
@@ -2626,6 +2929,15 @@ static int imx500_start_streaming(struct imx500 *imx500)
}
if (imx500->fw_network && !imx500->network_written) {
if (imx500->tensor_injection) {
ret = imx500_data_injection_init(imx500);
if (ret) {
dev_err(&client->dev,
"%s failed to initialize data injection\n",
__func__);
return ret;
}
}
ret = imx500_transition_to_network(imx500);
if (ret) {
dev_err(&client->dev,
@@ -2638,7 +2950,8 @@ static int imx500_start_streaming(struct imx500 *imx500)
/* Enable DNN */
if (imx500->fw_network) {
ret = cci_write(imx500->regmap, CCI_REG8(0xD100), 4, NULL);
ret = cci_write(imx500->regmap, IMX500_REG_STRM_MODE_SEL, 4,
NULL);
if (ret) {
dev_err(&client->dev, "%s failed to enable DNN\n",
__func__);
@@ -2673,76 +2986,26 @@ static int imx500_start_streaming(struct imx500 *imx500)
if (ret)
goto err_runtime_put;
if (imx500->tensor_injection) {
ret = imx500_data_injection_init_wait(imx500);
if (ret) {
dev_err(&client->dev,
"%s failed to wait for data injection init\n",
__func__);
goto err_runtime_put;
}
}
return 0;
err_runtime_put:
__v4l2_ctrl_grab(imx500->enable_injection, false);
pm_runtime_mark_last_busy(&client->dev);
pm_runtime_put_autosuspend(&client->dev);
return ret;
}
/* Stop streaming */
static void imx500_stop_streaming(struct imx500 *imx500)
{
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
int ret;
/* set stream off register */
ret = cci_write(imx500->regmap, IMX500_REG_MODE_SELECT,
IMX500_MODE_STANDBY, NULL);
if (ret)
dev_err(&client->dev, "%s failed to set stream\n", __func__);
/* Disable DNN */
ret = cci_write(imx500->regmap, CCI_REG8(0xD100), 0, NULL);
if (ret)
dev_err(&client->dev, "%s failed to disable DNN\n", __func__);
pm_runtime_mark_last_busy(&client->dev);
pm_runtime_put_autosuspend(&client->dev);
}
static int imx500_set_stream(struct v4l2_subdev *sd, int enable)
{
struct imx500 *imx500 = to_imx500(sd);
int ret = 0;
mutex_lock(&imx500->mutex);
if (imx500->streaming == enable) {
mutex_unlock(&imx500->mutex);
return 0;
}
if (enable) {
/*
* Apply default & customized values
* and then start streaming.
*/
ret = imx500_start_streaming(imx500);
if (ret)
goto err_start_streaming;
} else {
imx500_stop_streaming(imx500);
}
imx500->streaming = enable;
/* vflip and hflip cannot change during streaming */
__v4l2_ctrl_grab(imx500->vflip, enable);
__v4l2_ctrl_grab(imx500->hflip, enable);
__v4l2_ctrl_grab(imx500->network_fw_ctrl, enable);
mutex_unlock(&imx500->mutex);
return ret;
err_start_streaming:
mutex_unlock(&imx500->mutex);
return ret;
}
/* Power/clock management functions */
static int imx500_power_on(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
@@ -2846,6 +3109,7 @@ static int imx500_power_off(struct device *dev)
/* Force reprogramming of the common registers when powered up again. */
imx500->fsm_state = IMX500_STATE_RESET;
imx500->tensor_injection = false;
imx500->common_regs_written = false;
imx500->loader_and_main_written = false;
imx500_clear_fw_network(imx500);
@@ -2853,6 +3117,102 @@ static int imx500_power_off(struct device *dev)
return 0;
}
/* Stop streaming */
static void imx500_stop_streaming(struct imx500 *imx500)
{
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
int ret;
/* set stream off register */
ret = cci_write(imx500->regmap, IMX500_REG_MODE_SELECT,
IMX500_MODE_STANDBY, NULL);
if (ret)
dev_err(&client->dev, "%s failed to set stream\n", __func__);
/* Disable DNN */
ret = cci_write(imx500->regmap, IMX500_REG_STRM_MODE_SEL, 0, NULL);
if (ret)
dev_err(&client->dev, "%s failed to disable DNN\n", __func__);
pm_runtime_mark_last_busy(&client->dev);
if (imx500->tensor_injection) {
/*
* The tensor_injection state is not persistent.
* It must be reset on streamoff to avoid breaking
* non-injection-aware applications that may use the
* device next. We reset both the internal flag and the
* control's value.
*/
imx500->tensor_injection = false;
__v4l2_ctrl_s_ctrl(imx500->enable_injection, 0);
/*
* A full power cycle is required to reset the sensor's SPI
* block. Disabling runtime PM allows us to do this safely.
*/
pm_runtime_disable(&client->dev);
imx500_power_off(&client->dev);
pm_runtime_set_suspended(&client->dev);
/*
* If the usage count is > 1, another user (e.g. sysfs) holds
* a reference, so the device must be powered on again.
* Otherwise, the upcoming 'put' will suspend, so we can
* leave it off.
*/
if (atomic_read(&client->dev.power.usage_count) > 1) {
usleep_range(5000, 10000);
imx500_power_on(&client->dev);
pm_runtime_set_active(&client->dev);
}
pm_runtime_enable(&client->dev);
}
pm_runtime_put_autosuspend(&client->dev);
}
static int imx500_set_stream(struct v4l2_subdev *sd, int enable)
{
struct imx500 *imx500 = to_imx500(sd);
int ret = 0;
mutex_lock(&imx500->mutex);
if (imx500->streaming == enable) {
mutex_unlock(&imx500->mutex);
return 0;
}
if (enable) {
/*
* Apply default & customized values
* and then start streaming.
*/
ret = imx500_start_streaming(imx500);
if (ret)
goto err_start_streaming;
} else {
imx500_stop_streaming(imx500);
}
imx500->streaming = enable;
/* vflip and hflip cannot change during streaming */
__v4l2_ctrl_grab(imx500->vflip, enable);
__v4l2_ctrl_grab(imx500->hflip, enable);
__v4l2_ctrl_grab(imx500->network_fw_ctrl, enable);
mutex_unlock(&imx500->mutex);
return ret;
err_start_streaming:
mutex_unlock(&imx500->mutex);
return ret;
}
static int imx500_get_regulators(struct imx500 *imx500)
{
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
@@ -2964,6 +3324,44 @@ static const struct v4l2_ctrl_config cam_get_device_id = {
.def = 0,
};
/* Custom control for enable injection */
static const struct v4l2_ctrl_config enable_injection_ctrl = {
.name = "IMX500 Enable Input Tensor Injection",
.id = V4L2_CID_USER_IMX500_ENABLE_INJECTION,
.ops = &imx500_ctrl_ops,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.min = 0,
.max = 1,
.step = 1,
.def = 0,
};
/* Custom control for input tensor file FD */
static const struct v4l2_ctrl_config input_tensor_fd = {
.name = "IMX500 Input Tensor File FD",
.id = V4L2_CID_USER_IMX500_INPUT_TENSOR_FD,
.ops = &imx500_ctrl_ops,
.type = V4L2_CTRL_TYPE_INTEGER,
.flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_WRITE_ONLY,
.min = -1,
.max = S32_MAX,
.step = 1,
.def = -1,
};
/* Custom control from tensor injection comparison frame */
static const struct v4l2_ctrl_config injection_cmp_frm = {
.name = "IMX500 Injection Comparison Frame",
.id = V4L2_CID_USER_IMX500_INJECTION_CMP_FRM,
.ops = &imx500_ctrl_ops,
.type = V4L2_CTRL_TYPE_INTEGER,
.flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE,
.min = 1,
.max = 254,
.step = 1,
.def = 42,
};
/* Initialize control handlers */
static int imx500_init_controls(struct imx500 *imx500)
{
@@ -3029,6 +3427,12 @@ static int imx500_init_controls(struct imx500 *imx500)
v4l2_ctrl_new_custom(ctrl_hdlr, &network_fw_fd, NULL);
imx500->device_id =
v4l2_ctrl_new_custom(ctrl_hdlr, &cam_get_device_id, NULL);
imx500->enable_injection =
v4l2_ctrl_new_custom(ctrl_hdlr, &enable_injection_ctrl, NULL);
imx500->input_tensor_fd =
v4l2_ctrl_new_custom(ctrl_hdlr, &input_tensor_fd, NULL);
imx500->injection_cmp_frm =
v4l2_ctrl_new_custom(ctrl_hdlr, &injection_cmp_frm, NULL);
if (ctrl_hdlr->error) {
ret = ctrl_hdlr->error;
@@ -3203,6 +3607,9 @@ static int imx500_probe(struct i2c_client *client)
/* Initialize default format */
imx500_set_default_format(imx500);
/* Initialize injection comparison frame to default value */
imx500->current_injection_cmp_frm = 0;
/* This needs the pm runtime to be registered. */
ret = imx500_init_controls(imx500);
if (ret)