diff --git a/DLCVCAM_BUILD_VERSION b/DLCVCAM_BUILD_VERSION new file mode 100644 index 0000000000000..18ef27d008985 --- /dev/null +++ b/DLCVCAM_BUILD_VERSION @@ -0,0 +1 @@ +2026072202 diff --git "a/DLCVCAM\345\206\205\346\240\270\347\274\226\350\257\221\344\270\216\346\233\264\346\226\260\346\223\215\344\275\234\346\211\213\345\206\214.md" "b/DLCVCAM\345\206\205\346\240\270\347\274\226\350\257\221\344\270\216\346\233\264\346\226\260\346\223\215\344\275\234\346\211\213\345\206\214.md" index bafffd7c9b044..45ac3a4a637ac 100644 --- "a/DLCVCAM\345\206\205\346\240\270\347\274\226\350\257\221\344\270\216\346\233\264\346\226\260\346\223\215\344\275\234\346\211\213\345\206\214.md" +++ "b/DLCVCAM\345\206\205\346\240\270\347\274\226\350\257\221\344\270\216\346\233\264\346\226\260\346\223\215\344\275\234\346\211\213\345\206\214.md" @@ -20,7 +20,54 @@ which aarch64-linux-gnu-gcc ## 2. 编译内核 -### 2.1 初次配置(仅需执行一次) +### 2.1 日期构建编号规则 + +DLCVCAM 发布内核使用仓库根目录的 `DLCVCAM_BUILD_VERSION` 作为构建编号, +格式固定为: + +```text +YYYYMMDDNN +``` + +其中 `YYYYMMDD` 是发布日期,`NN` 是当天两位序号,从 `01` 开始。例如: + +```text +2026072201 +``` + +合入 `master` 前必须把该文件更新为实际合入日期;同一天发布多个内核时依次使用 +`01`、`02`、`03`。不要通过修改 `VERSION/PATCHLEVEL/SUBLEVEL` 记录发布日期, +这样可以保持 `uname -r` 和 `/lib/modules/6.1.99-rk3576` 路径稳定。 + +开发机查看仓库默认构建编号: + +```bash +cat DLCVCAM_BUILD_VERSION +make -s dlcvcam-build-version +``` + +板卡刷入对应内核后查看: + +```bash +uname -v +cat /proc/version +``` + +输出示例: + +```text +#2026072201 SMP Wed Jul 22 10:46:10 CST 2026 +``` + +正常发布构建不需要再手工传入 `KBUILD_BUILD_VERSION`。CI 或临时构建仍可显式传入 +该变量覆盖仓库值,但交付镜像必须使用 `DLCVCAM_BUILD_VERSION` 中已提交的编号。 +建议镜像文件名同时记录构建编号和 Git 短提交,例如: + +```text +boot-rk3576-6.1.99-2026072201-e8c10e0f.img +``` + +### 2.2 初次配置(仅需执行一次) ```bash cd /home/ypw/kernel @@ -36,7 +83,7 @@ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat_linux_rk3576_defconfig make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig ``` -### 2.2 增量编译(日常修改后) +### 2.3 增量编译(日常修改后) ```bash cd /home/ypw/kernel @@ -67,6 +114,13 @@ BOOT_ITS=boot.its ./scripts/mkimg --dtb dlcvcam-rk3576.dtb mkimage -l boot.img ``` +同时确认本次编译使用的日期构建编号: + +```bash +make -s ARCH=arm64 O=/path/to/kernel-out dlcvcam-build-version +# 2026072201 +``` + --- ## 3. 烧录到板子 diff --git a/Makefile b/Makefile index 84f782580f962..2eb37484ffc7b 100644 --- a/Makefile +++ b/Makefile @@ -203,6 +203,21 @@ endif this-makefile := $(lastword $(MAKEFILE_LIST)) abs_srctree := $(realpath $(dir $(this-makefile))) +# DLCVCAM release build identifier. Keep the Linux release and module path +# stable (for example 6.1.99-rk3576), while exposing a date-based identifier +# through UTS_VERSION (`uname -v`) on the target board. A command-line +# KBUILD_BUILD_VERSION still takes precedence for temporary/CI builds. +DLCVCAM_BUILD_VERSION_FILE := $(abs_srctree)/DLCVCAM_BUILD_VERSION +DLCVCAM_BUILD_VERSION := $(strip $(shell cat $(DLCVCAM_BUILD_VERSION_FILE) 2>/dev/null)) +DLCVCAM_BUILD_VERSION_VALID := $(shell printf '%s\n' '$(DLCVCAM_BUILD_VERSION)' | \ + grep -Eq '^[0-9]{8}(0[1-9]|[1-9][0-9])$$' && echo y) +ifneq ($(DLCVCAM_BUILD_VERSION_VALID),y) +$(error DLCVCAM_BUILD_VERSION must use YYYYMMDDNN with NN in 01..99) +endif +KBUILD_BUILD_VERSION ?= $(DLCVCAM_BUILD_VERSION) + +export DLCVCAM_BUILD_VERSION KBUILD_BUILD_VERSION + ifneq ($(words $(subst :, ,$(abs_srctree))), 1) $(error source directory cannot contain spaces or colons) endif @@ -286,7 +301,8 @@ no-dot-config-targets := $(clean-targets) \ cscope gtags TAGS tags help% %docs check% coccicheck \ $(version_h) headers headers_% archheaders archscripts \ %asm-generic kernelversion %src-pkg dt_binding_check \ - outputmakefile rustavailable rustfmt rustfmtcheck + outputmakefile rustavailable rustfmt rustfmtcheck \ + dlcvcam-build-version # Installation targets should not require compiler. Unfortunately, vdso_install # is an exception where build artifacts may be updated. This must be fixed. no-compiler-targets := $(no-dot-config-targets) install dtbs_install \ @@ -1687,6 +1703,7 @@ help: @echo ' gtags - Generate GNU GLOBAL index' @echo ' kernelrelease - Output the release version string (use with make -s)' @echo ' kernelversion - Output the version stored in Makefile (use with make -s)' + @echo ' dlcvcam-build-version - Output the date-based DLCVCAM build identifier' @echo ' image_name - Output the image name (use with make -s)' @echo ' headers_install - Install sanitised kernel headers to INSTALL_HDR_PATH'; \ echo ' (default: $(INSTALL_HDR_PATH))'; \ @@ -2095,7 +2112,7 @@ coccicheck: export_report: $(PERL) $(srctree)/scripts/export_report.pl -PHONY += checkstack kernelrelease kernelversion image_name +PHONY += checkstack kernelrelease kernelversion dlcvcam-build-version image_name # UML needs a little special treatment here. It wants to use the host # toolchain, so needs $(SUBARCH) passed to checkstack.pl. Everyone @@ -2116,6 +2133,9 @@ kernelrelease: kernelversion: @echo $(KERNELVERSION) +dlcvcam-build-version: + @echo $(KBUILD_BUILD_VERSION) + image_name: @echo $(KBUILD_IMAGE) diff --git a/drivers/input/misc/trigger-dev.c b/drivers/input/misc/trigger-dev.c index a87762f4ab486..c31a60ed0ec2d 100644 --- a/drivers/input/misc/trigger-dev.c +++ b/drivers/input/misc/trigger-dev.c @@ -13,6 +13,8 @@ * 2. trigger-path: write to sysfs "echo 1 > ..." - fallback for software trigger * - Optional result LEDs: ok-led-gpios / ng-led-gpios; sysfs "result" (ok/ng/off), * "result_led_enable"; each external trigger clears LEDs before camera action. + * - Runtime gate: sysfs "enable" (0/1). Disabling masks the input IRQ, cancels + * queued work and drops pending triggers before returning. * * When trigger-output-gpios is set, keep a single owner for that output GPIO. * If another node already claims the same pin, remove one side in DT. @@ -87,6 +89,11 @@ struct trigger_dev { bool mode_use_gpio; struct mutex mode_mutex; + /* 外部触发总门闩;默认开启,模式/ROI 切换期间由 sysfs enable 关闭 */ + bool enabled; + /* Serializes IRQ masking and pending-work cancellation. */ + struct mutex enable_mutex; + /* 结果指示灯:DT ok-led-gpios / ng-led-gpios;逻辑 0=inactive(灭) 1=active(亮) */ struct gpio_desc *ok_led_gpiod; struct gpio_desc *ng_led_gpiod; @@ -101,6 +108,7 @@ struct trigger_dev { atomic64_t irq_count; atomic64_t trigger_ok_count; atomic64_t trigger_fail_count; + atomic64_t trigger_suppressed_count; u32 max_pending; u64 last_irq_ns; }; @@ -163,9 +171,13 @@ static int trigger_dev_set_input_mode(struct trigger_dev *tdev, enum trigger_dev_input_mode mode) { unsigned int irq_type = trigger_dev_irq_type_for_mode(tdev, mode); + bool enabled; int ret; - disable_irq(tdev->irq); + mutex_lock(&tdev->enable_mutex); + enabled = READ_ONCE(tdev->enabled); + if (enabled) + disable_irq(tdev->irq); cancel_delayed_work_sync(&tdev->debounce_work); ret = irq_set_irq_type(tdev->irq, irq_type); @@ -175,7 +187,51 @@ static int trigger_dev_set_input_mode(struct trigger_dev *tdev, WRITE_ONCE(tdev->input_mode, mode); } - enable_irq(tdev->irq); + if (enabled) + enable_irq(tdev->irq); + mutex_unlock(&tdev->enable_mutex); + return ret; +} + +static int trigger_dev_set_enabled(struct trigger_dev *tdev, bool enabled) +{ + int dropped; + int ret = 0; + + mutex_lock(&tdev->enable_mutex); + if (enabled == READ_ONCE(tdev->enabled)) + goto out; + + if (!enabled) { + /* Publish the closed gate before waiting for any in-flight handler. */ + WRITE_ONCE(tdev->enabled, false); + disable_irq(tdev->irq); + cancel_delayed_work_sync(&tdev->debounce_work); + cancel_work_sync(&tdev->trigger_work); + + dropped = atomic_xchg(&tdev->trigger_pending, 0); + if (dropped > 0) + atomic64_add(dropped, &tdev->trigger_suppressed_count); + + if (tdev->output_gpiod) + gpiod_set_value_cansleep(tdev->output_gpiod, 0); + + if (tdev->pressed) { + tdev->pressed = false; + input_report_key(tdev->input, tdev->key_code, 0); + input_sync(tdev->input); + } + } else { + atomic_set(&tdev->trigger_pending, 0); + ret = trigger_dev_refresh_pressed_state(tdev); + if (ret) + goto out; + WRITE_ONCE(tdev->enabled, true); + enable_irq(tdev->irq); + } + +out: + mutex_unlock(&tdev->enable_mutex); return ret; } @@ -283,6 +339,11 @@ static void trigger_dev_trigger_work(struct work_struct *work) /* Drain pending triggers (presses) */ while (atomic_dec_if_positive(&tdev->trigger_pending) >= 0) { + if (!READ_ONCE(tdev->enabled)) { + atomic64_inc(&tdev->trigger_suppressed_count); + continue; + } + mutex_lock(&tdev->led_mutex); if (tdev->result_led_enable) trigger_dev_leds_off_locked(tdev); @@ -327,6 +388,9 @@ static void trigger_dev_trigger_work(struct work_struct *work) static void trigger_dev_handle_state(struct trigger_dev *tdev, bool pressed_now) { + if (!READ_ONCE(tdev->enabled)) + return; + if (pressed_now == tdev->pressed) return; @@ -375,6 +439,10 @@ static irqreturn_t trigger_dev_irq(int irq, void *dev_id) tdev->last_irq_ns = ktime_get_ns(); atomic64_inc(&tdev->irq_count); + if (!READ_ONCE(tdev->enabled)) { + atomic64_inc(&tdev->trigger_suppressed_count); + return IRQ_HANDLED; + } /* * Fast edge mode: @@ -516,7 +584,9 @@ static int trigger_dev_probe(struct platform_device *pdev) tdev->dev = dev; platform_set_drvdata(pdev, tdev); mutex_init(&tdev->mode_mutex); + mutex_init(&tdev->enable_mutex); mutex_init(&tdev->led_mutex); + WRITE_ONCE(tdev->enabled, true); /* * Primary DT property: input-gpios (con_id = "input") @@ -555,6 +625,7 @@ static int trigger_dev_probe(struct platform_device *pdev) atomic64_set(&tdev->irq_count, 0); atomic64_set(&tdev->trigger_ok_count, 0); atomic64_set(&tdev->trigger_fail_count, 0); + atomic64_set(&tdev->trigger_suppressed_count, 0); tdev->max_pending = 0; tdev->last_irq_ns = 0; @@ -590,7 +661,7 @@ static int trigger_dev_probe(struct platform_device *pdev) gpio = desc_to_gpio(tdev->input_gpiod); active_low = gpiod_is_active_low(tdev->input_gpiod); - dev_info(dev, "ready: gpio=%d irq=%d active_low=%d input_mode=%s debounce_ms=%u key_code=%u mode=%s\n", + dev_info(dev, "ready: gpio=%d irq=%d active_low=%d input_mode=%s debounce_ms=%u key_code=%u mode=%s enabled=%d\n", gpio, tdev->irq, active_low, @@ -598,7 +669,8 @@ static int trigger_dev_probe(struct platform_device *pdev) tdev->debounce_ms, tdev->key_code, tdev->output_gpiod ? - "direct-gpio-pulse" : "sysfs-write"); + "direct-gpio-pulse" : "sysfs-write", + READ_ONCE(tdev->enabled)); if (tdev->input_mode == TRIGGER_DEV_INPUT_MODE_BUTTON && !tdev->debounce_ms) dev_warn(dev, "button input mode is selected with debounce-ms=0; mechanical keys may still bounce\n"); @@ -666,6 +738,36 @@ static ssize_t input_mode_store(struct device *dev, } static DEVICE_ATTR_RW(input_mode); +/* Sysfs: enable (0=屏蔽外部触发并清空 pending, 1=允许外部触发) */ +static ssize_t enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct trigger_dev *tdev = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", READ_ONCE(tdev->enabled)); +} + +static ssize_t enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct trigger_dev *tdev = dev_get_drvdata(dev); + bool enabled; + int ret; + + ret = kstrtobool(buf, &enabled); + if (ret) + return ret; + + ret = trigger_dev_set_enabled(tdev, enabled); + if (ret) + return ret; + + dev_info(dev, "external trigger %s\n", enabled ? "enabled" : "disabled"); + return count; +} +static DEVICE_ATTR_RW(enable); + /* Sysfs: mode (gpio|sysfs) */ static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -874,10 +976,12 @@ static ssize_t stats_show(struct device *dev, struct device_attribute *attr, cha u64 ago_us = tdev->last_irq_ns ? div_u64(now - tdev->last_irq_ns, 1000) : 0; return sysfs_emit(buf, - "irq=%lld ok=%lld fail=%lld pending=%d max_pending=%u last_irq_ago_us=%llu input_mode=%s debounce_ms=%u\n", + "enabled=%d irq=%lld ok=%lld fail=%lld suppressed=%lld pending=%d max_pending=%u last_irq_ago_us=%llu input_mode=%s debounce_ms=%u\n", + READ_ONCE(tdev->enabled), atomic64_read(&tdev->irq_count), atomic64_read(&tdev->trigger_ok_count), atomic64_read(&tdev->trigger_fail_count), + atomic64_read(&tdev->trigger_suppressed_count), atomic_read(&tdev->trigger_pending), tdev->max_pending, ago_us, @@ -888,6 +992,7 @@ static DEVICE_ATTR_RO(stats); static struct attribute *trigger_dev_attrs[] = { &dev_attr_input_mode.attr, + &dev_attr_enable.attr, &dev_attr_mode.attr, &dev_attr_pulse_count.attr, &dev_attr_pulse_interval_us.attr, @@ -930,5 +1035,3 @@ module_platform_driver(trigger_dev_driver); MODULE_DESCRIPTION("Generic GPIO input trigger device (camera trigger + optional result LEDs)"); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); - - diff --git a/drivers/media/i2c/imx296.c b/drivers/media/i2c/imx296.c index 0eae71b874623..1128cd13984e3 100644 --- a/drivers/media/i2c/imx296.c +++ b/drivers/media/i2c/imx296.c @@ -111,6 +111,7 @@ #define IMX296_GAIN IMX296_REG_16BIT(0x3204) #define IMX296_GAINDLY IMX296_REG_8BIT(0x3212) +#define IMX296_GAINDLY_0FRAME 0x08 #define IMX296_GAINDLY_1FRAME 0x09 #define IMX296_BLKLEVEL IMX296_REG_16BIT(0x3254) @@ -146,6 +147,12 @@ */ #define IMX296_HMAX_DEFAULT 1100U #define IMX296_VBLANK_DEFAULT 1162U +/* + * Fast Trigger does not use the 30 fps free-run blanking budget. Sony's + * timing requirement is VTR = ROIWV1 + 30 for vertical ROI, otherwise the + * ROI frame stays open until the next XTRIG and vb2_done is delayed one shot. + */ +#define IMX296_XTRIG_VBLANK 30U #define IMX296_VBLANK_MAX (1048575U - IMX296_PIXEL_ARRAY_HEIGHT) #define IMX296_EXPOSURE_DEFAULT_LINES 1104U #define IMX296_ANALOG_GAIN_MIN 0U @@ -173,11 +180,29 @@ #define V4L2_CID_IMX296_LIGHT_SOURCE_ADVANCE_US (V4L2_CID_USER_IMX296_BASE + 0x4) #define V4L2_CID_IMX296_LIGHT_SOURCE_OFF_DELAY_US (V4L2_CID_USER_IMX296_BASE + 0x5) +/* Datasheet: changing ROI geometry produces one invalid output frame. */ +#define IMX296_ROI_INVALID_FRAME_WAIT_US 50000U + enum imx296_op_mode { IMX296_FREE_RUN = 0, IMX296_XTRIG_ONE_SHOT = 1, }; +/* + * RKISP sits behind the RKCIF bridge on this platform, so its active media + * entity is the CIF subdevice rather than the IMX296 subdevice. Keep a small + * read-only mode bridge for RKISP's fast-trigger early-done decision. The + * board has a single IMX296; atomic access also makes the IRQ/start paths + * independent of the sensor mutex. + */ +static atomic_t imx296_fast_trigger_mode = ATOMIC_INIT(0); + +bool imx296_is_fast_trigger_active(void) +{ + return atomic_read(&imx296_fast_trigger_mode) != 0; +} +EXPORT_SYMBOL_GPL(imx296_is_fast_trigger_active); + struct imx296_clk_params { unsigned int freq; u8 incksel[4]; @@ -218,6 +243,7 @@ struct imx296 { enum imx296_op_mode active_mode; enum imx296_op_mode pending_mode; u32 trigger_pulse_us; + u64 trigger_pwm_base_period_ns; struct gpio_desc *light_source_gpio; struct pinctrl_state *pins_active_high; @@ -239,6 +265,8 @@ struct imx296 { struct v4l2_subdev subdev; struct media_pad pad; struct v4l2_rect crop; + /* Set when ACTIVE crop changes; cleared after ROI invalid-frame discard. */ + bool roi_boundary_pending; struct v4l2_mbus_framefmt format; struct v4l2_ctrl_handler ctrls; @@ -358,20 +386,26 @@ static struct pwm_device *imx296_devm_pwm_get_optional(struct device *dev, return pwm; } -static u64 imx296_pwm_period_ns(struct pwm_device *pwm) +static u64 imx296_trigger_base_period_ns(struct imx296 *sensor) { - struct pwm_state state; struct pwm_args args; - if (!pwm) - return 0; + /* + * Cache the firmware-provided period once. The runtime PWM state may have + * been enlarged for an earlier long pulse and must never become the base + * for later calculations, otherwise a short pulse cannot restore the + * original trigger rate until reboot. + */ + if (!sensor->trigger_pwm) + return IMX296_TRIGGER_PERIOD_NS_DEFAULT; + if (sensor->trigger_pwm_base_period_ns) + return sensor->trigger_pwm_base_period_ns; - pwm_get_state(pwm, &state); - if (state.period) - return state.period; + pwm_get_args(sensor->trigger_pwm, &args); + sensor->trigger_pwm_base_period_ns = args.period ? + args.period : IMX296_TRIGGER_PERIOD_NS_DEFAULT; - pwm_get_args(pwm, &args); - return args.period; + return sensor->trigger_pwm_base_period_ns; } static u64 imx296_trigger_period_ns(u64 pulse_ns, u64 base_period_ns) @@ -384,17 +418,18 @@ static u64 imx296_trigger_period_ns(u64 pulse_ns, u64 base_period_ns) return max(base_period_ns, min_period_ns); } -static int imx296_init_trigger_pwm(struct imx296 *sensor) +static int imx296_apply_trigger_pwm_idle_locked(struct imx296 *sensor) { - struct pwm_state state = { 0 }; + struct pwm_state state; u64 pulse_ns; if (!sensor->trigger_pwm) return 0; pulse_ns = (u64)sensor->trigger_pulse_us * 1000ULL; + pwm_get_state(sensor->trigger_pwm, &state); state.period = imx296_trigger_period_ns( - pulse_ns, imx296_pwm_period_ns(sensor->trigger_pwm)); + pulse_ns, imx296_trigger_base_period_ns(sensor)); state.duty_cycle = 0; state.polarity = PWM_POLARITY_INVERSED; state.enabled = false; @@ -402,6 +437,16 @@ static int imx296_init_trigger_pwm(struct imx296 *sensor) return pwm_apply_state(sensor->trigger_pwm, &state); } +static int imx296_init_trigger_pwm(struct imx296 *sensor) +{ + if (!sensor->trigger_pwm) + return 0; + + imx296_trigger_base_period_ns(sensor); + + return imx296_apply_trigger_pwm_idle_locked(sensor); +} + static int imx296_light_source_value_locked(struct imx296 *sensor, bool on) { /* @@ -506,7 +551,7 @@ static int imx296_trigger_once_locked(struct imx296 *sensor) pwm_get_state(sensor->trigger_pwm, &state); state.period = imx296_trigger_period_ns( - duty_ns, imx296_pwm_period_ns(sensor->trigger_pwm)); + duty_ns, imx296_trigger_base_period_ns(sensor)); state.duty_cycle = duty_ns; state.polarity = PWM_POLARITY_INVERSED; state.enabled = true; @@ -559,7 +604,6 @@ static int imx296_trigger_once_locked(struct imx296 *sensor) return ret; } - static int imx296_set_ctrl(struct v4l2_ctrl *ctrl); static int imx296_mode_switch(struct imx296 *sensor, enum imx296_op_mode new_mode); @@ -754,6 +798,7 @@ static ssize_t trigger_pulse_us_store(struct device *dev, { struct imx296 *sensor = imx296_from_dev(dev); unsigned int pulse_us; + int ret = 0; if (!sensor) return -ENODEV; @@ -765,12 +810,25 @@ static ssize_t trigger_pulse_us_store(struct device *dev, return -ERANGE; mutex_lock(&sensor->mutex); - sensor->trigger_pulse_us = pulse_us; - mutex_unlock(&sensor->mutex); + if (sensor->trigger_pulse_us != pulse_us) { + u32 old_pulse = sensor->trigger_pulse_us; + + sensor->trigger_pulse_us = pulse_us; + ret = imx296_apply_trigger_pwm_idle_locked(sensor); + if (ret) { + sensor->trigger_pulse_us = old_pulse; + imx296_apply_trigger_pwm_idle_locked(sensor); + goto unlock; + } + dev_info(dev, + "trigger pulse width set to %u us (was %u)\n", + pulse_us, old_pulse); + } - dev_info(dev, "trigger pulse width set to %u us\n", pulse_us); +unlock: + mutex_unlock(&sensor->mutex); - return count; + return ret ? ret : count; } static DEVICE_ATTR_RW(trigger_pulse_us); @@ -1325,7 +1383,19 @@ static int imx296_setup(struct imx296 *sensor) imx296_write(sensor, IMX296_FID0_ROIWV1, crop->height, &ret); imx296_write(sensor, IMX296_MIPIC_AREA3W, crop->height, &ret); } else { + /* + * Restore the ROI geometry registers as well as disabling ROI. + * Leaving the previous window programmed is observable after an + * ROI-to-full-frame restart in fast-trigger mode: the frame identity + * is current, but the last output lines can still contain data from + * the preceding buffer. The power-on full-frame state has all four + * geometry registers cleared, so reproduce that state explicitly. + */ imx296_write(sensor, IMX296_FID0_ROI, 0, &ret); + imx296_write(sensor, IMX296_FID0_ROIPH1, 0, &ret); + imx296_write(sensor, IMX296_FID0_ROIPV1, 0, &ret); + imx296_write(sensor, IMX296_FID0_ROIWH1, 0, &ret); + imx296_write(sensor, IMX296_FID0_ROIWV1, 0, &ret); imx296_write(sensor, IMX296_MIPIC_AREA3W, IMX296_PIXEL_ARRAY_HEIGHT, &ret); } @@ -1348,7 +1418,10 @@ static int imx296_setup(struct imx296 *sensor) imx296_write(sensor, IMX296_GTTABLENUM, 0xc5, &ret); imx296_write(sensor, IMX296_CTRL418C, sensor->clk_params->ctrl418c, &ret); - imx296_write(sensor, IMX296_GAINDLY, IMX296_GAINDLY_1FRAME, &ret); + imx296_write(sensor, IMX296_GAINDLY, + sensor->pending_mode == IMX296_XTRIG_ONE_SHOT ? + IMX296_GAINDLY_0FRAME : IMX296_GAINDLY_1FRAME, + &ret); imx296_write(sensor, IMX296_BLKLEVEL, 0x03c, &ret); imx296_write(sensor, IMX296_VINT, IMX296_VINT_EN, &ret); @@ -1424,7 +1497,8 @@ static int imx296_restore_ctrls_for_mode_locked(struct imx296 *sensor, return imx296_apply_exposure_locked(sensor, sensor->exposure->val); } - return imx296_apply_vblank_locked(sensor, IMX296_VBLANK_DEFAULT); + /* XTRIG exposure is set by pulse width; VMAX must use the VTR baseline. */ + return imx296_apply_vblank_locked(sensor, IMX296_XTRIG_VBLANK); } static int imx296_stream_on(struct imx296 *sensor) @@ -1483,6 +1557,8 @@ static int imx296_mode_switch(struct imx296 *sensor, if (new_mode > IMX296_XTRIG_ONE_SHOT) return -EINVAL; + atomic_set(&imx296_fast_trigger_mode, + new_mode == IMX296_XTRIG_ONE_SHOT); sensor->pending_mode = new_mode; imx296_update_ctrl_visibility_locked(sensor, new_mode); @@ -1677,6 +1753,18 @@ static int imx296_s_stream(struct v4l2_subdev *sd, int enable) sensor->active_mode = sensor->pending_mode; sensor->streaming = true; + /* + * The datasheet specifies one invalid frame after ROI geometry changes. + * CamOS restarts the sensor in free-run before switching to fast trigger, + * so let that invalid frame drain before userspace can change run mode. + */ + if (sensor->roi_boundary_pending && + sensor->active_mode == IMX296_FREE_RUN) { + usleep_range(IMX296_ROI_INVALID_FRAME_WAIT_US, + IMX296_ROI_INVALID_FRAME_WAIT_US + 5000U); + sensor->roi_boundary_pending = false; + } + if (sensor->active_mode == IMX296_FREE_RUN && sensor->light_source_enabled) imx296_light_source_set_locked(sensor, true); @@ -1980,6 +2068,12 @@ static int imx296_set_selection(struct v4l2_subdev *sd, format->height = rect.height; } + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE && + (rect.left != crop->left || rect.top != crop->top || + rect.width != crop->width || rect.height != crop->height)) { + sensor->roi_boundary_pending = true; + } + *crop = rect; sel->r = rect; diff --git a/drivers/media/platform/rockchip/isp/capture.c b/drivers/media/platform/rockchip/isp/capture.c index dd69891d0df60..06c16ab358822 100644 --- a/drivers/media/platform/rockchip/isp/capture.c +++ b/drivers/media/platform/rockchip/isp/capture.c @@ -600,6 +600,50 @@ void rkisp_stream_buf_done_early(struct rkisp_device *dev) } } +static enum hrtimer_restart rkisp_early_done_timer(struct hrtimer *timer) +{ + struct rkisp_capture_device *cap_dev = + container_of(timer, struct rkisp_capture_device, early_done_timer); + + rkisp_stream_buf_done_early(cap_dev->ispdev); + atomic_set(&cap_dev->early_done_pending, 0); + return HRTIMER_NORESTART; +} + +void rkisp_stream_buf_done_early_delayed(struct rkisp_device *dev, + u32 delay_us) +{ + struct rkisp_capture_device *cap_dev = &dev->cap_dev; + + if (!delay_us) { + rkisp_stream_buf_done_early(dev); + return; + } + + /* One line IRQ is expected per SOF. Do not move an already armed deadline. */ + if (atomic_cmpxchg(&cap_dev->early_done_pending, 0, 1)) + return; + + hrtimer_start(&cap_dev->early_done_timer, + ns_to_ktime((u64)delay_us * NSEC_PER_USEC), + HRTIMER_MODE_REL); +} + +void rkisp_stream_cancel_early_done(struct rkisp_device *dev) +{ + struct rkisp_capture_device *cap_dev = &dev->cap_dev; + int ret; + + if (in_interrupt()) { + ret = hrtimer_try_to_cancel(&cap_dev->early_done_timer); + if (ret < 0) + return; + } else { + hrtimer_cancel(&cap_dev->early_done_timer); + } + atomic_set(&cap_dev->early_done_pending, 0); +} + int rkisp_stream_buf_cnt(struct rkisp_stream *stream) { unsigned long lock_flags = 0; @@ -1911,6 +1955,10 @@ int rkisp_register_stream_vdevs(struct rkisp_device *dev) memset(cap_dev, 0, sizeof(*cap_dev)); cap_dev->ispdev = dev; atomic_set(&cap_dev->refcnt, 0); + atomic_set(&cap_dev->early_done_pending, 0); + hrtimer_init(&cap_dev->early_done_timer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + cap_dev->early_done_timer.function = rkisp_early_done_timer; if (dev->isp_ver <= ISP_V13) { if (dev->isp_ver == ISP_V12) { @@ -1962,6 +2010,8 @@ int rkisp_register_stream_vdevs(struct rkisp_device *dev) void rkisp_unregister_stream_vdevs(struct rkisp_device *dev) { + rkisp_stream_cancel_early_done(dev); + if (dev->isp_ver <= ISP_V13) rkisp_unregister_stream_v1x(dev); else if (dev->isp_ver == ISP_V20) diff --git a/drivers/media/platform/rockchip/isp/capture.h b/drivers/media/platform/rockchip/isp/capture.h index 82fe5e0d7c66a..38c796fb44dbb 100644 --- a/drivers/media/platform/rockchip/isp/capture.h +++ b/drivers/media/platform/rockchip/isp/capture.h @@ -35,6 +35,7 @@ #ifndef _RKISP_PATH_VIDEO_H #define _RKISP_PATH_VIDEO_H +#include #include #include "common.h" @@ -302,6 +303,7 @@ struct rkisp_stream { bool is_crop_upd; bool is_using_resmem; bool frame_early; + u64 early_done_sof_ns; bool need_scl_upd; bool is_attach_info; wait_queue_head_t done; @@ -334,6 +336,9 @@ struct rkisp_capture_device { struct tasklet_struct rd_tasklet; atomic_t refcnt; u32 wait_line; + u32 early_done_delay_us; + struct hrtimer early_done_timer; + atomic_t early_done_pending; u32 wrap_width; u32 wrap_line; bool is_done_early; @@ -348,6 +353,9 @@ extern struct rockit_isp_ops rockit_isp_ops; void rkisp_stream_vir_cpy_image(struct work_struct *work); void rkisp_stream_buf_done_early(struct rkisp_device *dev); +void rkisp_stream_buf_done_early_delayed(struct rkisp_device *dev, + u32 delay_us); +void rkisp_stream_cancel_early_done(struct rkisp_device *dev); void rkisp_stream_buf_done(struct rkisp_stream *stream, struct rkisp_buffer *buf); void rkisp_unregister_stream_vdev(struct rkisp_stream *stream); diff --git a/drivers/media/platform/rockchip/isp/capture_v39.c b/drivers/media/platform/rockchip/isp/capture_v39.c index 0242e065c488d..a742d32ad72ea 100644 --- a/drivers/media/platform/rockchip/isp/capture_v39.c +++ b/drivers/media/platform/rockchip/isp/capture_v39.c @@ -1039,6 +1039,7 @@ static int mi_frame_end(struct rkisp_stream *stream, u32 state) struct capture_fmt *isp_fmt = &stream->out_isp_fmt; unsigned long lock_flags = 0; struct rkisp_buffer *buf = NULL; + u64 sof_ns = dev->isp_sdev.frm_timestamp; u32 i, seq; if (stream->id == RKISP_STREAM_VIR) @@ -1050,12 +1051,26 @@ static int mi_frame_end(struct rkisp_stream *stream, u32 state) if (stream->id == RKISP_STREAM_MP && dev->cap_dev.wrap_line) return 0; spin_lock_irqsave(&stream->vbq_lock, lock_flags); + /* + * wait_line can fire very close to the normal MI frame interrupt. + * If IRQ wins first, FRAME_WORK must not consume the next buffer; + * if FRAME_WORK wins first, IRQ must only advance the queue. The + * SOF timestamp uniquely identifies both callbacks as one frame. + */ + if (sof_ns && stream->early_done_sof_ns == sof_ns) { + spin_unlock_irqrestore(&stream->vbq_lock, lock_flags); + if (state == FRAME_IRQ) + goto end; + return 0; + } if (state == FRAME_IRQ && stream->curr_buf) stream->frame_early = false; else stream->frame_early = true; buf = stream->curr_buf; stream->curr_buf = NULL; + if (buf && sof_ns) + stream->early_done_sof_ns = sof_ns; spin_unlock_irqrestore(&stream->vbq_lock, lock_flags); if ((!stream->frame_early && state == FRAME_WORK) || (stream->frame_early && state == FRAME_IRQ)) @@ -1126,7 +1141,6 @@ static int mi_frame_end(struct rkisp_stream *stream, u32 state) stream->dbg.delay = ns - dev->isp_sdev.frm_timestamp; stream->dbg.timestamp = ns; stream->dbg.id = seq; - if (vir->streaming && vir->conn_id == stream->id) { spin_lock_irqsave(&vir->vbq_lock, lock_flags); list_add_tail(&buf->queue, &dev->cap_dev.vir_cpy.queue); @@ -1490,6 +1504,7 @@ rkisp_start_streaming(struct vb2_queue *queue, unsigned int count) } memset(&stream->dbg, 0, sizeof(stream->dbg)); + stream->early_done_sof_ns = 0; atomic_inc(&dev->cap_dev.refcnt); if (!dev->isp_inp || !stream->linked) { diff --git a/drivers/media/platform/rockchip/isp/rkisp.c b/drivers/media/platform/rockchip/isp/rkisp.c index ca8d8e35bcae7..65cdad538eeda 100644 --- a/drivers/media/platform/rockchip/isp/rkisp.c +++ b/drivers/media/platform/rockchip/isp/rkisp.c @@ -56,6 +56,18 @@ #define ISP_V4L2_EVENT_ELEMS 4 #define ISP_SUBDEV_NAME DRIVER_NAME "-isp-subdev" + +#ifndef V4L2_CID_USER_IMX296_BASE +#define V4L2_CID_USER_IMX296_BASE (V4L2_CID_USER_BASE + 0x10d0) +#endif +#define V4L2_CID_IMX296_OP_MODE (V4L2_CID_USER_IMX296_BASE + 0x1) +#define IMX296_XTRIG_ONE_SHOT 1 +#define ISP39_OUT_LINE_COUNTER_TAIL 8 + +static unsigned int rkisp_imx296_tail_wait_us = 50000; +module_param_named(imx296_tail_wait_us, rkisp_imx296_tail_wait_us, uint, 0644); +MODULE_PARM_DESC(imx296_tail_wait_us, + "V39 IMX296 fast-trigger tail DMA hrtimer delay before buffer done (us)"); /* * NOTE: MIPI controller and input MUX are also configured in this file, * because ISP Subdev is not only describe ISP submodule(input size,format, output size, format), @@ -89,6 +101,36 @@ static void rkisp_config_cmsk(struct rkisp_device *dev); static void rkisp_config_aiisp(struct rkisp_device *dev); static void rkisp_config_fpn(struct rkisp_device *dev); +#if IS_REACHABLE(CONFIG_VIDEO_IMX296) +extern bool imx296_is_fast_trigger_active(void); +#endif + +static bool rkisp_imx296_fast_trigger_active(struct rkisp_device *dev) +{ + struct v4l2_subdev *sensor; + struct v4l2_ctrl *mode; + + /* Direct sensor-to-ISP topologies can read the control normally. */ + if (dev->active_sensor && dev->active_sensor->sd) { + sensor = dev->active_sensor->sd; + mode = v4l2_ctrl_find(sensor->ctrl_handler, + V4L2_CID_IMX296_OP_MODE); + if (mode) + return v4l2_ctrl_g_ctrl(mode) == IMX296_XTRIG_ONE_SHOT; + } + + /* + * RK3576 routes IMX296 -> RKCIF -> RKISP across two media devices. + * RKISP therefore sees the CIF bridge as active_sensor and cannot walk + * to the IMX296 control handler. Use the sensor's read-only mode bridge. + */ +#if IS_REACHABLE(CONFIG_VIDEO_IMX296) + return imx296_is_fast_trigger_active(); +#else + return false; +#endif +} + static inline struct rkisp_device *sd_to_isp_dev(struct v4l2_subdev *sd) { return container_of(sd->v4l2_dev, struct rkisp_device, v4l2_dev); @@ -2218,6 +2260,7 @@ static int rkisp_isp_stop(struct rkisp_device *dev) v4l2_dbg(1, rkisp_debug, &dev->v4l2_dev, "%s refcnt:%d\n", __func__, atomic_read(&hw->refcnt)); + rkisp_stream_cancel_early_done(dev); if (atomic_read(&hw->refcnt) > 1) goto end; @@ -2346,12 +2389,39 @@ static int rkisp_isp_stop(struct rkisp_device *dev) static int rkisp_isp_start(struct rkisp_device *dev) { struct rkisp_hw_dev *hw = dev->hw_dev; + u32 height = dev->isp_sdev.out_crop.height; u32 val; v4l2_dbg(1, rkisp_debug, &dev->v4l2_dev, "%s refcnt:%d link_num:%d\n", __func__, atomic_read(&hw->refcnt), hw->dev_link_num); + /* + * IMX296 Fast Trigger keeps the MIPI frame open until the next XTRIG. + * Complete the current ISP buffer at the last observable V39 output line + * so a trigger publishes its own frame instead of N-1. The V39 output + * line counter stops eight counts below the active height (for example, + * 1080 for a 1088-line frame), so height - 1 can never assert the line + * interrupt. Program one count before that terminal value, then keep the + * buffer owned by the ISP for a short tail-DMA drain interval before + * vb2_done. Free-run and other sensors retain the configured/default + * wait_line behavior and have no added delay. + */ + dev->cap_dev.early_done_delay_us = 0; + rkisp_stream_cancel_early_done(dev); + if (dev->isp_ver == ISP_V39 && height > 1 && + rkisp_imx296_fast_trigger_active(dev)) { + dev->cap_dev.wait_line = + height > ISP39_OUT_LINE_COUNTER_TAIL + 1 ? + height - ISP39_OUT_LINE_COUNTER_TAIL - 1 : 1; + dev->cap_dev.early_done_delay_us = + min_t(u32, rkisp_imx296_tail_wait_us, 100000U); + v4l2_info(&dev->v4l2_dev, + "IMX296 fast trigger: early buffer done at line %u/%u, tail wait %u us\n", + dev->cap_dev.wait_line, height, + dev->cap_dev.early_done_delay_us); + } + dev->cap_dev.is_done_early = false; if (dev->cap_dev.wait_line >= dev->isp_sdev.out_crop.height) dev->cap_dev.wait_line = 0; @@ -3858,7 +3928,15 @@ static void rkisp_queue_event_aiisp(struct rkisp_device *dev, u32 irq) rd_line += dev->aiisp_cfg.rd_linecnt; if (rd_line > h) rd_line = h - 1; - rkisp_write(dev, ISP32_ISP_IRQ_CFG0, rd_line, true); + /* + * IRQ_CFG0 low 16 bits belong to AIISP's quarter-line + * interrupt; high 16 bits belong to capture early-done. + * Preserve the IMX296 Fast Trigger threshold when AIISP + * advances its read line at runtime. + */ + rkisp_write(dev, ISP32_ISP_IRQ_CFG0, + (rd_line & 0xffff) | + (dev->cap_dev.wait_line << 16), true); } } else { wr_line = ISP39_AIISP_WR_LINECNT(val); @@ -3901,7 +3979,8 @@ static void rkisp_config_aiisp(struct rkisp_device *dev) irq_mask = ISP39_AIISP_LINECNT_DONE | ISP3X_OUT_FRM_QUARTER; en_mask = ISP39_AIISP_EN; - rd_line = dev->aiisp_cfg.rd_linecnt; + rd_line = (dev->aiisp_cfg.rd_linecnt & 0xffff) | + (dev->cap_dev.wait_line << 16); wr_line = dev->aiisp_cfg.wr_linecnt << 16; rkisp_write(dev, ISP32_ISP_IRQ_CFG0, rd_line, false); @@ -5087,7 +5166,14 @@ void rkisp_isp_isr(unsigned int isp_mis, if (isp_mis & ISP3X_OUT_FRM_HALF) { writel(ISP3X_OUT_FRM_HALF, base + CIF_ISP_ICR); rkisp_dvbm_event(dev, ISP3X_OUT_FRM_HALF); - rkisp_stream_buf_done_early(dev); + /* + * V39 exposes the last usable line interrupt before all tail lines + * have reached memory. Arm a high-resolution timer and keep ownership + * until those writes drain; doing vb2_done immediately can expose + * green/stale bottom rows to userspace. The timer avoids a long udelay + * in this hard-IRQ handler. + */ + rkisp_stream_buf_done_early_delayed(dev, dev->cap_dev.early_done_delay_us); } if (isp_mis & ISP3X_OUT_FRM_END) { writel(ISP3X_OUT_FRM_END, base + CIF_ISP_ICR); @@ -5112,4 +5198,3 @@ irqreturn_t rkisp_vs_isr_handler(int irq, void *ctx) return IRQ_HANDLED; } -