Skip to content

temp/libcamera: refit the imx362 focus map, and give both sensors lens shading - #19

Draft
Jertlok wants to merge 7 commits into
taimen-bringupfrom
taimen/af-range
Draft

Jertlok wants to merge 7 commits into
taimen-bringupfrom
taimen/af-range

Conversation

@Jertlok

@Jertlok Jertlok commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Two camera calibration fixes for taimen, both measured rather than assumed.

The focus map was fitted to a lens that does not exist

The old map was fitted while the kernel exposed FOCUS_ABSOLUTE as hall +324..-257 — a narrow slice on the far side of the travel, taken from the vendor's af_tuning region params. Nothing closer than roughly 0.6 m could be focused in that range at all.

Measured by commanding the actuator's target register directly and reading the hall sensor back: the servo tracks from -257 to 1112, landing on this module's OTP macro code within two counts. A subject at 0.222 m then peaks at hall ~530 with a 13x sharpness ratio — about 200 counts beyond anything the old range could command.

The kernel change that opens the range to 0..1369 has to land with this one; the codes here mean nothing against an older driver. Two anchors are measured, the 10 dioptre point is extrapolated to the vendor's stated MinFocusDistance and says so.

Lens shading was never wired up

The soft ISP has had a shading stage since the GPU debayer work and both modules have a 17x13 grid in their OTP, but no tuning file carried one, so every frame this device has taken was uncorrected — about two stops of vignetting.

Verified on a flat field: corners come back at 98.7% of centre, where this sensor uncorrected sits near 24%.

The mapping is right too, which needed a second shot to establish. One flat field showed a 17.6% left-to-right tilt that could equally have been the grid sampled off-centre or the room's lighting. Turning the phone 180 degrees flipped the tilt's sign (+17.6% to -7.9%) and the dark corner moved with the room rather than with the frame. A grid mapped wrongly is fixed to the sensor and would not have rotated.

Generated by bringup/camera/otp-to-lsc.py, so another module or another port can be read out the same way.

Sign-off is yours to add before merge.

Stage 2 of run 35075286983 died at ninja edge 2077 with

  ninja: job failed with status 1: python3 ../../third_party/dawn/tools/generate-sources-gn.py gen
  failed to initialize build cache at /home/pmos/.cache/go-build: mkdir /home/pmos/.cache/go-build: file exists

That path is a symlink pmbootstrap makes to /mnt/pmbootstrap/go/gocache,
a directory inside the cache_go bind mount. Both the symlink and the
directory are created by pmb.chroot.init(), but only on the way that
creates the chroot: for one that exists it mounts and returns.

chromium-state.sh stores the prep layer with --exclude='./cache_*/*', so
the cache directories are restored empty and gocache is not restored at
all. Every stage that re-enters the work directory therefore has a
dangling /home/pmos/.cache/go-build, and Go's os.MkdirAll() on a dangling
symlink returns EEXIST: stat() fails, mkdir() then hits the symlink. Go
prints "not a directory" for a regular file and succeeds on a missing
one, so this message only ever means the symlink. The three cargo
directories under cache_rust are missing for the same reason.

Stage 1 was not spared, it just stopped at edge 1206 before any Go action
ran.

Recreate the missing targets in enter(), after pmb.chroot.init(), which
is where this script already redoes what run_abuild() sets up. Only the
missing ones: a target that is there but wrong keeps failing where it is
used, instead of being quietly replaced here.

The caches stay out of the stored state: ninja records a finished Go
action in .ninja_log, so no later stage reruns it, and a build cache that
is only used within one stage is not worth 2 GB of release assets.

Assisted-by: Claude
Not observed yet: only the package stage writes to $WORK/packages, and no
run has reached it.

run_abuild() chowns $WORK/packages to the chroot's build user (12345), but
only on the branch that creates the directory. In a restored work
directory it exists, and it belongs to the runner's uid: the prep
container's exit trap in run-pmbootstrap.sh hands /work/packages back to
HOST_UID so the runner can read it, and that is the owner the prep layer
was tarred with and restores to. abuild runs as pmos and would get EACCES
writing the apk into $REPODEST/pmos/aarch64, after however many hours the
package stage spent on check.

Do what run_abuild() does, in enter(), where this script already redoes
its setup. The container's exit trap still hands the directory back to the
runner for the upload step.

Assisted-by: Claude
The old map was fitted while the kernel still exposed FOCUS_ABSOLUTE as
hall +324..-257 -- a narrow slice on the far side of the travel, taken
from the vendor's af_tuning region params. Nothing closer than roughly
0.6 m could be focused in that range at all, so the map described a
lens that does not exist.

Measured 2026-09-16 by commanding the actuator's target register
directly and reading the hall sensor back: the servo tracks from -257
to 1112, landing on this module's OTP macro code within two counts. A
subject at 0.222 m (laser, +/-3%) then peaks at hall ~530 with a 13x
sharpness ratio -- about 200 counts beyond anything the old range could
command.

The kernel change that opens the range to 0..1369 has to land with this
one: the codes here mean nothing against an older driver.

Two anchors are measured, the 10 dioptre point is extrapolated to the
vendor's stated MinFocusDistance and says so.

Assisted-by: Claude
The soft ISP has had a lens shading stage since the GPU debayer work,
and both modules have a 17x13 shading grid in their OTP, but no tuning
file carried one -- so every frame this device has ever taken was
uncorrected.

The OTP stores the measured response: 1023 at the centre falling to
roughly 250 in the corners on the rear sensor and 221 on the front,
about two stops of vignetting. These grids are its reciprocal, peaking
at 4.11x and 4.63x, both inside the shader's 5.0 ceiling. Gr and Gb are
averaged into one green. Generated by bringup/camera/otp-to-lsc.py
rather than pasted, so another module -- or another port -- can be read
out the same way.

Verified on a flat field: corners come back at 98.7% of centre, where
this sensor uncorrected sits near 24%.

The mapping is right too, which needed a second shot to establish. A
single flat field showed a 17.6% left-to-right tilt that could equally
have been the grid sampled off-centre or the room's lighting. Turning
the phone 180 degrees flipped the tilt's sign (+17.6% to -7.9%) and the
dark corner moved with the room rather than with the frame. A grid
mapped wrongly is fixed to the sensor and would not have rotated; the
residual is illumination, not correction.

Assisted-by: Claude
@Jertlok
Jertlok marked this pull request as draft September 16, 2026 13:15

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant