mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 01:49:46 +00:00
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:
@@ -208,6 +208,25 @@
|
|||||||
|
|
||||||
#define IMX500_REG_MAIN_FW_VERSION CCI_REG32(0xD07C)
|
#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 */
|
/* Colour balance controls */
|
||||||
#define IMX500_REG_COLOUR_BALANCE_R CCI_REG16(0xd804)
|
#define IMX500_REG_COLOUR_BALANCE_R CCI_REG16(0xd804)
|
||||||
#define IMX500_REG_COLOUR_BALANCE_GR CCI_REG16(0xd806)
|
#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_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_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_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)
|
#define ONE_MIB (1024 * 1024)
|
||||||
|
|
||||||
@@ -1367,6 +1389,9 @@ struct imx500 {
|
|||||||
struct v4l2_ctrl *hblank;
|
struct v4l2_ctrl *hblank;
|
||||||
struct v4l2_ctrl *network_fw_ctrl;
|
struct v4l2_ctrl *network_fw_ctrl;
|
||||||
struct v4l2_ctrl *device_id;
|
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;
|
struct v4l2_rect inference_window;
|
||||||
|
|
||||||
@@ -1388,6 +1413,12 @@ struct imx500 {
|
|||||||
bool loader_and_main_written;
|
bool loader_and_main_written;
|
||||||
bool network_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 */
|
/* Current long exposure factor in use. Set through V4L2_CID_VBLANK */
|
||||||
unsigned int long_exp_shift;
|
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);
|
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)
|
static int imx500_set_ctrl(struct v4l2_ctrl *ctrl)
|
||||||
{
|
{
|
||||||
struct imx500 *imx500 =
|
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);
|
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
const u8 *tensor_data;
|
||||||
|
size_t tensor_data_size;
|
||||||
|
|
||||||
if (ctrl->id == V4L2_CID_USER_IMX500_NETWORK_FW_FD) {
|
if (ctrl->id == V4L2_CID_USER_IMX500_NETWORK_FW_FD) {
|
||||||
/* Reset state of the control. */
|
/* Reset state of the control. */
|
||||||
if (ctrl->val < 0) {
|
if (ctrl->val < 0) {
|
||||||
@@ -1957,6 +2165,56 @@ static int imx500_set_ctrl(struct v4l2_ctrl *ctrl)
|
|||||||
return 0;
|
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
|
* The VBLANK control may change the limits of usable exposure, so check
|
||||||
* and adjust if necessary.
|
* 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);
|
ret = imx500_get_device_id(imx500, device_id);
|
||||||
memcpy(ctrl->p_new.p_u32, device_id, sizeof(device_id));
|
memcpy(ctrl->p_new.p_u32, device_id, sizeof(device_id));
|
||||||
break;
|
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:
|
default:
|
||||||
dev_info(&client->dev, "ctrl(id:0x%x,val:0x%x) is not handled\n",
|
dev_info(&client->dev, "ctrl(id:0x%x,val:0x%x) is not handled\n",
|
||||||
ctrl->id, ctrl->val);
|
ctrl->id, ctrl->val);
|
||||||
@@ -2306,16 +2570,55 @@ static int imx500_get_selection(struct v4l2_subdev *sd,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __must_check imx500_spi_write(struct imx500 *state, const u8 *data,
|
static int imx500_data_injection_init(struct imx500 *imx500)
|
||||||
size_t size)
|
|
||||||
{
|
{
|
||||||
if (size % 4 || size > ONE_MIB)
|
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
if (!state->spi_device)
|
int ret;
|
||||||
return -ENODEV;
|
|
||||||
|
|
||||||
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.
|
/* 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->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);
|
ret = imx500_transition_to_network(imx500);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(&client->dev,
|
dev_err(&client->dev,
|
||||||
@@ -2638,7 +2950,8 @@ static int imx500_start_streaming(struct imx500 *imx500)
|
|||||||
|
|
||||||
/* Enable DNN */
|
/* Enable DNN */
|
||||||
if (imx500->fw_network) {
|
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) {
|
if (ret) {
|
||||||
dev_err(&client->dev, "%s failed to enable DNN\n",
|
dev_err(&client->dev, "%s failed to enable DNN\n",
|
||||||
__func__);
|
__func__);
|
||||||
@@ -2673,76 +2986,26 @@ static int imx500_start_streaming(struct imx500 *imx500)
|
|||||||
if (ret)
|
if (ret)
|
||||||
goto err_runtime_put;
|
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;
|
return 0;
|
||||||
|
|
||||||
err_runtime_put:
|
err_runtime_put:
|
||||||
|
__v4l2_ctrl_grab(imx500->enable_injection, false);
|
||||||
pm_runtime_mark_last_busy(&client->dev);
|
pm_runtime_mark_last_busy(&client->dev);
|
||||||
pm_runtime_put_autosuspend(&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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Power/clock management functions */
|
|
||||||
static int imx500_power_on(struct device *dev)
|
static int imx500_power_on(struct device *dev)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = to_i2c_client(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. */
|
/* Force reprogramming of the common registers when powered up again. */
|
||||||
imx500->fsm_state = IMX500_STATE_RESET;
|
imx500->fsm_state = IMX500_STATE_RESET;
|
||||||
|
imx500->tensor_injection = false;
|
||||||
imx500->common_regs_written = false;
|
imx500->common_regs_written = false;
|
||||||
imx500->loader_and_main_written = false;
|
imx500->loader_and_main_written = false;
|
||||||
imx500_clear_fw_network(imx500);
|
imx500_clear_fw_network(imx500);
|
||||||
@@ -2853,6 +3117,102 @@ static int imx500_power_off(struct device *dev)
|
|||||||
return 0;
|
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)
|
static int imx500_get_regulators(struct imx500 *imx500)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
|
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,
|
.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 */
|
/* Initialize control handlers */
|
||||||
static int imx500_init_controls(struct imx500 *imx500)
|
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);
|
v4l2_ctrl_new_custom(ctrl_hdlr, &network_fw_fd, NULL);
|
||||||
imx500->device_id =
|
imx500->device_id =
|
||||||
v4l2_ctrl_new_custom(ctrl_hdlr, &cam_get_device_id, NULL);
|
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) {
|
if (ctrl_hdlr->error) {
|
||||||
ret = ctrl_hdlr->error;
|
ret = ctrl_hdlr->error;
|
||||||
@@ -3203,6 +3607,9 @@ static int imx500_probe(struct i2c_client *client)
|
|||||||
/* Initialize default format */
|
/* Initialize default format */
|
||||||
imx500_set_default_format(imx500);
|
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. */
|
/* This needs the pm runtime to be registered. */
|
||||||
ret = imx500_init_controls(imx500);
|
ret = imx500_init_controls(imx500);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
|||||||
Reference in New Issue
Block a user