|
| 1 | +From c0bc32d888fb1c7d291b7b20f07f5d37cd18edcd Mon Sep 17 00:00:00 2001 |
| 2 | +From: "Goh, Wei Khang1" < [email protected]> |
| 3 | +Date: Thu, 16 Oct 2025 10:23:20 +0800 |
| 4 | +Subject: [PATCH 1/3] media: isx031: Add support on ISX031 MIPI CSI |
| 5 | + |
| 6 | +* isx031: Add support on ISX031 MIPI CSI (D3) |
| 7 | + |
| 8 | +- Verified sensor driver compatibility with RPL & ARL kernel 6.12 |
| 9 | +- Added ISX031 sensor driver implementation |
| 10 | +- Added reference XML configuration for ISX031 sensor |
| 11 | +- Added reference BIOS configuration for Camera Option & Control Logic |
| 12 | +- Added user-space command for sensor testing |
| 13 | + |
| 14 | +* isx031: Refine is_direct flag for sensor type checking |
| 15 | + |
| 16 | +- Simplified sensor type selection (MIPI/GMSL) using is_direct flag |
| 17 | + |
| 18 | +* isx031: Remove redundant isx031_reset() |
| 19 | + |
| 20 | +- Removed isx031_reset() as sensor reset action completed by devm_gpiod_get_optional(). |
| 21 | + |
| 22 | +* isx031: Add sensor power control for isx031_suspend() & isx031_resume() |
| 23 | + |
| 24 | +- Added sensor power off for isx031_suspend() |
| 25 | +- Added sensor power on for isx031_resume() |
| 26 | +- Fixed compilation warning |
| 27 | + |
| 28 | +Signed-off-by: Lui, Jonathan Ming Jun < [email protected]> |
| 29 | +Signed-off-by: Goh, Wei Khang1 < [email protected]> |
| 30 | +--- |
| 31 | + drivers/media/i2c/isx031.c | 146 +++++++++++++++++++++------ |
| 32 | + drivers/media/i2c/max9x/serdes.c | 2 +- |
| 33 | + drivers/media/pci/intel/ipu-bridge.c | 2 + |
| 34 | + 3 files changed, 119 insertions(+), 31 deletions(-) |
| 35 | + |
| 36 | +diff --git a/drivers/media/i2c/isx031.c b/drivers/media/i2c/isx031.c |
| 37 | +index acaf41a82fbe..8a6f60ccdacb 100644 |
| 38 | +--- a/drivers/media/i2c/isx031.c |
| 39 | ++++ b/drivers/media/i2c/isx031.c |
| 40 | +@@ -34,6 +34,10 @@ |
| 41 | + #define ISX031_REG_MODE_SET_F_LOCK 0xBEF0 |
| 42 | + #define ISX031_MODE_UNLOCK 0x53 |
| 43 | + |
| 44 | ++struct isx031_info { |
| 45 | ++ bool is_direct; |
| 46 | ++}; |
| 47 | ++ |
| 48 | + struct isx031_reg { |
| 49 | + enum { |
| 50 | + ISX031_REG_LEN_DELAY = 0, |
| 51 | +@@ -79,7 +83,7 @@ struct isx031 { |
| 52 | + /* Previous mode */ |
| 53 | + const struct isx031_mode *pre_mode; |
| 54 | + |
| 55 | +- /* To serialize asynchronus callbacks */ |
| 56 | ++ /* To serialize asynchronous callbacks */ |
| 57 | + struct mutex mutex; |
| 58 | + |
| 59 | + /* i2c client */ |
| 60 | +@@ -90,6 +94,15 @@ struct isx031 { |
| 61 | + |
| 62 | + /* Streaming on/off */ |
| 63 | + bool streaming; |
| 64 | ++ |
| 65 | ++ struct v4l2_ctrl_handler ctrls; |
| 66 | ++ |
| 67 | ++ /* MIPI direct connection */ |
| 68 | ++ bool is_direct; |
| 69 | ++}; |
| 70 | ++ |
| 71 | ++static const s64 isx031_link_frequencies[] = { |
| 72 | ++ 300000000ULL |
| 73 | + }; |
| 74 | + |
| 75 | + static const struct isx031_reg isx031_init_reg[] = { |
| 76 | +@@ -101,7 +114,7 @@ static const struct isx031_reg isx031_init_reg[] = { |
| 77 | + static const struct isx031_reg isx031_framesync_reg[] = { |
| 78 | + /* External sync */ |
| 79 | + {ISX031_REG_LEN_08BIT, 0xBF14, 0x01}, /* SG_MODE_APL */ |
| 80 | +- {ISX031_REG_LEN_08BIT, 0x8AFF, 0x0c}, /* Hi-Z (input setting or output disabled) */ |
| 81 | ++ {ISX031_REG_LEN_08BIT, 0x8AFF, 0x0c}, /* Hi-Z (input setting or output disabled) */ |
| 82 | + {ISX031_REG_LEN_08BIT, 0x0153, 0x00}, |
| 83 | + {ISX031_REG_LEN_08BIT, 0x8AF0, 0x01}, /* external pulse-based sync */ |
| 84 | + {ISX031_REG_LEN_08BIT, 0x0144, 0x00}, |
| 85 | +@@ -224,21 +237,6 @@ static const struct isx031_mode supported_modes[] = { |
| 86 | + }, |
| 87 | + }; |
| 88 | + |
| 89 | +-static int isx031_reset(struct gpio_desc *reset_gpio) |
| 90 | +-{ |
| 91 | +- if (!IS_ERR_OR_NULL(reset_gpio)) { |
| 92 | +- gpiod_set_value_cansleep(reset_gpio, 0); |
| 93 | +- usleep_range(500, 1000); |
| 94 | +- gpiod_set_value_cansleep(reset_gpio, 1); |
| 95 | +- /*Needs to sleep for quite a while before register writes*/ |
| 96 | +- usleep_range(200 * 1000, 200 * 1000 + 500); |
| 97 | +- |
| 98 | +- return 0; |
| 99 | +- } |
| 100 | +- |
| 101 | +- return -EINVAL; |
| 102 | +-} |
| 103 | +- |
| 104 | + static int isx031_read_reg(struct isx031 *isx031, u16 reg, u16 len, u32 *val) |
| 105 | + { |
| 106 | + struct i2c_client *client = isx031->client; |
| 107 | +@@ -351,7 +349,7 @@ static int isx031_mode_transit(struct isx031 *isx031, int state) |
| 108 | + return ret; |
| 109 | + } |
| 110 | + |
| 111 | +- /*streaming transit to standby need 1 frame+5ms*/ |
| 112 | ++ /* streaming transit to standby need 1 frame+5ms */ |
| 113 | + retry = 50; |
| 114 | + while (retry--) { |
| 115 | + ret = isx031_read_reg(isx031, ISX031_REG_SENSOR_STATE, |
| 116 | +@@ -384,7 +382,7 @@ static int isx031_identify_module(struct isx031 *isx031) |
| 117 | + |
| 118 | + dev_dbg(&client->dev, "sensor in mode 0x%x", val); |
| 119 | + |
| 120 | +- /* if sensor alreay in ISX031_STATE_STARTUP, can access i2c write directly*/ |
| 121 | ++ /* if sensor alreay in ISX031_STATE_STARTUP, can access i2c write directly */ |
| 122 | + if (val == ISX031_STATE_STREAMING) { |
| 123 | + if (isx031_mode_transit(isx031, ISX031_STATE_STARTUP)) |
| 124 | + return ret; |
| 125 | +@@ -431,6 +429,14 @@ static int isx031_start_streaming(struct isx031 *isx031) |
| 126 | + dev_dbg(&client->dev, "same mode, skip write reg list"); |
| 127 | + } |
| 128 | + |
| 129 | ++ if (isx031->is_direct) { |
| 130 | ++ ret = __v4l2_ctrl_handler_setup(&isx031->ctrls); |
| 131 | ++ if (ret) { |
| 132 | ++ dev_err(&client->dev, "failed to setup ctrls"); |
| 133 | ++ return ret; |
| 134 | ++ } |
| 135 | ++ } |
| 136 | ++ |
| 137 | + ret = isx031_mode_transit(isx031, ISX031_STATE_STREAMING); |
| 138 | + if (ret) { |
| 139 | + dev_err(&client->dev, "failed to start streaming"); |
| 140 | +@@ -509,6 +515,9 @@ static int __maybe_unused isx031_suspend(struct device *dev) |
| 141 | + |
| 142 | + mutex_unlock(&isx031->mutex); |
| 143 | + |
| 144 | ++ /* Active low gpio reset, set 1 to power off sensor */ |
| 145 | ++ gpiod_set_value_cansleep(isx031->reset_gpio, 1); |
| 146 | ++ |
| 147 | + return 0; |
| 148 | + } |
| 149 | + |
| 150 | +@@ -519,9 +528,23 @@ static int __maybe_unused isx031_resume(struct device *dev) |
| 151 | + struct isx031 *isx031 = to_isx031(sd); |
| 152 | + const struct isx031_reg_list *reg_list; |
| 153 | + int ret; |
| 154 | ++ int count = 0; |
| 155 | + |
| 156 | +- if (isx031->reset_gpio != NULL) |
| 157 | +- isx031_reset(isx031->reset_gpio); |
| 158 | ++ /* Active low gpio reset, set 0 to power on sensor, |
| 159 | ++ * sensor must on back before start resume |
| 160 | ++ */ |
| 161 | ++ if (isx031->reset_gpio != NULL) { |
| 162 | ++ do { |
| 163 | ++ gpiod_set_value_cansleep(isx031->reset_gpio, 0); |
| 164 | ++ ret = gpiod_get_value_cansleep(isx031->reset_gpio); |
| 165 | ++ usleep_range(200 * 1000, 200 * 1000 + 500); |
| 166 | ++ |
| 167 | ++ if (++count >= 5) { |
| 168 | ++ dev_err(&client->dev, "%s: failed to power on reset gpio, reset gpio is %d", __func__, ret); |
| 169 | ++ break; |
| 170 | ++ } |
| 171 | ++ } while (ret != 0); |
| 172 | ++ } |
| 173 | + |
| 174 | + ret = isx031_identify_module(isx031); |
| 175 | + if (ret == 0) { |
| 176 | +@@ -755,10 +778,40 @@ static void isx031_remove(struct i2c_client *client) |
| 177 | + #endif |
| 178 | + } |
| 179 | + |
| 180 | ++static int isx031_set_ctrl(struct v4l2_ctrl *ctrl) |
| 181 | ++{ |
| 182 | ++ return 0; |
| 183 | ++} |
| 184 | ++ |
| 185 | ++static const struct v4l2_ctrl_ops isx031_ctrl_ops = { |
| 186 | ++ .s_ctrl = isx031_set_ctrl, |
| 187 | ++}; |
| 188 | ++ |
| 189 | ++static int isx031_ctrls_init(struct isx031 *sensor) |
| 190 | ++{ |
| 191 | ++ int ret = 0; |
| 192 | ++ struct v4l2_ctrl *ctrl; |
| 193 | ++ |
| 194 | ++ v4l2_ctrl_handler_init(&sensor->ctrls, 10); |
| 195 | ++ |
| 196 | ++ /* There's a need to set the link frequency because IPU6 dictates it. */ |
| 197 | ++ ctrl = v4l2_ctrl_new_int_menu(&sensor->ctrls, &isx031_ctrl_ops, |
| 198 | ++ V4L2_CID_LINK_FREQ, |
| 199 | ++ ARRAY_SIZE(isx031_link_frequencies) - 1, 0, |
| 200 | ++ isx031_link_frequencies); |
| 201 | ++ |
| 202 | ++ if (ctrl) |
| 203 | ++ ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; |
| 204 | ++ |
| 205 | ++ sensor->sd.ctrl_handler = &sensor->ctrls; |
| 206 | ++ return ret; |
| 207 | ++} |
| 208 | ++ |
| 209 | + static int isx031_probe(struct i2c_client *client) |
| 210 | + { |
| 211 | + struct v4l2_subdev *sd; |
| 212 | + struct isx031 *isx031; |
| 213 | ++ const struct isx031_info *info; |
| 214 | + const struct isx031_reg_list *reg_list; |
| 215 | + int ret; |
| 216 | + |
| 217 | +@@ -772,19 +825,31 @@ static int isx031_probe(struct i2c_client *client) |
| 218 | + dev_warn(&client->dev, "no platform data provided\n"); |
| 219 | + |
| 220 | + isx031->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", |
| 221 | +- GPIOD_OUT_HIGH); |
| 222 | ++ GPIOD_OUT_LOW); |
| 223 | ++ |
| 224 | + if (IS_ERR(isx031->reset_gpio)) |
| 225 | + return -EPROBE_DEFER; |
| 226 | + else if (isx031->reset_gpio == NULL) |
| 227 | + dev_warn(&client->dev, "Reset GPIO not found"); |
| 228 | +- else { |
| 229 | ++ else |
| 230 | + dev_dbg(&client->dev, "Found reset GPIO"); |
| 231 | +- isx031_reset(isx031->reset_gpio); |
| 232 | +- } |
| 233 | ++ |
| 234 | ++ info = device_get_match_data(&client->dev); |
| 235 | ++ if (info) |
| 236 | ++ isx031->is_direct = info->is_direct; |
| 237 | ++ else |
| 238 | ++ isx031->is_direct = false; |
| 239 | + |
| 240 | + /* initialize subdevice */ |
| 241 | + sd = &isx031->sd; |
| 242 | + v4l2_i2c_subdev_init(sd, client, &isx031_subdev_ops); |
| 243 | ++ if (isx031->is_direct) { |
| 244 | ++ ret = isx031_ctrls_init(isx031); |
| 245 | ++ if (ret) { |
| 246 | ++ dev_err(&client->dev, "failed to init sensor ctrls: %d", ret); |
| 247 | ++ return ret; |
| 248 | ++ } |
| 249 | ++ } |
| 250 | + #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 10, 0) |
| 251 | + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; |
| 252 | + #else |
| 253 | +@@ -798,9 +863,13 @@ static int isx031_probe(struct i2c_client *client) |
| 254 | + isx031->pad.flags = MEDIA_PAD_FL_SOURCE; |
| 255 | + ret = media_entity_pads_init(&sd->entity, 1, &isx031->pad); |
| 256 | + if (ret < 0) { |
| 257 | +- dev_err(&client->dev, |
| 258 | +- "%s : media entity init Failed %d\n", __func__, ret); |
| 259 | +- return ret; |
| 260 | ++ dev_err(&client->dev, "failed to init entity pads: %d", ret); |
| 261 | ++ goto probe_error_v4l2_ctrl_handler_free; |
| 262 | ++ } |
| 263 | ++ |
| 264 | ++ if (isx031->is_direct) { |
| 265 | ++ isx031->sd.state_lock = isx031->sd.ctrl_handler->lock; |
| 266 | ++ v4l2_subdev_init_finalize(&isx031->sd); |
| 267 | + } |
| 268 | + |
| 269 | + ret = isx031_identify_module(isx031); |
| 270 | +@@ -851,6 +920,9 @@ static int isx031_probe(struct i2c_client *client) |
| 271 | + pm_runtime_disable(&client->dev); |
| 272 | + mutex_destroy(&isx031->mutex); |
| 273 | + |
| 274 | ++probe_error_v4l2_ctrl_handler_free: |
| 275 | ++ v4l2_ctrl_handler_free(isx031->sd.ctrl_handler); |
| 276 | ++ |
| 277 | + return ret; |
| 278 | + } |
| 279 | + |
| 280 | +@@ -860,13 +932,25 @@ static const struct dev_pm_ops isx031_pm_ops = { |
| 281 | + |
| 282 | + static const struct i2c_device_id isx031_id_table[] = { |
| 283 | + { "isx031", 0 }, |
| 284 | +- { /* sentinel */ }, |
| 285 | ++ {} |
| 286 | + }; |
| 287 | + MODULE_DEVICE_TABLE(i2c, isx031_id_table); |
| 288 | + |
| 289 | ++static const struct isx031_info isx031_mipi_info = { |
| 290 | ++ .is_direct = true, |
| 291 | ++}; |
| 292 | ++ |
| 293 | ++static const struct acpi_device_id isx031_acpi_ids[] = { |
| 294 | ++ { "INTC3031", (kernel_ulong_t)&isx031_mipi_info }, |
| 295 | ++ {} |
| 296 | ++}; |
| 297 | ++ |
| 298 | ++MODULE_DEVICE_TABLE(acpi, isx031_acpi_ids); |
| 299 | ++ |
| 300 | + static struct i2c_driver isx031_i2c_driver = { |
| 301 | + .driver = { |
| 302 | + .name = "isx031", |
| 303 | ++ .acpi_match_table = ACPI_PTR(isx031_acpi_ids), |
| 304 | + .pm = &isx031_pm_ops, |
| 305 | + }, |
| 306 | + #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) |
| 307 | +@@ -880,6 +964,8 @@ static struct i2c_driver isx031_i2c_driver = { |
| 308 | + |
| 309 | + module_i2c_driver(isx031_i2c_driver); |
| 310 | + |
| 311 | +-MODULE_AUTHOR("Hao Yao <[email protected]>"); |
| 312 | + MODULE_DESCRIPTION("isx031 sensor driver"); |
| 313 | ++MODULE_AUTHOR("Hao Yao <[email protected]>"); |
| 314 | ++MODULE_AUTHOR("Jonathan Lui <[email protected]>"); |
| 315 | ++MODULE_AUTHOR("Wei Khang, Goh <[email protected]>"); |
| 316 | + MODULE_LICENSE("GPL v2"); |
| 317 | +diff --git a/drivers/media/i2c/max9x/serdes.c b/drivers/media/i2c/max9x/serdes.c |
| 318 | +index f29949d96aa7..424d0014d68e 100644 |
| 319 | +--- a/drivers/media/i2c/max9x/serdes.c |
| 320 | ++++ b/drivers/media/i2c/max9x/serdes.c |
| 321 | +@@ -1655,7 +1655,7 @@ static int max9x_registered(struct v4l2_subdev *sd) |
| 322 | + .dev_id = "", |
| 323 | + .table = { |
| 324 | + GPIO_LOOKUP("", 0, "reset", |
| 325 | +- GPIO_ACTIVE_HIGH), |
| 326 | ++ GPIO_ACTIVE_LOW), |
| 327 | + {} |
| 328 | + }, |
| 329 | + }; |
| 330 | +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c |
| 331 | +index 4e4f86a1f0b8..def371fe499b 100644 |
| 332 | +--- a/drivers/media/pci/intel/ipu-bridge.c |
| 333 | ++++ b/drivers/media/pci/intel/ipu-bridge.c |
| 334 | +@@ -91,6 +91,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { |
| 335 | + IPU_SENSOR_CONFIG("INTC10C5", 0), |
| 336 | + /* Lontium lt6911uxc */ |
| 337 | + IPU_SENSOR_CONFIG("INTC10B1", 0), |
| 338 | ++ /* D3 Embedded ISX031 */ |
| 339 | ++ IPU_SENSOR_CONFIG("INTC3031", 1, 300000000) |
| 340 | + }; |
| 341 | + |
| 342 | + static const struct ipu_sensor_config ipu_supported_sensors_dummy[] = { |
| 343 | +-- |
| 344 | +2.17.1 |
| 345 | + |
0 commit comments