From e8ab0f799bb11547d26f7dcecd148637523426a9 Mon Sep 17 00:00:00 2001 From: Claudius Date: Thu, 12 Nov 2026 17:16:56 +0000 Subject: [PATCH 1/6] Camera: the driver as it was before the S9's rear camera work, kept as hakios.exynos_camera_star2.c This is drivers/camera/exynos/hakios.exynos_camera.c of 13c4fce8, byte for byte, under a second name: the file Anan Jaser's three Galaxy S9+ camera commits were written on. Main's own file has since gone another way (the S9's rear camera in hakios.exynos_camera_rear.c, the lens in hakios.ak737x.c). His commits follow, applied to this copy, so that his work lands as he ran it; the commit after them wires it into the build for star2lte only. Not built yet. --- .../exynos/hakios.exynos_camera_star2.c | 3050 +++++++++++++++++ 1 file changed, 3050 insertions(+) create mode 100644 drivers/camera/exynos/hakios.exynos_camera_star2.c diff --git a/drivers/camera/exynos/hakios.exynos_camera_star2.c b/drivers/camera/exynos/hakios.exynos_camera_star2.c new file mode 100644 index 000000000..35b0c50ea --- /dev/null +++ b/drivers/camera/exynos/hakios.exynos_camera_star2.c @@ -0,0 +1,3050 @@ +#define HAKIOS_SOC_FIXED +/* + * The Galaxy S9's cameras (starlte, Exynos 9810): the front camera, a Samsung S5K3H1 (Android's camera 1, + * "fimc-is_sensor_3h1"), from its supplies to one raw frame in memory, without the ISP's libraries. Only with boot-arg + * cam_s9=N on this phone; nothing is written at cam_s9=0 (the default). Every address and number below belongs to this + * phone, from its device tree and the S9 kernel's camera driver (fimc-is2), read as data. + * + * STAGE 1, THE SENSOR (cam_s9=1, 2) + * the sensor 0x35 (7 bits; 0x6A as Samsung's node name writes it) on hsi2c@108F0000, the second I2C of USI08 + * of PERIC1 ("USI08_I2C"), 400 kHz; its pins gpp5-2 (SCL) and gpp5-3 (SDA), function 3 ("on_i2c"), + * function 0, inputs, when it is off ("off_i2c"). 16-bit register addresses, MIPI CCS's map: the + * model ID at 0x0000 (16 bits), the revision at 0x0002, the frame count at 0x0005 (0xff: not + * streaming), mode_select at 0x0100 + * reset gpg2-5 of PERIC0's GPIO (0x104300C0), an output, high = running + * MCLK CIS_CLK1 of CMU_TOP (0x1A240000): MUX_CLKCMU_CIS_CLK1 0x101C [0] (0: OSCCLK, 26 MHz), the divider + * 0x1818 [4:0], the gate 0x201C (bit 20 manual, 21 on), its Q-channel 0x3008; Android asks 26 MHz + * (exynos9810_fimc_is_sensor_mclk_on), which is OSCCLK undivided. Out on gpc1-5 of PERIC1's GPIO + * (0x10830080), function 2 ("fimc_is_mclk1_fn"); output low (function 1) when off + * the supplies all five on the S2MPB02 (0x59 on hsi2c@10920000, USI10 of PERIC1: the flashlight's chip, see + * hakios.s2mpb02_torch.c). An LDO's control register: bit 7 on, [6:0] the voltage, 600 mV + n steps + * (12.5 mV for LDOs 1-5, 25 mV for 6-18): + * VDDD_1.05V_VT LDO5, L5CTRL 0x22, 1.05 V = 36 (0x24) + * VDDIO_1.8V_VT LDO6, L6CTRL 0x23, 1.8 V = 48 (0x30) + * VDDA_2.8V_VT LDO13, L13CTRL 0x2A, 2.8 V = 88 (0x58) + * VDDAF_2.8V_VT LDO16, L16CTRL 0x2D, 2.8 V = 88 (0x58) + * and BUCK2, VDD_EXT_1P3_PB02, 1.3 V: B2CTRL1 0x0D [7:6] the enable (3 on, the regulator's enable + * mask 0xC0), B2CTRL2 0x0E the voltage, 400 mV + 6.25 mV n: 1.3 V = 144 (0x90); enable time 100 us. + * The star boards define USE_BUCK2_REGULATOR_CONTROL (fimc-is-vendor-config_star.h), and the 3H1's + * module file then switches BUCK2 on first and off last: the 1.3 V the low-voltage LDOs (VDDD's + * LDO5 among them) are made from. Without it VDDD is "on" with nothing behind it, and the sensor does + * not answer (the first phone run, 2 November: 0x35 NACKed, INT_STAT's no-device bit, 0x400) + * power-up (the module file's, fimc-is-device-module-3h1.c, star's): CIS_CLK1 on at 26 MHz; reset low; BUCK2 + * at 1.3 V and on; VDDAF on; VDDA on; VDDIO on; VDDD at 1.05 V and on, 1 ms; the bus pins to I2C, 10 + * us; reset high; MCLK out on its pin, 9 ms. Each supply is given 200 us (the regulator's enable + * times are 100 us for a buck, 180 us for an LDO). If the model ID is not read: 20 ms more and again, + * then the bus's and the pins' registers logged and the bus scanned (below) + * power-down MCLK's pin to an output, low; reset low; the bus pins back to inputs; VDDAF, VDDD, VDDA, VDDIO off, + * BUCK2 off (the module file's order); CIS_CLK1's gate, Q-channel, mux and divider as found. BUCK2's + * voltage register stays at 1.3 V, the value Linux's regulator core gives it at boot (min = max) + * the scan only when the sensor does not answer at 0x35: a one-byte READ at every 7-bit address 0x08-0x77 + * (nothing is written to anyone), the addresses that acknowledge listed; then, of those, only the + * other addresses a 3H1 can be strapped to (0x10, 0x1A, 0x20, 0x2D, 0x36, 0x5A: never 0x50-0x57, an + * EEPROM's) are asked for the model ID (a 16-bit register address written, two bytes read), and + * one that gives an ID other than 0 and 0xffff is used for the run. cam_s9_addr=A forces one + * + * STAGE 2, CSIS1 AND ITS DMA (cam_s9=3, 4) + * the domain ISPPRE (pd-isppre@14064048): the PMU's ISPPRE_CONFIGURATION 0x14064048 [3:0] = 0xF, its STATUS + * 0x1406404C [3:0] waited for 0xF (pmucal isppre_on); off: [3:0] = 0, STATUS 0. Its parent in the + * device tree is pd-dpu (DPU_STATUS 0x14064024), which the display keeps on: checked, never touched. + * need_smc = 0x16210004: after the power comes, SMC 0x82000410 (1, 0x16210004, 0), the DTZPC + * restore (exynos_pd_tz_restore; without it the bus refuses the first access, an SError, as the G3D + * did in hakios.mali.c); before it goes, the low-power setting of pmucal_lpm_init for it, + * MEMORY_ISPPRE_OPTION 0x14064730 [1:0] = 2, then SMC 0x82000410 (0, 0x16210004, 0), the DTZPC save. + * A domain found on is used as it is: no SMC, and it is left on. + * its clocks CMU_ISPPRE 0x16200000: PLL_CON0_MUX_CLKCMU_ISPPRE_BUS_USER +0x100 (bit 4: CMU_TOP's + * CLKCMU_ISPPRE_BUS rather than OSCCLK, bit 7 busy), DIV_CLK_ISPPRE_BUSP +0x1804; the Q-channels + * (GATE_IS_ISPPRE_CSISn in the clock driver are Q-channels: bit 0 hardware gating, bit 1 the clock + * requested) of the four CSISs +0x3020-0x302C (Linux's sensor path asks all four on), the PDP's + * DMA +0x3038 and the SysMMU +0x305C: bit 1 set (put back as found when the domain was found on). + * CMU_TOP's CLKCMU_ISPPRE_BUS: mux 0x1094 [1:0], divider 0x1890 [3:0], gate 0x209C (bit 20 manual, + * 21 on): the gate held on for the run and put back as found + * CSIS1 the link, 0x16240000 (fimc-is-hw-csi-v5_1.h): CSIS_VERSION 0x0, CSIS_CMN_CTRL 0x4 (bit 0 CSI_EN, 1 + * SW_RESET, [9:8] the lanes less one, [11:10] INTERLEAVE_MODE, [19:16] UPDATE_SHADOW, 20 QCHANNEL_EN, + * 21 PHY_SEL: 0 D-PHY), INT_MSK0/SRC0 0x10/0x14 (FRAMESTART [27:24], FRAMEEND [23:20], ERR_SOT_HS + * [19:16], LOST_FS [15:12], LOST_FE [11:8], OVER 4, WRONG_CFG 3, ECC 2, CRC 1, ID 0), INT_MSK1/SRC1 + * 0x18/0x1C (VRESOL [15:12], HRESOL [11:8] mismatches), ISP_CONFIG_CHn 0x40 + 0x10 n (VC [1:0], + * DATAFORMAT [7:2], PIXEL_MODE [13:12]: 1 dual), ISP_RESOL_CHn 0x44 + 0x10 n (height [31:16], + * width [15:0]), FRM_CNT_CH0 0x100, PHY_STATUS 0x700, PHY_CMN_CTRL 0x704 (bit 0 the clock lane, + * [4:1] the data lanes). The reset: SW_RESET set and waited for to clear, 10 x 10 us. + * its WDMA VC0's at 0x162C0000: DMA0_CTRL 0x0 (bit 0 the output on, 1 UPDT_PTR_EN, [4:2] UPDT_FRAMEPTR), + * DMA0_FMT 0x4 ([1:0] 0 RAW10, 3 PACK12, 4 the storage: 0 legacy, 5 the dimension: 0 2D), + * DMA0_ADDR1-8 0x10-0x2C (32-bit addresses: all eight given the one buffer, as csi_hw_s_dma_addr + * does), DMA0_ACT_CTRL 0x30, DMA0_RESOL 0x40 [13:0] the width, DMA0_FCNTSEQ_STAT 0x54; its DMA + * common at +0x400: DMA_DATA_CTRL 0x0 (bit 0 the input: 0 OTF), DMA_INT_MASK 0x4 (0x00F11F00, the + * BSP's), DMA_INT_SRC 0x8 (write 1 to clear: FRM_START bit 4, FRM_END 8, DMA_ERROR 12, ABORT_DONE + * 16, OTF_OVERLAP 20, for VC0), DMA_ERR_CODE 0x18, OTF_FORMAT 0x1C (0 RAW10). The other CSISs' DMA + * commons (0x162B0400, 0x162D0400, 0x162E0400) are read, never written, for the log + * the DMA common 0x162F0000, of all four CSISs (fimc_is@16290000's first region): COMMON_DMA_ARB_PRI 0x0 ([3:2] 1, + * [1:0] 2), COMMON_DMA_CTRL 0x8 (bit 1 IP_PROCESSING), COMMON_DMA_SRAM_SPLIT 0xC (0x140 each), + * COMMON_DMA_MATRIX 0x10 (0), as csi_hw_s_dma_common sets them; its test pattern generator: + * TEST_PATTERN_ENABLE 0x20 (bit 31 on, 30 two pixels a clock, [29:0] the vertical blank in clocks), + * TEST_PATTERN_CTRL 0x24 (V-to-H blank [23:16] 16, H blank [15:8] 8, H-to-V blank [7:0] 2), + * TEST_PATTERN_RESOL 0x28 (height [29:16], width [13:0]). With the pattern on, Android does not + * enable the link (csi_stream_on): the pattern stands in for it + * the D-PHY M0S4S2's slave 0 (CSIS1): its isolation in the PMU at 0x14060730 (bit 0: 1 bypassed, the PHY in + * use; shared with CSIS3), its reset bit 2 of SYSREG_ISPPRE's 0x16210500 (cleared, then set: running) + * (phy-exynos8895-mipi.c, the device tree's dphy_m0s4s2_csis1). Its registers at 0x16180000 (the + * CSIS1 node's second region): DPHY_ACTRL_SC_06 +0x1018 = 2, DPHY_DCTRL_SC_02 +0x1084 [7:0] = 1 + * (CLKSETTLECTL), and for each of the two lanes (lane n at +0x400 n): DPHY_ACTRL_SDn_08 +0x143C + * [6:5] = 0 (the skew delay), DPHY_DCTRL_SDn_05 +0x14AC bit 2 = 1 (HS_MODE_SEL, below 1.5 Gb/s), + * DPHY_DCTRL_SDn_06 +0x14B0 [7:0] = the settle, DPHY_DCTRL_SDn_13 +0x14E0 bit 0 = 0 (no skew + * calibration below 1.5 Gb/s) (csi_hw_s_phy_default_value, csi_hw_s_phy_config) + * the SysMMU sysmmu@16310000 ("3AAM, 3AA, PDP": the CSIS nodes' iommus): MMU_CTRL 0x0 (bit 0 on), MMU_CFG 0x4, + * MMU_STATUS 0x8, MMU_VERSION 0x34, read only and only with cam_s9_mmu_probe=1. Off (its reset + * state after the domain's power-on), it passes the DMA's addresses through: the buffer's physical + * address is the DMA's. Found on, there is no frame: this driver gives it no table. Unprobed (the + * default), it is taken as off and never touched + * the buffer 4 MB below 4 GB (the WDMA's addresses are 32 bits). XNU has no memory there on the S9 (its RAM is + * the bank from 0x880000000: kmem's "below 4 GB" never succeeds), so it is physical memory XNU does + * not manage, in the device tree's gap: the camera's 32 MB from 0xD8000000 in sys/hakios.exynos9810_mem.h + * (the raw frames, then camshot's picture), above the display's buffers and the moved payload, + * below UFS's own-mode memory; never 0xF0000000 and above (the modem's, ABOX's). + * Mapped once through IOKit, write-combined (hakios_physmap_wc()), never with ml_io_map() + * (io_submap is 8 MB); filled with 0xA5 first, so that what the DMA wrote shows + * + * STAGE 3, A FRAME (cam_s9=5) + * the mode 1632 x 1224 RAW10 at 30 frames a second, two lanes at 719 Mb/s, VC0 (module-3h1's mode 5, with the + * embedded data on CH2 as its CSI_MODE_VC_DT config gives it): setfile A's (the device tree's + * "default") table sensor_3h1_setfile_A_1632x1224_30fps, 86 16-bit writes taken as given, after 3 ms + * (sensor_cis_set_registers); then 0x3092 = 0 (no companion chip: sensor_3h1_cis_mode_change). The + * table leaves 0x0340 (frame length) 0x04F2 lines, 0x0342 (line length) 0x2970 pixel clocks of + * 403.6 MHz: 26.3 us a line, 33.3 ms a frame. The exposure: coarse integration 0x0202 (cam_s9_exp, + * 1000 lines unless said; at most the frame's less 4) and analog gain 0x0204 (cam_s9_gain, 0x40: + * 32 is 1x, sensor_cis_calc_again_code) + * the settle 36 (fimc-is-hw-settle-10nm-lpp.h at 720 Mb/s, the table fimc-is-v6_0_0's chain takes) + * stream on 0x0104 = 0 (group hold off), 8 ms, 0x6028 = 0x4000, 0x0100 = 1, 0x0216 = 0 (no WDR) + * (sensor_3h1_cis_stream_on); the frame count 0x0005 leaves 0xff + * the link csi_hw_enable: UPDATE_SHADOW 0xF, PHY_SEL 0, the clock lane on, QCHANNEL_EN 0, CSI_EN 1 + * stream off 0x0104 = 0, 0x6028 = 0x4000, 0x0100 = 0; the frame count back to 0xff. The link: lanes off, + * CSI_EN 0, QCHANNEL_EN 1 (csi_hw_disable), the reset; the WDMA's output off + * + * THE STEPS. cam_s9=N makes the first N and stops; each is said before it is taken: + * 1 reads the pins, CMU_TOP's CIS_CLK1, the two buses as found, the S2MPB02's ID and the four LDOs' registers + * 2 writes the sensor powered up, its model ID, revision, frame count and mode read; powered down again (at 5: + * kept up for step 5) + * 3 writes the ISPPRE domain on (and its DTZPC restore), its clocks; reads CSIS1's version, its WDMA, the DMA + * common (the SysMMU's state with cam_s9_mmu_probe=1) + * 4 writes the D-PHY's isolation off and its reset released, CSIS1 reset and set up for 1632 x 1224 RAW10, the + * buffer given to VC0's WDMA (a stale DMA abort request in its DMA_CMN_CTRL cleared first), the test + * pattern generator on: first as Android has it, CSIS1's link left off (csi_stream_on with pattern_en), + * 500 ms; if no frame has started on CSIS1's WDMA by then, the pattern off and on again with the link + * enabled as csi_hw_enable does (no clock lane: no sensor), 1.5 s; then the WDMA and the pattern off. The + * other three CSISs' DMA interrupts are unmasked for the while (put back after), so the log says which + * WDMA the pattern reached if not CSIS1's. The buffer's checksum, its first bytes, how much of it changed + * 5 writes the sensor's mode table, the D-PHY's settle for 719 Mb/s, the link on, the ring (four 4 MB slots) + * given to VC0's WDMA's eight frame pointers, streaming; the auto-exposure: the green of the frame's middle + * metered on every third frame and the exposure stepped towards 18 % grey (coarse integration time, then + * analog gain, then digital gain, within the mode's 1266-line frame) until it is there or + * cam_s9_ae_frames frames have passed (15 unless said); that frame is the one taken: the WDMA off, streaming off; the + * counters, the link's errors, the checksum and the mean of a 64 x 64 patch in the middle (a black frame + * reads about 64 in 10 bits) + * 6 camshot: steps 1 to 5 as above at the start, then the thread stays for the USB console's "camshot [N] [tp]" + * (hakios.dwc3.c, usb_gadget=5): each one makes steps 2 to 5 again (with "tp", 3 and 4: the test pattern), + * everything off again, then the frame demosaiced on the processor (bilinear, the whole 1632 x 1224 at + * N = 1, every Nth pixel at N; the black level off, a grey-world balance kept to sane gains, a saturation, + * a gamma of about 1/2.2) and handed to the USB link, which sends it as it sends a shot of the panel. A + * capture that fails says "usb: camshot failed: ..." with CSIS1's and the WDMA's status. + * tools/usb-shot.py --cam + * 7 the stream: after step 1's look (nothing is powered), the sensor streams into the ring while something wants + * the frames, with the auto-exposure running all along; /dev/hakios-camera gives the newest frame to user + * space (the POCO's protocol, bsd/sys/hakios.camera.h: libhakios.media's capture device and camera_test + * read it). The first read after an open brings the camera up; it goes down again (the sensor, its + * supplies, the link, the domain) at the last close, or after STREAM_IDLE_MS without a read. camshot + * wakes it too, and takes its frame once the auto-exposure has had cam_s9_ae_frames frames. The log says + * the frame rate every ten seconds of a stream + * Whatever a run switched on is switched off again in the reverse order, also when a step fails, and at the run's end + * (loader_return(), a panic's way out too): the WDMA and the pattern first, then the sensor, the link, the PHY, the + * domain (if this run switched it on). A WDMA not seen to stop: no more DMA into the buffer for the run. + * + * Boot-args: + * cam_s9=N the front camera's first N steps: 1 a look, 2 the sensor's ID, 3 the ISPPRE domain and CSIS1, + * 4 one frame of the test pattern into memory, 5 one frame of the sensor's, 6 that and then the + * USB console's camshot, 7 the stream and /dev/hakios-camera (0 or none: nothing) + * cam_s9_always=1 with 7: the stream from the boot to the run's end, wanted or not (the old behaviour, for debugging) + * cam_s9_addr=A the sensor's 7-bit I2C address, if not 0x35 (the device tree's) and not found by the scan + * cam_s9_ipclk=MHZ the two HSI2Cs' input clock in MHz (default 400, as the phone showed for the other buses on 1 + * November: a wrong guess on the high side only makes the bus slower) + * cam_s9_exp=LINES the first exposure of step 5's frame in lines of 26.3 us (default 1000, at most 1262) + * cam_s9_gain=CODE the first analog gain of step 5's frame, 0x20 being 1x (default 0x40, at most 0x200) + * cam_s9_ae=0 no auto-exposure: the frames as cam_s9_exp and cam_s9_gain have them (default 1: on) + * cam_s9_ae_frames=N the auto-exposure's frames at most before a camshot's frame is taken (2 to 30, default 15) + * cam_s9_dgain=CODE the most digital gain the auto-exposure may use, 0x100 being 1x (default 0x400, at most 0x1000) + * cam_s9_sat=PCT camshot's colour saturation in per cent (default 140; 100: none; at most 300) + * cam_s9_pack12=1 the WDMA's PACK12 layout rather than RAW10 as it comes (the BSP sets PACK12 for its V4L2 + * SBGGR10 buffers). Without it the WDMA writes each RAW10 pixel in 16 bits, little-endian, the + * value in [9:0] (the phone, 3 November): camshot's debayer reads only that) + * cam_s9_bayer=N camshot's Bayer order: 0 RGGB, 1 GRBG (the default, the sensor's), 2 GBRG, 3 BGGR + * cam_s9_stride=B camshot's bytes a line of the frame (default 3264: the WDMA's RAW10 is a pixel in 16 bits) + * cam_s9_ispdiv=D CLKCMU_ISPPRE_BUS (CSIS1's and its WDMA's clock) = SHARED1_DIV4, 466 MHz, / (D + 1); default + * 1, 233 MHz (Android's own is 466 MHz, D = 0). The boot leaves it at about 50 MHz, too slow: + * CSIS1 overflows and the WDMA's FIFO fills a few lines into the frame + * cam_s9_probe=1 the risky reads too, for the log: CMU_ISPPRE's whole Q-channel sweep with each DBG_NFO (+0x4000), + * and the other three CSISs' DMA commons (read, and their interrupt masks for the pattern's while). + * Off by default: on 3 November the sweep's read of CMU_ISPPRE 0x303C took a synchronous external + * abort (isp_status(), the phone's cam-fix.img) + * cam_s9_mmu_probe=1 the SysMMU (0x16310000) mapped and read: MMU_CTRL/CFG/STATUS/VERSION in the log, and a SysMMU + * found translating stops the DMA. Off by default (its clock or Q-channel may be off, and a read + * then aborts, as the PCIe SysMMU's did): the SysMMU is taken as off, pass-through, and the DMA + * starts without asking it + * + * Limits, whatever asks. The S2MPB02 makes other supplies too (the flash LED, the rear camera's lens motor, the grip + * sensor, the proximity LED): every write to it goes through one function that takes only the four LDO registers and + * BUCK2's two above, only the values above (Android's voltages, on or off; BUCK2's control register only in its enable + * bits), and reads each back; a write that does not read back, or a byte + * nobody acknowledges, and everything is powered down in the power-down's order and nothing more is done for the run. + * A supply found on is left as it is and left on at the end; one found on at another voltage stops the run before + * anything is written (someone else has it). No other PMIC register is written. The flashlight's driver stays off in a + * run with cam_s9=N (the same bus, one driver per bus). Nothing of the ISPPRE block is read before its domain is seen + * on. The DMA is given only the buffer's address, checked to lie below 4 GB, and with the SysMMU taken as off (seen off, with cam_s9_mmu_probe=1). Every + * wait is bounded by the clock. + * + * Not written yet (docs/devices/starlte-camera.md): the CSIS's frame-end interrupt (the stream polls every millisecond), + * a colour matrix and lens shading of the module's own. + * + * Sources: the phone's device tree (exynos9810-starlte_eur_open_26.dts of the BSP: hsi2c@108F0000 and fimc-is-3h1@35, + * fimc-is_sensor_3h1@6A, s2mpb02_pmic@59 and its regulators (s2mpb02-buck2: VDD_EXT_1P3_PB02), the fimc_is_mclk1 pin states, hsi2c21-bus, usi@1081102C, + * pd-isppre@14064048 and pd-dpu, fimc_is_sensor@16240000, fimc_is@16290000, dphy_m0s4s2_csis1, sysmmu@16310000); + * the S9 kernel's fimc-is-device-module-3h1.c (and vendor/mcd/fimc-is-vendor-config_star.h: USE_BUCK2_REGULATOR_CONTROL), fimc-is-cis-3h1.c/.h (and its bayer_order, GR_BG, for camshot), fimc-is-cis-3h1-setA.h, fimc-is-cis.c, + * ischain/fimc-is-v6_0_0/setup-fimc-is-sensor.c, fimc-is-config.h and fimc-is-hw-chain.c/.h, + * include/fimc-is-hw-settle-10nm-lpp.h, include/fimc-is-hw.h, fimc-is-device-csi_v4.c, sensor/csi2/fimc-is-hw-csi-v5_1.c + * and .h, drivers/phy/phy-exynos8895-mipi.c, drivers/iommu/exynos-iommu.h, drivers/soc/samsung/exynos-pd.c, + * cal-if/pmucal_local.c, cal-if/ra.c, cal-if/exynos9810/flexpmu_cal_local_exynos9810.h and + * flexpmu_cal_system_exynos9810.h, cmucal-sfr.c, cmucal-node.c and cmucal-qch.c, drivers/clk/samsung/clk-exynos9810.c, + * s2mpb02-regulator.c, s2mpb02-private.h (BUCK2's registers, steps and enable mask), + * flexpmu_cal_local_exynos9810.h's isppre_save (the CMU_ISPPRE and SYSREG_ISPPRE registers logged at step 3), pinctrl-exynos.c (android_kernel_samsung_exynos9810, lineage-24.0, e5906667; + * GPL: read for these facts, not copied). The SMC's convention and the lesson of the half-off domain: hakios.mali.c. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool leaving; + +static int say(const char *format, ...) __printflike(1, 2); + +static int +say(const char *format, ...) +{ + char line[512]; + va_list list; + int length; + + va_start(list, format); + length = vsnprintf(line, sizeof(line), format, list); + va_end(list); + loader_log_line(line); + if (!leaving) { + printf("%s", line); + } + return length; +} + +/* ---- the S2MPB02 ---- */ + +#define PMIC 0x59 +#define PMIC_ID 0x00 +#define LDO_ON 0x80 +#define LDO_VSEL 0x7f +#define B2CTRL1 0x0D /* BUCK2, VDD_EXT_1P3_PB02: [7:6] the enable (3: on) */ +#define B2CTRL2 0x0E /* BUCK2's voltage: 400 mV + 6.25 mV n */ +#define BUCK_ON 0xC0 +#define BUCK2_VSEL 0x90 /* 1.3 V */ + +static const struct ldo { + const char *name; + uint8_t reg, vsel; + bool switched; /* by this driver */ +} ldos[] = { + { "VDDD_1.05V_VT (LDO5)", 0x22, 0x24, true }, + { "VDDIO_1.8V_VT (LDO6)", 0x23, 0x30, true }, + { "VDDA_2.8V_VT (LDO13)", 0x2A, 0x58, true }, + { "VDDAF_2.8V_VT (LDO16)", 0x2D, 0x58, true }, +}; +#define VDDD 0 +#define VDDIO 1 +#define VDDA 2 +#define VDDAF 3 +#define NLDOS (sizeof(ldos) / sizeof(ldos[0])) + +static struct hakios_hsi2c pmic_bus = { + .name = "cam-s2mpb02", + .regs_pa = EXYNOS_PERIC1_USI10_HSI2C0_PA, /* hsi2c@10920000: USI10 of PERIC1 */ + .usi_mode_pa = EXYNOS_PERIC1_USI10_MODE_PA, /* usi@1081103C: its mode word */ + .cmu_pa = EXYNOS_CMU_PERIC1_PA, .cmu_size = 0x4000, /* CMU_PERIC1 */ + .gates = { 0x2048, 0x20AC, 0x2114, 0x2118 }, .ngates = 4, + .qch = 0x3050, .div = 0x1828, .mux = 0x0120, /* DIV_CLK_PERIC1_USI10_USI; MUX_CLKCMU_PERIC1_IP_USER */ + .pins_pa = EXYNOS_GPIO_PERIC1_PA + 0x40, /* gpp6: SCL pin 0, SDA pin 1, function 2 */ + .pins_mask = 0xffu, .pins_want = 0x22u, + .ipclk_mhz = 400, .bus_hz = 100000, /* the device tree gives this bus no speed: Linux's default */ +}; + +/* ---- the sensor's bus ---- */ + +#define SENSOR 0x35 /* the device tree's (fimc-is-3h1@35, reg = <53>): 7 bits */ +static uint8_t sensor_addr = SENSOR; /* the one used: the scan's or cam_s9_addr's, if not 0x35 */ +static uint8_t sensor_addr_given = SENSOR; /* cam_s9_addr's, or 0x35 */ + +static struct hakios_hsi2c sensor_bus = { + .name = "cam-3h1", + .regs_pa = EXYNOS_PERIC1_USI08_HSI2C1_PA, /* hsi2c@108F0000: USI08_I2C of PERIC1 (hsi2c21) */ + .usi_mode_pa = EXYNOS_PERIC1_USI08_MODE_PA, /* usi@1081102C: USI08's mode word ("i2c" in the device tree) */ + .cmu_pa = EXYNOS_CMU_PERIC1_PA, .cmu_size = 0x4000, /* CMU_PERIC1 */ + /* GATE_CLK_PERIC1_USI_I2C, RSTNSYNC_CLK_PERIC1_USI08_I2C, USI08_I2C IPCLK, USI08_I2C PCLK */ + .gates = { 0x2050, 0x2098, 0x20EC, 0x20F0 }, .ngates = 4, + .qch = 0x303C, .div = 0x1830, .mux = 0x0120, /* QCH_CON_USI08_I2C_QCH; DIV_CLK_PERIC1_USI_I2C */ + .pins_pa = EXYNOS_GPIO_PERIC1_PA + 0x20, /* gpp5: SCL pin 2, SDA pin 3, function 3 */ + .pins_mask = 0xff00u, .pins_want = 0x3300u, + .ipclk_mhz = 400, .bus_hz = 400000, /* clock-frequency = <400000> */ +}; + +/* ---- pins and the clock ---- */ + +#define GPIO_PERIC0_PA EXYNOS_GPIO_PERIC0_PA +#define GPIO_PERIC1_PA EXYNOS_GPIO_PERIC1_PA +#define GPG2 0x0C0 /* PERIC0: the reset, pin 5 */ +#define GPC1 0x080 /* PERIC1: MCLK1, pin 5 */ +#define GPP5 0x020 /* PERIC1: the sensor's bus, pins 2 and 3 */ +#define RESET_PIN 5 +#define MCLK_PIN 5 + +#define CMU_TOP_PA EXYNOS_CMU_TOP_PA +#define CIS1_MUX EXYNOS_CAM_CIS1_MUX /* [0]: 0 OSCCLK, 1 DIV_PLL_SHARED2_DIV2 */ +#define CIS1_DIV EXYNOS_CAM_CIS1_DIV /* [4:0]: the ratio less one */ +#define CIS1_GATE EXYNOS_CAM_CIS1_GATE /* bit 20 manual, bit 21 on */ +#define CIS1_QCH EXYNOS_CAM_CIS1_QCH /* DMYQCH_CON_DFTMUX_TOP_QCH_CIS_CLK1: [0] enable, [1] clock request */ +#define GATE_MANUAL (1u << 20) +#define GATE_ON (1u << 21) + +static vm_offset_t peric0, peric1, cmu_top; +static uint8_t found[NLDOS]; /* each LDO's register as found */ +static bool mine[NLDOS]; /* switched on by this run */ +static uint8_t b2ctrl1_found, b2ctrl2_found; /* BUCK2's two registers as found */ +static bool buck2_mine; /* BUCK2 switched on by this run */ +static bool broken; /* a write did not read back, or the bus failed: no more writing */ +static bool powered; /* the sensor's power-up was begun */ +static uint32_t gate_found, mux_found, div_found, qch_found; +static bool clock_mine; + +static inline uint32_t +rd(vm_offset_t base, uint32_t off) +{ + return *(volatile uint32_t *)(base + off); +} + +static inline void +wr(vm_offset_t base, uint32_t off, uint32_t v) +{ + *(volatile uint32_t *)(base + off) = v; +} + +/* by the clock, never sleeping: also on the run's way out */ +static uint64_t +absolutetime_ms(uint64_t t) +{ + uint64_t ns; + + absolutetime_to_nanoseconds(t, &ns); + return ns / NSEC_PER_MSEC; +} + +static void +spin_us(uint32_t us) +{ + uint64_t until; + + nanoseconds_to_absolutetime((uint64_t)us * NSEC_PER_USEC, &until); + until += mach_absolute_time(); + while (mach_absolute_time() < until) { + } +} + +static uint32_t +pmic_get(uint8_t reg, uint8_t *value) +{ + return hakios_hsi2c_read_reg(&pmic_bus, PMIC, reg, value, 1); +} + +/* what may be written to the S2MPB02: the four LDOs at Android's voltage, on or off; BUCK2 at 1.3 V, on or off */ +static bool +pmic_allowed(uint8_t reg, uint8_t value) +{ + for (uint32_t i = 0; i < NLDOS; i++) { + if (ldos[i].switched && reg == ldos[i].reg) { + return value == ldos[i].vsel || value == (LDO_ON | ldos[i].vsel); + } + } + if (reg == B2CTRL2) { + return value == BUCK2_VSEL; + } + if (reg == B2CTRL1) { + return (value & ~BUCK_ON) == (b2ctrl1_found & ~BUCK_ON) && ((value & BUCK_ON) == 0 || (value & BUCK_ON) == BUCK_ON); + } + return false; +} + +/* The only way anything reaches the S2MPB02: a register and a value pmic_allowed() takes; read back. */ +static bool +pmic_put(uint8_t reg, uint8_t value, const char *name) +{ + uint8_t buf[2], back = 0; + bool same; + + if (broken || !pmic_allowed(reg, value)) { + if (!broken) { + say("exynos_camera: S2MPB02 %#x (%s) = %#x refused: not a value this driver writes\n", reg, name, value); + } + return false; + } + buf[0] = reg; + buf[1] = value; + if (hakios_hsi2c_message(&pmic_bus, PMIC, false, buf, 2, true) != 0 || pmic_get(reg, &back) != 0) { + same = false; + } else { + /* BUCK2's control register: its enable bits read back (the rest is as found and not ours) */ + same = reg == B2CTRL1 ? (back & BUCK_ON) == (value & BUCK_ON) : back == value; + } + if (!same) { + say("exynos_camera: S2MPB02 %#x (%s): wrote %#x, read back %#x (bus %#x): no more writing for this run\n", + reg, name, value, back, pmic_bus.last_error); + broken = true; + return false; + } + return true; +} + +static bool +ldo_put(uint32_t i, bool on) +{ + if (i >= NLDOS || !ldos[i].switched) { + return false; + } + return pmic_put(ldos[i].reg, (uint8_t)((on ? LDO_ON : 0) | ldos[i].vsel), ldos[i].name); +} + +/* BUCK2 (VDD_EXT_1P3_PB02) at 1.3 V and on, unless it was found on (then it is left as it is) */ +static bool +buck2_on(void) +{ + if ((b2ctrl1_found & BUCK_ON) == BUCK_ON) { + say("exynos_camera: 2: VDD_EXT_1P3_PB02 (BUCK2) was on already (%#x, voltage %#x): left as it is\n", + b2ctrl1_found, b2ctrl2_found); + return true; + } + if (b2ctrl2_found != BUCK2_VSEL && !pmic_put(B2CTRL2, BUCK2_VSEL, "BUCK2's voltage")) { + return false; + } + buck2_mine = true; + if (!pmic_put(B2CTRL1, (uint8_t)((b2ctrl1_found & ~BUCK_ON) | BUCK_ON), "VDD_EXT_1P3_PB02 (BUCK2)")) { + return false; + } + say("exynos_camera: 2: VDD_EXT_1P3_PB02 (BUCK2) on at 1.3 V (B2CTRL1 %#x, B2CTRL2 %#x)\n", + (b2ctrl1_found & ~BUCK_ON) | BUCK_ON, BUCK2_VSEL); + return true; +} + +static void +buck2_off(void) +{ + if (buck2_mine) { + bool was_broken = broken; + + broken = false; /* switching off is always tried */ + if (pmic_put(B2CTRL1, (uint8_t)(b2ctrl1_found & ~BUCK_ON), "VDD_EXT_1P3_PB02 (BUCK2)")) { + buck2_mine = false; + } + broken = broken || was_broken; + } +} + +/* an LDO to Android's voltage and on, unless it was found on (then it is left as it is) */ +static bool +ldo_on(uint32_t i) +{ + if (found[i] & LDO_ON) { + say("exynos_camera: 2: %s was on already (%#x): left as it is\n", ldos[i].name, found[i]); + return true; + } + if ((found[i] & LDO_VSEL) != ldos[i].vsel && !ldo_put(i, false)) { + return false; + } + mine[i] = true; + if (!ldo_put(i, true)) { + return false; + } + say("exynos_camera: 2: %s on (%#x)\n", ldos[i].name, LDO_ON | ldos[i].vsel); + spin_us(200); + return true; +} + +static void +ldo_off(uint32_t i) +{ + if (mine[i]) { + bool was_broken = broken; + + broken = false; /* switching off is always tried */ + if (ldo_put(i, false)) { + mine[i] = false; + } + broken = broken || was_broken; + } +} + +/* a pin: an output (CON nibble 1) at the level given */ +static void +pin_out(vm_offset_t bank, uint32_t number, bool high) +{ + uint32_t data = rd(bank, 4); + + wr(bank, 4, high ? data | (1u << number) : data & ~(1u << number)); + wr(bank, 0, (rd(bank, 0) & ~(0xfu << (4 * number))) | (1u << (4 * number))); +} + +static void +pin_function(vm_offset_t bank, uint32_t number, uint32_t function) +{ + wr(bank, 0, (rd(bank, 0) & ~(0xfu << (4 * number))) | (function << (4 * number))); +} + +static void +clock_on(void) +{ + gate_found = rd(cmu_top, CIS1_GATE); + mux_found = rd(cmu_top, CIS1_MUX); + div_found = rd(cmu_top, CIS1_DIV); + qch_found = rd(cmu_top, CIS1_QCH); + if ((mux_found & 1) != 0 || (div_found & 0x1f) != 0) { + /* the mux on OSCCLK and undivided: 26 MHz, what Android sets; written only when it is not so already */ + wr(cmu_top, CIS1_MUX, mux_found & ~1u); + wr(cmu_top, CIS1_DIV, div_found & ~0x1fu); + } + wr(cmu_top, CIS1_QCH, qch_found | 3); + wr(cmu_top, CIS1_GATE, gate_found | GATE_MANUAL | GATE_ON); + clock_mine = true; + say("exynos_camera: 2: CIS_CLK1: mux %x -> %x, divider %x -> %x, gate %x -> %x (26 MHz from OSCCLK)\n", mux_found, + rd(cmu_top, CIS1_MUX), div_found, rd(cmu_top, CIS1_DIV), gate_found, rd(cmu_top, CIS1_GATE)); +} + +/* The power-down, in the module file's order; only what this run switched on. */ +static void +power_down(void) +{ + if (!powered) { + return; + } + pin_out(peric1 + GPC1, MCLK_PIN, false); + pin_out(peric0 + GPG2, RESET_PIN, false); + spin_us(1000); + pin_function(peric1 + GPP5, 2, 0); + pin_function(peric1 + GPP5, 3, 0); + ldo_off(VDDAF); + ldo_off(VDDD); + ldo_off(VDDA); + ldo_off(VDDIO); + buck2_off(); + if (clock_mine) { + wr(cmu_top, CIS1_GATE, gate_found); + wr(cmu_top, CIS1_QCH, qch_found); + wr(cmu_top, CIS1_DIV, div_found); + wr(cmu_top, CIS1_MUX, mux_found); + clock_mine = false; + } + powered = false; + say("exynos_camera: powered down: MCLK and reset low, the bus pins inputs, BUCK2 and the LDOs %s; CIS_CLK1's gate " + "%x again\n", (mine[VDDD] || mine[VDDA] || mine[VDDIO] || mine[VDDAF] || buck2_mine) ? + "NOT ALL OFF (a write failed)" : "off as found", gate_found); +} + +/* a register of the sensor: its 16-bit address written, a repeated start, len bytes read */ +static uint32_t +sensor_get(uint16_t reg, uint8_t *buf, uint32_t len) +{ + uint8_t address[2] = { (uint8_t)(reg >> 8), (uint8_t)reg }; + uint32_t error = hakios_hsi2c_message(&sensor_bus, sensor_addr, false, address, 2, false); + + return error ? error : hakios_hsi2c_message(&sensor_bus, sensor_addr, true, buf, len, true); +} + +static void +look_pins(const char *when) +{ + say("exynos_camera: %s: reset gpg2-5 CON %x DAT %u; MCLK1 gpc1-5 CON %x DAT %u; bus gpp5-2/3 CON %x %x\n", when, + (rd(peric0 + GPG2, 0) >> (4 * RESET_PIN)) & 0xf, (rd(peric0 + GPG2, 4) >> RESET_PIN) & 1, + (rd(peric1 + GPC1, 0) >> (4 * MCLK_PIN)) & 0xf, (rd(peric1 + GPC1, 4) >> MCLK_PIN) & 1, + (rd(peric1 + GPP5, 0) >> 8) & 0xf, (rd(peric1 + GPP5, 0) >> 12) & 0xf); +} + +/* ---- stage 2: the ISPPRE domain, CSIS1, its D-PHY and its DMA ---- */ + +#define PMU_PA EXYNOS_PMU_PA +#define PMU_SIZE 0x5000 +#define PMU_DPHY_ISO_CSIS1 EXYNOS_CAM_PMU_DPHY_ISO_CSIS1 /* bit 0: 1 the isolation bypassed (CSIS1 and CSIS3's M0S4S2) */ +#define PMU_DPU_STATUS EXYNOS_CAM_PMU_DPU_STATUS /* pd-dpu, pd-isppre's parent */ +#define PMU_ISPPRE_CONFIG EXYNOS_CAM_PMU_ISPPRE_CONFIG +#define PMU_ISPPRE_STATUS EXYNOS_CAM_PMU_ISPPRE_STATUS +#define PMU_ISPPRE_MEMORY EXYNOS_CAM_PMU_ISPPRE_MEMORY /* MEMORY_ISPPRE_OPTION: [1:0] = 2 before the power goes */ +#define PD_ON 0xfu +#define PD_WAIT_US 10000u +#define ISO_BYPASS 1u + +#define DTZPC_SMC 0x82000410u /* Samsung's SiP call, SMC_CMD_PREPARE_PD_ONOFF */ +#define DTZPC_SAVE 0u +#define DTZPC_RESTORE 1u +#define DTZPC_ISPPRE EXYNOS_CAM_DTZPC_ISPPRE /* pd-isppre's need_smc */ + +#define CMU_ISPPRE_PA EXYNOS_CMU_ISPPRE_PA +#define ISPPRE_BUS_USER 0x0100 /* bit 4 MUX_SEL (1: CMU_TOP's CLKCMU_ISPPRE_BUS), bit 7 busy */ +#define ISPPRE_BUSP_DIV 0x1804 +#define QCH_CSIS1 EXYNOS_CAM_QCH_CSIS1 +#define QCH_SYSMMU EXYNOS_CAM_QCH_SYSMMU +/* + * the Q-channels asked on: the four CSISs' (as setup-fimc-is-sensor.c does), the PDP's DMA's and the SysMMU's, and the + * way the WDMA's writes leave the block: LHS_AXI_D_ISPPRE (+0x306C, the AXI master to the bus) and BTM_ISPPRE (+0x3010, + * its traffic monitor), both in pmucal's isppre_save list + */ +static const uint32_t qchs[] = { 0x3020, QCH_CSIS1, 0x3028, 0x302C, 0x3038, QCH_SYSMMU, 0x306C, 0x3010 }; +#define NQCHS (sizeof(qchs) / sizeof(qchs[0])) +#define QCH_REQ (1u << 1) +#define TOP_ISPPRE_MUX EXYNOS_CAM_TOP_ISPPRE_MUX /* CMU_TOP: MUX_CLKCMU_ISPPRE_BUS [1:0] */ +#define TOP_ISPPRE_DIV EXYNOS_CAM_TOP_ISPPRE_DIV /* DIV_CLKCMU_ISPPRE_BUS [3:0] */ +#define TOP_ISPPRE_GATE EXYNOS_CAM_TOP_ISPPRE_GATE /* GATE_CLKCMU_ISPPRE_BUS: bit 20 manual, 21 on */ +#define TOP_BUSY (1u << 16) /* the mux's and the divider's busy bit */ +#define ISPPRE_MUX_SHARED1_DIV4 1u /* the mux's parents: 0 SHARED0_DIV4, 1 SHARED1_DIV4, 2 SHARED2_DIV2, 3 */ +#define ISPPRE_CTRL_OPTION 0x0800 /* ISPPRE_CMU_ISPPRE_CONTROLLER_OPTION: [29:28] the CMU's power management */ +#define QCH_FIRST EXYNOS_CAM_QCH_FIRST /* QCH_CON_*: 0x3010 (BTM) to 0x3070 (SYSREG), bit 0 enable, 1 clock request */ +#define QCH_LAST EXYNOS_CAM_QCH_LAST +#define QCH_DBG EXYNOS_CAM_QCH_DBG /* DBG_NFO_QCH_CON_*: each QCH_CON's state at +0x4000 */ +#define SYSREG_DRCG_EN EXYNOS_CAM_SYSREG_DRCG_EN /* SYSREG_ISPPRE's BUS_COMPONENT_DRCG_EN */ + +#define SYSREG_ISPPRE_PA EXYNOS_SYSREG_ISPPRE_PA +#define DPHY_RESET 0x0500 /* the MIPI PHYs' resets: bit 2 CSIS1's M0S4S2 (1: running) */ +#define DPHY_RESET_CSIS1 (1u << 2) + +#define CSIS1_PA EXYNOS_CSIS1_PA +#define CSIS_VERSION 0x000 +#define CSIS_CMN_CTRL 0x004 +#define CSIS_CLK_CTRL 0x008 /* CLKGATE_TRAIL [31:16], CLKGATE_EN [7:4] (reset 0xF), read only here */ +#define CSIS_INT_MSK0 0x010 +#define CSIS_INT_SRC0 0x014 +#define CSIS_INT_MSK1 0x018 +#define CSIS_INT_SRC1 0x01C +#define CSIS_ISP_CONFIG(n) (0x040 + 0x10 * (n)) +#define CSIS_ISP_RESOL(n) (0x044 + 0x10 * (n)) +#define CSIS_FRM_CNT_CH0 0x100 +#define CSIS_PHY_STATUS 0x700 +#define CSIS_PHY_CMN_CTRL 0x704 +#define CMN_CSI_EN (1u << 0) +#define CMN_SW_RESET (1u << 1) +#define CMN_LANES_SHIFT 8 /* [9:8] the lanes less one */ +#define CMN_INTERLEAVE_SHIFT 10 /* [11:10]: 3 CSI_MODE_VC_DT */ +#define CMN_UPDATE_SHADOW (0xfu << 16) +#define CMN_QCHANNEL_EN (1u << 20) +#define CMN_PHY_SEL (1u << 21) +#define PHY_ENABLE_CLK (1u << 0) +#define PHY_ENABLE_DAT_SHIFT 1 /* [4:1] */ +#define CSIS_IRQ_MASK0 0x011FFF1Fu /* the BSP's default masks */ +#define CSIS_IRQ_MASK1 0x0000FFF0u + +#define CSIS1_PHY_PA EXYNOS_CSIS1_PHY_PA +#define DPHY_ACTRL_SC_06 0x1018 +#define DPHY_DCTRL_SC_02 0x1084 +#define DPHY_LANE(n) (0x400u * (n)) +#define DPHY_ACTRL_SD_08 0x143C /* [6:5] SKEW_DLYSEL */ +#define DPHY_DCTRL_SD_05 0x14AC /* bit 2 HS_MODE_SEL */ +#define DPHY_DCTRL_SD_06 0x14B0 /* [7:0] HSSETTLECTL */ +#define DPHY_DCTRL_SD_13 0x14E0 /* bit 0 SKEWCAL_EN */ +#define SETTLE_719 36 /* fimc-is-hw-settle-10nm-lpp.h at 720 */ + +#define CSIS1_WDMA_PA EXYNOS_CSIS1_WDMA_PA /* VC0's; the DMA common of CSIS1 at +0x400 */ +#define DMA_CTRL 0x000 +#define DMA_FMT 0x004 +#define DMA_ADDR(n) (0x010 + 4 * (n)) +#define DMA_ACT_CTRL 0x030 +#define DMA_RESOL 0x040 +#define DMA_FCNTSEQ_STAT 0x054 +#define DMA_ON (1u << 0) /* the BSP's DMA_N_DISABLE field, which its code sets to switch it on */ +#define DMA_UPDT_PTR_EN (1u << 1) +#define DMA_FRAMEPTR_MASK (7u << 2) +#define DMA_PACK12 (1u << 3) +#define VCMN 0x400 /* the DMA common of this CSIS: */ +#define VCMN_DATA_CTRL 0x00 +#define VCMN_INT_MASK 0x04 +#define VCMN_INT_SRC 0x08 +#define VCMN_CRC_RESULT 0x0C +#define VCMN_CMN_CTRL 0x14 /* DMA_CMN_CTRL: bit 0 DMA_ABORT_REQ */ +#define VCMN_ERR_CODE 0x18 +#define VCMN_OTF_FORMAT 0x1C +#define VCMN_IRQ_MASK 0x00F11F00u +#define DMA_FRM_START0 (1u << 4) +#define DMA_FRM_END0 (1u << 8) +#define DMA_ERROR (1u << 12) +#define DMA_ABORT_DONE (1u << 16) +#define DMA_OVERLAP0 (1u << 20) + +#define CSIS_DMA_PA EXYNOS_CSIS_DMA_PA /* the DMA common of the four */ +#define CDMA_ARB_PRI 0x00 +#define CDMA_CTRL 0x08 +#define CDMA_SRAM_SPLIT 0x0C +#define CDMA_MATRIX 0x10 +#define CDMA_CLK_CTRL 0x14 /* COMMON_DMA_CLK_CTRL: bit 0 CLKGATE_OFF, read only here */ +#define TP_ENABLE 0x20 +#define TP_CTRL 0x24 +#define TP_RESOL 0x28 +#define TP_ON (1u << 31) +#define TP_DUAL (1u << 30) +#define TP_VBLANK 100000u /* clocks: a short pause before the frame, whatever the clock is */ + +#define SYSMMU_PA EXYNOS_CSIS_SYSMMU_PA +#define MMU_CTRL 0x000 +#define MMU_CFG 0x004 +#define MMU_STATUS 0x008 +#define MMU_VERSION 0x034 + +#define HW_FORMAT_RAW10 0x2B +#define HW_FORMAT_EMBEDDED 0x12 +#define HW_FORMAT_UNKNOWN 0x3F +#define FRAME_W 1632 +#define FRAME_H 1224 +#define LINE_BYTES (FRAME_W * 2) /* the WDMA's RAW10 (PACK12 off): a pixel in 16 bits, little-endian, [9:0] */ +#define BUF_BYTES (4u << 20) +#define CANARY 0xA5 +/* + * The frame's memory: none of XNU's own is below 4 GB on the S9 (its RAM is the bank from 0x880000000), so it is + * physical memory of the lower bank that XNU does not manage: the camera's range in the table of that bank, + * sys/hakios.exynos9810_mem.h, 32 MB from 0xD8000000 to 0xD9FFFFFF (the display's scan-out buffers below it, UFS's + * own-mode memory above): a ring of four raw frames of 4 MB each (the WDMA's eight frame pointers take them in turn, pointer i + * slot i % 4), then camshot's still (a raw frame copied out of the ring while the sensor streams on, cam_s9=7), then + * its RGB picture (1632 x 1224 x 3). Mapped once, write-combined, through IOKit (hakios_physmap_wc(), + * sys/hakios.physmap.h), never ml_io_map(). Nothing at or above 0xF0000000 (the modem's and ABOX's memory) is ever + * given to the DMA. + */ +#define MEM_PA EXYNOS9810_MEM_CAMERA_PA +#define MEM_BYTES ((uint32_t)EXYNOS9810_MEM_SIZE(CAMERA)) +#define MEM_FLOOR EXYNOS9810_MEM_DPU_SCAN_END /* the display's scan-out buffers end here */ +#define MEM_CEILING EXYNOS9810_MEM_CAMERA_END /* UFS's own-mode memory */ +#define RING_SLOTS 4 +#define SLOT_BYTES BUF_BYTES /* a raw frame's place: 3264 x 1224 = 3995136 bytes, in 4 MB */ +#define STILL_OFF (RING_SLOTS * SLOT_BYTES) /* camshot's copy of a frame of the stream */ +#define RGB_OFF (STILL_OFF + SLOT_BYTES) /* camshot's picture */ +#define RGB_BYTES (FRAME_W * FRAME_H * 3) + +static const uint64_t other_cmn_pa[3] = { 0x162B0400, 0x162D0400, 0x162E0400 }; /* CSIS0, 2, 3: read only */ + +static vm_offset_t pmu, cmu_isppre, sysreg_isppre, csis, dphy, wdma, cdma, mmu, other_cmn[3]; +static bool domain_mine; /* this run switched ISPPRE on */ +static bool domain_up; /* ISPPRE is on and its blocks may be read */ +static bool phy_mine, link_on, dma_on, pattern_on, streaming; +static uint32_t iso_found, dphy_reset_found, top_gate_found, cdma_found[4]; +static bool top_gate_mine, cdma_mine, clocks_mine; +static uint32_t top_mux_found, top_div_found; +static bool top_rate_mine; /* CLKCMU_ISPPRE_BUS's mux and divider are this run's */ +static uint32_t cam_ispdiv = 1; /* cam_s9_ispdiv: CLKCMU_ISPPRE_BUS = SHARED1_DIV4 (466 MHz) / (1 + this) */ +static uint32_t user_found, isppre_qch_found[NQCHS]; +static vm_offset_t buffer; /* the frame's (write-combined: MEM_PA's mapping) */ +static vm_offset_t frame; /* the frame looked at: the ring's slot of the frame taken, or the still */ +static uint64_t buffer_pa; +static bool buffer_busy; /* the WDMA was not seen to stop: no more DMA into it this run */ +static uint32_t frame_last; /* buffer_report()'s: the bytes up to the last one the DMA changed */ +static uint32_t frame_ends; /* sensor_frame()'s frame ends */ +static uint32_t cam_exp = 1000, cam_gain = 0x40, cam_pack12; +static uint32_t cam_probe, cam_mmu_probe; /* cam_s9_probe, cam_s9_mmu_probe: the risky reads, off by default */ + +/* the SysMMU found translating; never read unless cam_s9_mmu_probe=1 (else taken as off: pass-through) */ +static bool +mmu_translating(void) +{ + return cam_mmu_probe && mmu != 0 && (rd(mmu, MMU_CTRL) & 1); +} + + +/* One SMC to EL3 (through Carbon at EL2): x0 back. */ +static uint64_t +smc(uint64_t function, uint64_t first, uint64_t second, uint64_t third) +{ + register uint64_t x0 __asm__("x0") = function; + register uint64_t x1 __asm__("x1") = first; + register uint64_t x2 __asm__("x2") = second; + register uint64_t x3 __asm__("x3") = third; + + __asm__ volatile ("smc #0" : "+r" (x0), "+r" (x1), "+r" (x2), "+r" (x3) : : + "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "memory"); + return x0; +} + +/* (reg & mask) == want within us microseconds, by the clock; sleeping a millisecond at a time when it may */ +static bool +wait_for(vm_offset_t base, uint32_t off, uint32_t mask, uint32_t want, uint32_t us, bool may_sleep) +{ + uint64_t until; + + nanoseconds_to_absolutetime((uint64_t)us * NSEC_PER_USEC, &until); + until += mach_absolute_time(); + for (;;) { + if ((rd(base, off) & mask) == want) { + return true; + } + if (mach_absolute_time() > until) { + return (rd(base, off) & mask) == want; + } + if (may_sleep && !leaving) { + delay_for_interval(1, NSEC_PER_MSEC); + } else { + spin_us(10); + } + } +} + +static void +cache_flush(vm_offset_t start, uint32_t bytes) +{ + for (vm_offset_t line = start; line < start + bytes; line += 64) { + __asm__ volatile ("dc civac, %0" : : "r" (line) : "memory"); + } + __asm__ volatile ("dsb sy" : : : "memory"); +} + +/* ---- the sensor's registers, written ---- */ + +static bool sensor_broken; + +static bool +sensor_put(uint16_t reg, uint16_t value, uint32_t bytes) +{ + uint8_t buf[4] = { (uint8_t)(reg >> 8), (uint8_t)reg, 0, 0 }; + uint32_t error; + + if (bytes == 2) { + buf[2] = (uint8_t)(value >> 8); + buf[3] = (uint8_t)value; + } else { + buf[2] = (uint8_t)value; + } + error = hakios_hsi2c_message(&sensor_bus, sensor_addr, false, buf, 2 + bytes, true); + if (error != 0) { + say("exynos_camera: the sensor did not take %#06x = %#x (%x)\n", reg, value, error); + sensor_broken = true; + return false; + } + return true; +} + +/* + * Setfile A's 1632 x 1224 at 30 frames a second (fimc-is-cis-3h1-setA.h, sensor_3h1_setfile_A_1632x1224_30fps): the + * sensor's own register values, as data. Every one is a 16-bit write. + */ +static const uint16_t mode_1632x1224[][2] = { + { 0xFCFC, 0x4000 }, { 0x6028, 0x2000 }, { 0x6214, 0x7971 }, { 0x6218, 0x7150 }, { 0x0344, 0x0010 }, + { 0x0348, 0x0CCF }, { 0x0346, 0x0010 }, { 0x034A, 0x099F }, { 0x034C, 0x0660 }, { 0x034E, 0x04C8 }, + { 0x0408, 0x0000 }, { 0x040A, 0x0000 }, { 0x0342, 0x2970 }, { 0x0340, 0x04F2 }, { 0x0900, 0x0112 }, + { 0x0386, 0x0003 }, { 0x0400, 0x0001 }, { 0x0404, 0x0020 }, { 0x379E, 0x0104 }, { 0x31B8, 0x0020 }, + { 0x37FA, 0x0032 }, { 0xF46C, 0x0010 }, { 0xF414, 0x64C6 }, { 0xF416, 0x002F }, { 0xF462, 0x0000 }, + { 0xF464, 0x0000 }, { 0xF476, 0x0082 }, { 0x37C2, 0x0804 }, { 0x37C6, 0x0403 }, { 0x37BE, 0x0909 }, + { 0x37BA, 0x130D }, { 0xF44C, 0x0014 }, { 0xF452, 0x000B }, { 0xF44E, 0x0012 }, { 0x37DE, 0x0000 }, + { 0x37E0, 0x7FE7 }, { 0x37E2, 0x03FF }, { 0x37F0, 0x007F }, { 0x0200, 0x13B8 }, { 0x021C, 0x13B8 }, + { 0x3158, 0x0060 }, { 0x3254, 0x0001 }, { 0x3258, 0x0001 }, { 0x325C, 0x0003 }, { 0x3260, 0x0002 }, + { 0x3298, 0x000A }, { 0x32B8, 0x0002 }, { 0x32BC, 0x0000 }, { 0x31B2, 0x0200 }, { 0x3ACE, 0xFECD }, + { 0x3B00, 0xFEF6 }, { 0x3B32, 0xFEF6 }, { 0x3E0C, 0x0FF7 }, { 0x3DF8, 0x1008 }, { 0x3E02, 0x1008 }, + { 0x3AC4, 0xFF29 }, { 0x3AF6, 0xFF3E }, { 0x3B28, 0xFF3E }, { 0x3E0A, 0x0FF7 }, { 0x3DF6, 0x1000 }, + { 0x3E00, 0x1000 }, { 0x3ABA, 0xFF43 }, { 0x3AEC, 0xFF7B }, { 0x3B1E, 0xFF7B }, { 0x3E08, 0x1000 }, + { 0x3DF4, 0x0FF7 }, { 0x3DFE, 0x0FF7 }, { 0x3DE4, 0x0001 }, { 0x3AA4, 0x0100 }, { 0xF4A2, 0x000C }, + { 0x0136, 0x1A00 }, { 0x1130, 0x43CA }, { 0x1132, 0x0000 }, { 0x0304, 0x0007 }, { 0x0306, 0x00A3 }, + { 0x030C, 0x0003 }, { 0x030E, 0x0053 }, { 0x300A, 0x0001 }, { 0x3092, 0x0000 }, { 0x31AA, 0x0001 }, + { 0xB134, 0x0000 }, { 0x307C, 0x0340 }, { 0x3052, 0x0104 }, { 0x6214, 0x7970 }, { 0x0216, 0x0000 }, +}; +#define MODE_ENTRIES (sizeof(mode_1632x1224) / sizeof(mode_1632x1224[0])) +#define MODE_LINES 0x04F2 /* 0x0340 as the table leaves it */ +#define MODE_MARGIN 4 /* SENSOR_3H1_COARSE_INTEGRATION_TIME_MAX_MARGIN */ + +/* ---- stopping things (also on the way out: no sleeping, bounded) ---- */ + +static bool others_watched; /* the other CSISs' DMA interrupt masks are this run's (step 4) */ +static void others_watch(bool on); + +static void +dma_stop(void) +{ + if (wdma == 0 || !domain_up) { + return; + } + if (dma_on) { + wr(wdma, DMA_CTRL, rd(wdma, DMA_CTRL) & ~(DMA_ON | DMA_UPDT_PTR_EN)); + dma_on = false; + } + if (pattern_on) { + wr(cdma, TP_ENABLE, rd(cdma, TP_ENABLE) & ~TP_ON); + pattern_on = false; + } +} + +/* the WDMA seen idle: its active state off, or its domain gone */ +static bool +dma_quiet(void) +{ + if (!domain_up) { + return true; + } + return wait_for(wdma, DMA_ACT_CTRL, DMA_ON, 0, 100000, false); +} + +/* the WDMA's frame in flight dropped, as the BSP's DMA cancel does it (DMA_ABORT_REQ, then the request taken back) */ +static void +dma_abort(void) +{ + if (!domain_up) { + return; + } + wr(wdma, VCMN + VCMN_INT_SRC, DMA_ABORT_DONE); + wr(wdma, VCMN + VCMN_CMN_CTRL, rd(wdma, VCMN + VCMN_CMN_CTRL) | 1u); + (void)wait_for(wdma, VCMN + VCMN_INT_SRC, DMA_ABORT_DONE, DMA_ABORT_DONE, 10000, false); + wr(wdma, VCMN + VCMN_CMN_CTRL, rd(wdma, VCMN + VCMN_CMN_CTRL) & ~1u); + wr(wdma, VCMN + VCMN_INT_SRC, DMA_ABORT_DONE); +} + +static void +stream_stop(void) +{ + if (!streaming) { + return; + } + (void)sensor_put(0x0104, 0x00, 1); + (void)sensor_put(0x6028, 0x4000, 2); + (void)sensor_put(0x0100, 0x00, 1); + streaming = false; + say("exynos_camera: the sensor told to stop streaming (0x0100 = 0)\n"); +} + +static void +link_stop(void) +{ + if (!link_on || !domain_up) { + return; + } + wr(csis, CSIS_PHY_CMN_CTRL, rd(csis, CSIS_PHY_CMN_CTRL) & ~(PHY_ENABLE_CLK | (0xfu << PHY_ENABLE_DAT_SHIFT))); + wr(csis, CSIS_CMN_CTRL, (rd(csis, CSIS_CMN_CTRL) & ~CMN_CSI_EN) | CMN_QCHANNEL_EN); + wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) | CMN_SW_RESET); + (void)wait_for(csis, CSIS_CMN_CTRL, CMN_SW_RESET, 0, 100, false); + wr(csis, CSIS_INT_MSK0, 0); + wr(csis, CSIS_INT_MSK1, 0); + link_on = false; +} + +/* the PHY's reset held and its isolation back as found */ +static void +phy_off(void) +{ + if (!phy_mine) { + return; + } + if (domain_up && (dphy_reset_found & DPHY_RESET_CSIS1) == 0) { + wr(sysreg_isppre, DPHY_RESET, rd(sysreg_isppre, DPHY_RESET) & ~DPHY_RESET_CSIS1); + } + if ((iso_found & ISO_BYPASS) == 0) { + wr(pmu, PMU_DPHY_ISO_CSIS1, rd(pmu, PMU_DPHY_ISO_CSIS1) & ~ISO_BYPASS); + } + phy_mine = false; +} + +/* + * The domain off again, if this run switched it on (hakios.mali.c's lesson: the low-power setting first, the DTZPC + * save, then the power; one that does not go off is switched on again with its protection restored, and left on). + */ +static void +isp_down(void) +{ + uint64_t answer; + uint32_t was; + + dma_stop(); + if (others_watched) { + others_watch(false); + } + link_stop(); + phy_off(); + if (cdma_mine && domain_up) { + wr(cdma, CDMA_ARB_PRI, cdma_found[0]); + wr(cdma, CDMA_CTRL, cdma_found[1]); + wr(cdma, CDMA_SRAM_SPLIT, cdma_found[2]); + wr(cdma, CDMA_MATRIX, cdma_found[3]); + cdma_mine = false; + } + if (clocks_mine && !domain_mine && domain_up) { + /* a domain the boot left on stays on: its clocks as they were found */ + for (uint32_t i = 0; i < NQCHS; i++) { + wr(cmu_isppre, qchs[i], isppre_qch_found[i]); + } + wr(cmu_isppre, ISPPRE_BUS_USER, user_found); + } + clocks_mine = false; + if (domain_mine) { + was = rd(pmu, PMU_ISPPRE_MEMORY); + wr(pmu, PMU_ISPPRE_MEMORY, (was & ~3u) | 2u); + answer = smc(DTZPC_SMC, DTZPC_SAVE, DTZPC_ISPPRE, 0); + say("exynos_camera: the ISPPRE domain off again: MEMORY_ISPPRE_OPTION %#x -> %#x; SMC %#x (%u, %#x): the DTZPC " + "save, returned %#llx\n", was, rd(pmu, PMU_ISPPRE_MEMORY), DTZPC_SMC, DTZPC_SAVE, DTZPC_ISPPRE, answer); + if ((uint32_t)answer != 0) { + say("exynos_camera: the protection was not saved: the domain is left ON\n"); + } else { + domain_up = false; + wr(pmu, PMU_ISPPRE_CONFIG, rd(pmu, PMU_ISPPRE_CONFIG) & ~PD_ON); + if (!wait_for(pmu, PMU_ISPPRE_STATUS, PD_ON, 0, PD_WAIT_US, false)) { + say("exynos_camera: the domain did not go off (status %#x): on again, its protection restored, left on\n", + rd(pmu, PMU_ISPPRE_STATUS)); + wr(pmu, PMU_ISPPRE_CONFIG, rd(pmu, PMU_ISPPRE_CONFIG) | PD_ON); + if (wait_for(pmu, PMU_ISPPRE_STATUS, PD_ON, PD_ON, PD_WAIT_US, false)) { + answer = smc(DTZPC_SMC, DTZPC_RESTORE, DTZPC_ISPPRE, 0); + say("exynos_camera: on again; the DTZPC restore returned %#llx\n", answer); + } else { + say("exynos_camera: NOT ON AGAIN EITHER: status %#x\n", rd(pmu, PMU_ISPPRE_STATUS)); + } + } + } + domain_mine = false; + say("exynos_camera: ISPPRE: configuration %#x, status %#x\n", rd(pmu, PMU_ISPPRE_CONFIG), + rd(pmu, PMU_ISPPRE_STATUS)); + } + if (top_rate_mine) { + /* the divider first, then the mux: the way back goes through the slower of the two */ + wr(cmu_top, TOP_ISPPRE_DIV, top_div_found); + (void)wait_for(cmu_top, TOP_ISPPRE_DIV, TOP_BUSY, 0, 1000, false); + wr(cmu_top, TOP_ISPPRE_MUX, top_mux_found); + (void)wait_for(cmu_top, TOP_ISPPRE_MUX, TOP_BUSY, 0, 1000, false); + top_rate_mine = false; + } + if (top_gate_mine) { + wr(cmu_top, TOP_ISPPRE_GATE, top_gate_found); + top_gate_mine = false; + } +} + +/* ---- step 3: the domain on, its clocks, what is there ---- */ + +/* the power domain's and CMU_ISPPRE's state, every Q-channel with its debug state, for the log (reads only) */ +static void +isp_status(const char *step) +{ + char line[2][300]; + uint32_t len[2] = { 0, 0 }; + + say("exynos_camera: %s: PD: ISPPRE configuration %#x, status %#x (0xf: on), DPU status %#x; CMU_TOP CLKCMU_ISPPRE_BUS " + "mux %#x, divider %#x, gate %#x; CMU_ISPPRE: BUS_USER %#x, PLL_CON2 %#x, BUSP divider %#x, CONTROLLER_OPTION %#x; " + "SYSREG_ISPPRE DRCG_EN %#x, MIPI PHY resets %#x\n", step, rd(pmu, PMU_ISPPRE_CONFIG), rd(pmu, PMU_ISPPRE_STATUS), + rd(pmu, PMU_DPU_STATUS), rd(cmu_top, TOP_ISPPRE_MUX), rd(cmu_top, TOP_ISPPRE_DIV), rd(cmu_top, TOP_ISPPRE_GATE), + rd(cmu_isppre, ISPPRE_BUS_USER), rd(cmu_isppre, 0x0108), rd(cmu_isppre, ISPPRE_BUSP_DIV), + rd(cmu_isppre, ISPPRE_CTRL_OPTION), rd(sysreg_isppre, SYSREG_DRCG_EN), rd(sysreg_isppre, DPHY_RESET)); + if (!cam_probe) { + return; /* the Q-channel sweep aborted on CMU_ISPPRE 0x303C: cam_s9_probe=1 only */ + } + for (uint32_t off = QCH_FIRST; off <= QCH_LAST; off += 4) { + uint32_t h = off < 0x3040 ? 0 : 1; + + if (len[h] < sizeof(line[h])) { + len[h] += (uint32_t)snprintf(line[h] + len[h], sizeof(line[h]) - len[h], " %x:%x/%x", off & 0xff, + rd(cmu_isppre, off), rd(cmu_isppre, off + QCH_DBG)); + } + } + say("exynos_camera: %s: CMU_ISPPRE Q-channels, 0x30XX: QCH_CON/DBG_NFO (10 BTM, 14 CMU, 18 3AA, 1c 3AAM, 20-2c " + "CSIS0-3, 30/34 PDP cores, 38 PDP_DMA, 3c PDP_STAT_DMA):%s\n", step, line[0]); + say("exynos_camera: %s: CMU_ISPPRE Q-channels, 0x30XX (40/44 PGEN, 48 PPMU, 4c-58 QE, 5c SysMMU, 60 AXI_P, 64/68 ATB, " + "6c AXI_D, 70 SYSREG):%s\n", step, line[1]); +} + +static bool +isp_up(void) +{ + uint64_t answer; + uint32_t status, user; + + say("exynos_camera: 3: map the PMU; read pd-dpu (the parent) and pd-isppre\n"); + if (pmu == 0) { + pmu = ml_io_map(PMU_PA, PMU_SIZE); /* once a run: camshot comes back here (io_submap is 8 MB) */ + } + if (pmu == 0) { + say("exynos_camera: 3: the PMU could not be mapped\n"); + return false; + } + iso_found = rd(pmu, PMU_DPHY_ISO_CSIS1); + status = rd(pmu, PMU_ISPPRE_STATUS); + say("exynos_camera: 3: DPU status %#x; ISPPRE configuration %#x, status %#x; MEMORY_ISPPRE_OPTION %#x; the D-PHY's " + "isolation (0x14060730) %#x\n", rd(pmu, PMU_DPU_STATUS), rd(pmu, PMU_ISPPRE_CONFIG), status, + rd(pmu, PMU_ISPPRE_MEMORY), iso_found); + if ((rd(pmu, PMU_DPU_STATUS) & PD_ON) != PD_ON) { + say("exynos_camera: 3: the parent domain (DPU) is off: ISPPRE is not switched on under it\n"); + return false; + } + if ((status & PD_ON) == PD_ON) { + say("exynos_camera: 3: ISPPRE is on already (the boot left it): used as it is, no SMC, and left on\n"); + } else { + say("exynos_camera: 3: ISPPRE on: configuration [3:0] = 0xF, status waited for (%u ms at the most)\n", + PD_WAIT_US / 1000); + domain_mine = true; + wr(pmu, PMU_ISPPRE_CONFIG, rd(pmu, PMU_ISPPRE_CONFIG) | PD_ON); + if (!wait_for(pmu, PMU_ISPPRE_STATUS, PD_ON, PD_ON, PD_WAIT_US, true)) { + say("exynos_camera: 3: the domain did not come on (status %#x): its configuration back to off\n", + rd(pmu, PMU_ISPPRE_STATUS)); + wr(pmu, PMU_ISPPRE_CONFIG, rd(pmu, PMU_ISPPRE_CONFIG) & ~PD_ON); + (void)wait_for(pmu, PMU_ISPPRE_STATUS, PD_ON, 0, PD_WAIT_US, true); + domain_mine = false; + return false; + } + say("exynos_camera: 3: the domain is on (status %#x); about to make SMC %#x (%u, %#x, 0): the DTZPC restore; " + "Carbon, at EL2, passes it on to EL3\n", rd(pmu, PMU_ISPPRE_STATUS), DTZPC_SMC, DTZPC_RESTORE, DTZPC_ISPPRE); + answer = smc(DTZPC_SMC, DTZPC_RESTORE, DTZPC_ISPPRE, 0); + say("exynos_camera: 3: SMC %#x returned %#llx\n", DTZPC_SMC, answer); + if ((uint32_t)answer != 0) { + say("exynos_camera: 3: %s: nothing in the domain is touched; its power goes again (no save: nothing was " + "restored)\n", answer == (uint64_t)-1 ? "the call is not known to whoever answered it" : "a refusal"); + wr(pmu, PMU_ISPPRE_CONFIG, rd(pmu, PMU_ISPPRE_CONFIG) & ~PD_ON); + (void)wait_for(pmu, PMU_ISPPRE_STATUS, PD_ON, 0, PD_WAIT_US, true); + domain_mine = false; + return false; + } + } + domain_up = true; + + say("exynos_camera: 3: map CMU_ISPPRE, SYSREG_ISPPRE, CSIS1, its D-PHY, its WDMA and the DMA common%s%s\n", + cam_mmu_probe ? ", the SysMMU" : "", cam_probe ? ", the other CSISs' DMA commons" : ""); + if (cmu_isppre == 0) { + cmu_isppre = ml_io_map(CMU_ISPPRE_PA, 0x8000); /* the Q-channels' debug states are at +0x7010 */ + sysreg_isppre = ml_io_map(SYSREG_ISPPRE_PA, 0x1000); + csis = ml_io_map(CSIS1_PA, 0x1000); + dphy = ml_io_map(CSIS1_PHY_PA, 0x3000); + wdma = ml_io_map(CSIS1_WDMA_PA, 0x1000); + cdma = ml_io_map(CSIS_DMA_PA, 0x1000); + if (cam_mmu_probe) { + mmu = ml_io_map(SYSMMU_PA, 0x1000); + } + if (cam_probe) { + for (uint32_t i = 0; i < 3; i++) { + other_cmn[i] = ml_io_map(other_cmn_pa[i] & ~0xfffULL, 0x1000); + } + } + } + if (cmu_isppre == 0 || sysreg_isppre == 0 || csis == 0 || dphy == 0 || wdma == 0 || cdma == 0 || + (cam_mmu_probe && mmu == 0) || + (cam_probe && (other_cmn[0] == 0 || other_cmn[1] == 0 || other_cmn[2] == 0))) { + say("exynos_camera: 3: a block could not be mapped\n"); + return false; + } + + top_gate_found = rd(cmu_top, TOP_ISPPRE_GATE); + say("exynos_camera: 3: CMU_TOP's CLKCMU_ISPPRE_BUS: mux %#x, divider %#x, gate %#x; CMU_ISPPRE: BUS_USER %#x, BUSP " + "divider %#x, Q-channels CSIS1 %#x, SysMMU %#x\n", rd(cmu_top, TOP_ISPPRE_MUX), rd(cmu_top, TOP_ISPPRE_DIV), + top_gate_found, rd(cmu_isppre, ISPPRE_BUS_USER), rd(cmu_isppre, ISPPRE_BUSP_DIV), rd(cmu_isppre, QCH_CSIS1), + rd(cmu_isppre, QCH_SYSMMU)); + if ((top_gate_found & (GATE_MANUAL | GATE_ON)) != (GATE_MANUAL | GATE_ON)) { + wr(cmu_top, TOP_ISPPRE_GATE, top_gate_found | GATE_MANUAL | GATE_ON); + top_gate_mine = true; + } + /* + * The bus's rate. The boot leaves CLKCMU_ISPPRE_BUS at its reset setting, mux 2 (SHARED2_DIV2) divided by 16: + * about 50 MHz, and at that rate CSIS1 overflowed and VC0's WDMA filled its FIFO (DMA_ERROR) 28 lines into the + * frame (cam-fix2, 3 November). Android's INT table (cmucal-vclklut.c, vddi's nm level) has mux 1, SHARED1_DIV4, + * undivided: 466 MHz (BUSP, half of it, 233 MHz: blk_isppre). Taken here: mux 1 divided by 1 + cam_s9_ispdiv + * (default 1: 233 MHz, half Android's, no voltage asked of INT). Mux first (to the lower rate), then the divider + */ + top_mux_found = rd(cmu_top, TOP_ISPPRE_MUX); + top_div_found = rd(cmu_top, TOP_ISPPRE_DIV); + if ((top_mux_found & 3u) != ISPPRE_MUX_SHARED1_DIV4 || (top_div_found & 0xfu) != cam_ispdiv) { + top_rate_mine = true; + wr(cmu_top, TOP_ISPPRE_MUX, (top_mux_found & ~3u) | ISPPRE_MUX_SHARED1_DIV4); + if (!wait_for(cmu_top, TOP_ISPPRE_MUX, TOP_BUSY, 0, 1000, true)) { + say("exynos_camera: 3: CLKCMU_ISPPRE_BUS's mux stays busy (%#x)\n", rd(cmu_top, TOP_ISPPRE_MUX)); + } + wr(cmu_top, TOP_ISPPRE_DIV, (top_div_found & ~0xfu) | cam_ispdiv); + if (!wait_for(cmu_top, TOP_ISPPRE_DIV, TOP_BUSY, 0, 1000, true)) { + say("exynos_camera: 3: CLKCMU_ISPPRE_BUS's divider stays busy (%#x)\n", rd(cmu_top, TOP_ISPPRE_DIV)); + } + } + say("exynos_camera: 3: CLKCMU_ISPPRE_BUS: mux %#x -> %#x, divider %#x -> %#x (SHARED1_DIV4, 466 MHz, / %u)\n", + top_mux_found, rd(cmu_top, TOP_ISPPRE_MUX), top_div_found, rd(cmu_top, TOP_ISPPRE_DIV), cam_ispdiv + 1); + user = user_found = rd(cmu_isppre, ISPPRE_BUS_USER); + for (uint32_t i = 0; i < NQCHS; i++) { + isppre_qch_found[i] = rd(cmu_isppre, qchs[i]); + } + clocks_mine = true; + if ((user & (1u << 4)) == 0) { + wr(cmu_isppre, ISPPRE_BUS_USER, user | (1u << 4)); + if (!wait_for(cmu_isppre, ISPPRE_BUS_USER, 1u << 7, 0, 1000, true)) { + say("exynos_camera: 3: the ISPPRE bus's mux stays busy (%#x)\n", rd(cmu_isppre, ISPPRE_BUS_USER)); + } + } + for (uint32_t i = 0; i < NQCHS; i++) { + wr(cmu_isppre, qchs[i], isppre_qch_found[i] | QCH_REQ); + } + say("exynos_camera: 3: clocks: CMU_TOP's gate %#x, BUS_USER %#x (bit 4: CMU_TOP's clock), Q-channels CSIS1 %#x, " + "SysMMU %#x (bit 1: requested)\n", rd(cmu_top, TOP_ISPPRE_GATE), rd(cmu_isppre, ISPPRE_BUS_USER), + rd(cmu_isppre, QCH_CSIS1), rd(cmu_isppre, QCH_SYSMMU)); + isp_status("3"); + + dphy_reset_found = rd(sysreg_isppre, DPHY_RESET); + say("exynos_camera: 3: CSIS1: version %#x, CMN_CTRL %#x, PHY_STATUS %#x, PHY_CMN_CTRL %#x; the PHYs' resets " + "(0x16210500) %#x\n", rd(csis, CSIS_VERSION), rd(csis, CSIS_CMN_CTRL), rd(csis, CSIS_PHY_STATUS), + rd(csis, CSIS_PHY_CMN_CTRL), dphy_reset_found); + say("exynos_camera: 3: VC0's WDMA: CTRL %#x, FMT %#x, ACT_CTRL %#x, ADDR1 %#x; its DMA common: INT_SRC %#x, ERR_CODE " + "%#x; the DMA common: ARB_PRI %#x, CTRL %#x, SRAM_SPLIT %#x, MATRIX %#x, TEST_PATTERN %#x\n", + rd(wdma, DMA_CTRL), rd(wdma, DMA_FMT), rd(wdma, DMA_ACT_CTRL), rd(wdma, DMA_ADDR(0)), + rd(wdma, VCMN + VCMN_INT_SRC), + rd(wdma, VCMN + VCMN_ERR_CODE), rd(cdma, CDMA_ARB_PRI), rd(cdma, CDMA_CTRL), rd(cdma, CDMA_SRAM_SPLIT), + rd(cdma, CDMA_MATRIX), rd(cdma, TP_ENABLE)); + if (cam_mmu_probe) { + say("exynos_camera: 3: the SysMMU: MMU_CTRL %#x (bit 0: translating), CFG %#x, STATUS %#x, VERSION %#x\n", + rd(mmu, MMU_CTRL), rd(mmu, MMU_CFG), rd(mmu, MMU_STATUS), rd(mmu, MMU_VERSION)); + } else { + say("exynos_camera: 3: the SysMMU is not read (cam_s9_mmu_probe=1 reads it): taken as off, pass-through\n"); + } + if ((rd(csis, CSIS_VERSION) == 0 || rd(csis, CSIS_VERSION) == 0xffffffffu)) { + say("exynos_camera: 3: CSIS1's version reads %#x: is its clock there?\n", rd(csis, CSIS_VERSION)); + } + return true; +} + +/* ---- step 4: the PHY, the link set up, the WDMA, the test pattern ---- */ + +static bool +buffer_get(void) +{ + if (buffer != 0) { + return !buffer_busy; + } + if (MEM_PA < MEM_FLOOR || MEM_PA + MEM_BYTES > MEM_CEILING || MEM_PA + MEM_BYTES > 0x100000000ULL) { + say("exynos_camera: the frame's memory %#llx + %u is not in the gap %#llx - %#llx: not used\n", MEM_PA, MEM_BYTES, + MEM_FLOOR, MEM_CEILING); + return false; + } + buffer = hakios_physmap_wc(MEM_PA, MEM_BYTES); + if (buffer == 0) { + say("exynos_camera: the frame's memory (%u bytes at %#llx, physical) cannot be mapped\n", MEM_BYTES, MEM_PA); + return false; + } + buffer_pa = MEM_PA; + say("exynos_camera: the buffer: %u bytes at %#llx (physical, outside XNU's RAM, below 4 GB; mapped write-combined " + "at %#lx)\n", BUF_BYTES, buffer_pa, (unsigned long)buffer); + return true; +} + +static void +buffer_fill(void) +{ + memset((void *)buffer, CANARY, RING_SLOTS * SLOT_BYTES); + cache_flush(buffer, RING_SLOTS * SLOT_BYTES); + frame = buffer; +} + +/* What the DMA wrote: how much, how far, a checksum, the first bytes. Returns the bytes up to the last one changed. */ +static uint32_t +buffer_report(const char *step) +{ + const uint8_t *b = (const uint8_t *)frame; + uint32_t changed = 0, last = 0, sum = 0, fnv = 2166136261u; + + cache_flush(frame, BUF_BYTES); + /* eight bytes a read: the mapping is not cached, and a byte a read over 4 MB is slow */ + for (uint32_t i = 0; i < BUF_BYTES; i += 8) { + uint64_t w = *(const volatile uint64_t *)(frame + i); + + if (w == 0xA5A5A5A5A5A5A5A5ULL) { + continue; + } + for (uint32_t k = 0; k < 8; k++) { + if (((w >> (8 * k)) & 0xff) != CANARY) { + changed++; + last = i + k + 1; + } + } + } + for (uint32_t i = 0; i < last; i += 8) { + uint64_t w = *(const volatile uint64_t *)(frame + i); + + for (uint32_t k = 0; k < 8 && i + k < last; k++) { + uint8_t c = (uint8_t)(w >> (8 * k)); + + sum += c; + fnv = (fnv ^ c) * 16777619u; + } + } + frame_last = last; + say("exynos_camera: %s: the buffer: %u bytes changed, the last at %u (%u a line over %u lines); sum %#x, FNV-1a " + "%#x\n", step, changed, last, last / FRAME_H, FRAME_H, sum, fnv); + say("exynos_camera: %s: its first bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x " + "%02x; at %u: %02x %02x %02x %02x %02x %02x %02x %02x\n", step, b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], + b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], LINE_BYTES, b[LINE_BYTES], b[LINE_BYTES + 1], + b[LINE_BYTES + 2], b[LINE_BYTES + 3], b[LINE_BYTES + 4], b[LINE_BYTES + 5], b[LINE_BYTES + 6], b[LINE_BYTES + 7]); + return last; +} + +/* the D-PHY's isolation off and its reset released (exynos_mipi_phy_power_on) */ +static void +phy_on(void) +{ + say("exynos_camera: 4: the D-PHY (M0S4S2, CSIS1): isolation bypass, PMU 0x730 %#x |= 1; reset bit 2 of 0x16210500 " + "(%#x) cleared, then set\n", iso_found, rd(sysreg_isppre, DPHY_RESET)); + phy_mine = true; + wr(pmu, PMU_DPHY_ISO_CSIS1, rd(pmu, PMU_DPHY_ISO_CSIS1) | ISO_BYPASS); + wr(sysreg_isppre, DPHY_RESET, rd(sysreg_isppre, DPHY_RESET) & ~DPHY_RESET_CSIS1); + wr(sysreg_isppre, DPHY_RESET, rd(sysreg_isppre, DPHY_RESET) | DPHY_RESET_CSIS1); + say("exynos_camera: 4: the D-PHY: isolation %#x, resets %#x\n", rd(pmu, PMU_DPHY_ISO_CSIS1), + rd(sysreg_isppre, DPHY_RESET)); +} + +/* CSIS1 reset and set up for the mode (csi_hw_reset, csi_hw_s_lane, _s_control, _s_config, _s_config_dma(_cmn)) */ +static bool +link_setup(void) +{ + uint32_t v; + + wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) | CMN_SW_RESET); + if (!wait_for(csis, CSIS_CMN_CTRL, CMN_SW_RESET, 0, 100, false)) { + say("exynos_camera: CSIS1's reset does not finish (CMN_CTRL %#x)\n", rd(csis, CSIS_CMN_CTRL)); + return false; + } + link_on = true; /* (from here, the way down resets it again) */ + wr(csis, CSIS_INT_MSK0, CSIS_IRQ_MASK0); + wr(csis, CSIS_INT_MSK1, CSIS_IRQ_MASK1); + wr(csis, CSIS_INT_SRC0, rd(csis, CSIS_INT_SRC0)); + wr(csis, CSIS_INT_SRC1, rd(csis, CSIS_INT_SRC1)); + v = rd(csis, CSIS_CMN_CTRL) & ~((3u << CMN_LANES_SHIFT) | (3u << CMN_INTERLEAVE_SHIFT)); + wr(csis, CSIS_CMN_CTRL, v | (1u << CMN_LANES_SHIFT) | (3u << CMN_INTERLEAVE_SHIFT)); + wr(csis, CSIS_PHY_CMN_CTRL, (rd(csis, CSIS_PHY_CMN_CTRL) & ~(0xfu << PHY_ENABLE_DAT_SHIFT)) | + (3u << PHY_ENABLE_DAT_SHIFT)); + /* VC0 RAW10 1632 x 1224, two pixels a clock; CH1 nothing; CH2 VC0's embedded data (0 x 0); CH3 nothing */ + wr(csis, CSIS_ISP_CONFIG(0), (rd(csis, CSIS_ISP_CONFIG(0)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_RAW10 << 2) | 0); + wr(csis, CSIS_ISP_RESOL(0), (FRAME_H << 16) | FRAME_W); + wr(csis, CSIS_ISP_CONFIG(1), (rd(csis, CSIS_ISP_CONFIG(1)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_UNKNOWN << 2) | 1); + wr(csis, CSIS_ISP_RESOL(1), 0); + wr(csis, CSIS_ISP_CONFIG(2), (rd(csis, CSIS_ISP_CONFIG(2)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_EMBEDDED << 2) | 0); + wr(csis, CSIS_ISP_RESOL(2), 0); + wr(csis, CSIS_ISP_CONFIG(3), (rd(csis, CSIS_ISP_CONFIG(3)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_UNKNOWN << 2) | 3); + wr(csis, CSIS_ISP_RESOL(3), 0); + + /* VC0's WDMA: 2D, legacy storage, RAW10 (or PACK12), the width; its common: OTF in, RAW10 */ + v = rd(wdma, DMA_FMT) & ~0x3fu; + wr(wdma, DMA_FMT, v | (cam_pack12 ? DMA_PACK12 : 0)); + wr(wdma, DMA_RESOL, (rd(wdma, DMA_RESOL) & ~0x3fffu) | FRAME_W); + wr(wdma, VCMN + VCMN_OTF_FORMAT, rd(wdma, VCMN + VCMN_OTF_FORMAT) & ~3u); + wr(wdma, VCMN + VCMN_DATA_CTRL, rd(wdma, VCMN + VCMN_DATA_CTRL) & ~1u); + wr(wdma, VCMN + VCMN_INT_MASK, VCMN_IRQ_MASK); + wr(wdma, VCMN + VCMN_INT_SRC, rd(wdma, VCMN + VCMN_INT_SRC)); + say("exynos_camera: CSIS1 set up: CMN_CTRL %#x, PHY_CMN_CTRL %#x, CH0 %#x %#x, CH2 %#x; WDMA FMT %#x, RESOL %#x; " + "its common: DATA_CTRL %#x, OTF_FORMAT %#x\n", rd(csis, CSIS_CMN_CTRL), rd(csis, CSIS_PHY_CMN_CTRL), + rd(csis, CSIS_ISP_CONFIG(0)), rd(csis, CSIS_ISP_RESOL(0)), rd(csis, CSIS_ISP_CONFIG(2)), rd(wdma, DMA_FMT), + rd(wdma, DMA_RESOL), rd(wdma, VCMN + VCMN_DATA_CTRL), rd(wdma, VCMN + VCMN_OTF_FORMAT)); + return true; +} + +/* the buffer to all eight frame pointers and the output on (csi_hw_s_dma_addr, csi_hw_s_output_dma) */ +static void +dma_start(void) +{ + wr(wdma, DMA_CTRL, rd(wdma, DMA_CTRL) & ~DMA_FRAMEPTR_MASK); + for (uint32_t i = 0; i < 8; i++) { + wr(wdma, DMA_ADDR(i), (uint32_t)buffer_pa); + } + wr(wdma, VCMN + VCMN_INT_SRC, rd(wdma, VCMN + VCMN_INT_SRC)); + dma_on = true; + wr(wdma, DMA_CTRL, rd(wdma, DMA_CTRL) | DMA_ON | DMA_UPDT_PTR_EN); +} + +/* the DMA common as csi_hw_s_dma_common leaves it (found values kept, put back on the way down) */ +static void +dma_common(void) +{ + if (!cdma_mine) { + cdma_found[0] = rd(cdma, CDMA_ARB_PRI); + cdma_found[1] = rd(cdma, CDMA_CTRL); + cdma_found[2] = rd(cdma, CDMA_SRAM_SPLIT); + cdma_found[3] = rd(cdma, CDMA_MATRIX); + cdma_mine = true; + } + wr(cdma, CDMA_ARB_PRI, (cdma_found[0] & ~0xfu) | (1u << 2) | 2u); + wr(cdma, CDMA_CTRL, cdma_found[1] | (1u << 1)); + wr(cdma, CDMA_SRAM_SPLIT, (cdma_found[2] & ~0x03ff03ffu) | (0x140u << 16) | 0x140u); + wr(cdma, CDMA_MATRIX, cdma_found[3] & ~0x1fu); +} + +/* the link's and the WDMA's state as last read (camshot's failure line gives it) */ +static struct { + bool taken; + uint32_t src0, src1, frm, phy, ctrl, act, fcnt, isrc, err; +} snap; + +static void +snap_take(void) +{ + if (!domain_up || csis == 0 || wdma == 0) { + return; + } + snap.src0 = rd(csis, CSIS_INT_SRC0); + snap.src1 = rd(csis, CSIS_INT_SRC1); + snap.frm = rd(csis, CSIS_FRM_CNT_CH0); + snap.phy = rd(csis, CSIS_PHY_STATUS); + snap.ctrl = rd(wdma, DMA_CTRL); + snap.act = rd(wdma, DMA_ACT_CTRL); + snap.fcnt = rd(wdma, DMA_FCNTSEQ_STAT); + snap.isrc = rd(wdma, VCMN + VCMN_INT_SRC); + snap.err = rd(wdma, VCMN + VCMN_ERR_CODE); + snap.taken = true; +} + +static void +dma_log(const char *step) +{ + snap_take(); + say("exynos_camera: %s: WDMA CTRL %#x, ACT_CTRL %#x, FCNTSEQ_STAT %#x; its common: INT_SRC %#x (frame start [7:4], " + "end [11:8], error 12, abort done 16), ERR_CODE %#x, CRC %#x, CMN_CTRL %#x; the pattern %#x\n", step, + rd(wdma, DMA_CTRL), rd(wdma, DMA_ACT_CTRL), rd(wdma, DMA_FCNTSEQ_STAT), rd(wdma, VCMN + VCMN_INT_SRC), + rd(wdma, VCMN + VCMN_ERR_CODE), rd(wdma, VCMN + VCMN_CRC_RESULT), rd(wdma, VCMN + VCMN_CMN_CTRL), + rd(cdma, TP_ENABLE)); + if (cam_probe) { + say("exynos_camera: %s: the others' INT_SRC (VC0 ACT_CTRL): CSIS0 %#x (%#x), CSIS2 %#x (%#x), CSIS3 %#x (%#x)\n", + step, rd(other_cmn[0], 0x400 + VCMN_INT_SRC), rd(other_cmn[0], DMA_ACT_CTRL), + rd(other_cmn[1], 0x400 + VCMN_INT_SRC), rd(other_cmn[1], DMA_ACT_CTRL), + rd(other_cmn[2], 0x400 + VCMN_INT_SRC), rd(other_cmn[2], DMA_ACT_CTRL)); + } + say("exynos_camera: %s: CSIS1 INT_SRC0 %#x (frame start [27:24], end [23:20], SOT [19:16], lost FS [15:12], lost FE " + "[11:8], overflow 4, wrong config 3, ECC 2, CRC 1, ID 0), INT_SRC1 %#x (V/H size mismatch [15:12]/[11:8]), " + "FRM_CNT_CH0 %u, PHY_STATUS %#x\n", step, rd(csis, CSIS_INT_SRC0), rd(csis, CSIS_INT_SRC1), + rd(csis, CSIS_FRM_CNT_CH0), rd(csis, CSIS_PHY_STATUS)); +} + +/* the other three CSISs' DMA interrupts unmasked for the pattern's while (so a frame that goes there shows), and back */ +static uint32_t other_mask_found[3]; + +static void +others_watch(bool on) +{ + if (!cam_probe) { + return; /* the other CSISs are not touched unless cam_s9_probe=1 */ + } + for (uint32_t i = 0; i < 3; i++) { + if (on) { + other_mask_found[i] = rd(other_cmn[i], 0x400 + VCMN_INT_MASK); + wr(other_cmn[i], 0x400 + VCMN_INT_MASK, VCMN_IRQ_MASK); + wr(other_cmn[i], 0x400 + VCMN_INT_SRC, rd(other_cmn[i], 0x400 + VCMN_INT_SRC)); + } else if (others_watched && domain_up) { + wr(other_cmn[i], 0x400 + VCMN_INT_MASK, other_mask_found[i]); + } + } + others_watched = on; +} + +static void +pattern_on_now(void) +{ + wr(cdma, TP_CTRL, (rd(cdma, TP_CTRL) & ~0xffffffu) | (16u << 16) | (8u << 8) | 2u); + wr(cdma, TP_RESOL, (FRAME_H << 16) | FRAME_W); + wr(cdma, TP_ENABLE, TP_DUAL | TP_VBLANK); + pattern_on = true; + wr(cdma, TP_ENABLE, rd(cdma, TP_ENABLE) | TP_ON); +} + +static bool +pattern_frame(void) +{ + uint32_t last, cmn; + bool ended; + + if (mmu_translating()) { + say("exynos_camera: 4: the SysMMU is on (MMU_CTRL %#x): the WDMA's addresses would be translated by a table " + "this driver did not make; no DMA\n", rd(mmu, MMU_CTRL)); + return false; + } + if (!buffer_get()) { + return false; + } + buffer_fill(); + phy_on(); + say("exynos_camera: 4: CSIS1 reset and set up for %u x %u RAW10 on VC0 (%s), two lanes\n", FRAME_W, FRAME_H, + cam_pack12 ? "PACK12" : "RAW10 as it comes"); + if (!link_setup()) { + return false; + } + dma_common(); + say("exynos_camera: 4: VC0's WDMA to %#llx, the output on; then the test pattern generator, %u x %u, two pixels a " + "clock, vertical blank %u clocks: ONE frame, its end waited for (2 s at the most)\n", buffer_pa, FRAME_W, FRAME_H, + TP_VBLANK); + cmn = rd(wdma, VCMN + VCMN_CMN_CTRL); + if (cmn & 1) { + wr(wdma, VCMN + VCMN_CMN_CTRL, cmn & ~1u); + } + say("exynos_camera: 4: before: WDMA ACT_CTRL %#x (bit 16: aborted, bit 0: active), DMA_CMN_CTRL %#x -> %#x (bit 0: " + "abort request); CSIS1 CLK_CTRL %#x; the DMA common: CTRL %#x, CLK_CTRL %#x\n", rd(wdma, DMA_ACT_CTRL), cmn, + rd(wdma, VCMN + VCMN_CMN_CTRL), rd(csis, CSIS_CLK_CTRL), rd(cdma, CDMA_CTRL), rd(cdma, CDMA_CLK_CTRL)); + if (cam_mmu_probe) { + say("exynos_camera: 4: the SysMMU: CTRL %#x, CFG %#x, STATUS %#x\n", rd(mmu, MMU_CTRL), rd(mmu, MMU_CFG), + rd(mmu, MMU_STATUS)); + } + others_watch(true); + dma_start(); + pattern_on_now(); + ended = wait_for(wdma, VCMN + VCMN_INT_SRC, DMA_FRM_END0, DMA_FRM_END0, 500000, true); + if (!ended && (rd(wdma, VCMN + VCMN_INT_SRC) & DMA_FRM_START0) == 0) { + dma_log("4, the link off"); + say("exynos_camera: 4: no frame start on CSIS1's WDMA in 500 ms with the link off (Android's way for the " + "pattern): the pattern off, CSIS1's link enabled as csi_hw_enable does (UPDATE_SHADOW, PHY_SEL 0, no " + "Q-channel, CSI_EN; no clock lane: no sensor), the pattern on again, 1.5 s\n"); + wr(cdma, TP_ENABLE, rd(cdma, TP_ENABLE) & ~TP_ON); + delay_for_interval(5, NSEC_PER_MSEC); + wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) | CMN_UPDATE_SHADOW); + wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) & ~(CMN_PHY_SEL | CMN_QCHANNEL_EN)); + wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) | CMN_CSI_EN); + wr(wdma, VCMN + VCMN_INT_SRC, rd(wdma, VCMN + VCMN_INT_SRC)); + pattern_on_now(); + ended = wait_for(wdma, VCMN + VCMN_INT_SRC, DMA_FRM_END0, DMA_FRM_END0, 1500000, true); + say("exynos_camera: 4: with the link on: %s (CMN_CTRL %#x)\n", ended ? "THE FRAME ENDED" : + "no frame end either", rd(csis, CSIS_CMN_CTRL)); + } else if (!ended) { + /* it started: give it the rest of the 2 s */ + ended = wait_for(wdma, VCMN + VCMN_INT_SRC, DMA_FRM_END0, DMA_FRM_END0, 1500000, true); + } + dma_stop(); + delay_for_interval(50, NSEC_PER_MSEC); + say("exynos_camera: 4: %s; the WDMA and the pattern off\n", ended ? "THE FRAME ENDED" : "no frame end in 2 s"); + dma_log("4"); + others_watch(false); + if (!ended) { + isp_status("4"); + } + if (!dma_quiet()) { + say("exynos_camera: 4: the WDMA still reads active (ACT_CTRL %#x): no more DMA into the buffer this run\n", + rd(wdma, DMA_ACT_CTRL)); + buffer_busy = true; + } + last = buffer_report("4"); + if (last == 0) { + say("exynos_camera: 4: nothing reached the buffer\n"); + return false; + } + say("exynos_camera: 4: THE PATTERN REACHED MEMORY: %u bytes (%u x %u RAW10 packed would be %u)\n", last, FRAME_W, + FRAME_H, FRAME_W * FRAME_H * 10 / 8); + return true; +} + +/* ---- step 5: a frame of the sensor's ---- */ + +static uint32_t +sensor_get16(uint16_t reg) +{ + uint8_t b[2] = { 0, 0 }; + + return sensor_get(reg, b, 2) ? 0xffffffffu : (uint32_t)((b[0] << 8) | b[1]); +} + +/* the D-PHY's timing for 719 Mb/s a lane (csi_hw_s_phy_default_value, csi_hw_s_phy_config, not USE_PHY_LINK) */ +static void +phy_setup(void) +{ + wr(dphy, DPHY_ACTRL_SC_06, 2); + wr(dphy, DPHY_DCTRL_SC_02, (rd(dphy, DPHY_DCTRL_SC_02) & ~0xffu) | 1u); + for (uint32_t lane = 0; lane < 2; lane++) { + wr(dphy, DPHY_ACTRL_SD_08 + DPHY_LANE(lane), rd(dphy, DPHY_ACTRL_SD_08 + DPHY_LANE(lane)) & ~(3u << 5)); + wr(dphy, DPHY_DCTRL_SD_13 + DPHY_LANE(lane), rd(dphy, DPHY_DCTRL_SD_13 + DPHY_LANE(lane)) & ~1u); + wr(dphy, DPHY_DCTRL_SD_05 + DPHY_LANE(lane), rd(dphy, DPHY_DCTRL_SD_05 + DPHY_LANE(lane)) | (1u << 2)); + wr(dphy, DPHY_DCTRL_SD_06 + DPHY_LANE(lane), (rd(dphy, DPHY_DCTRL_SD_06 + DPHY_LANE(lane)) & ~0xffu) | + SETTLE_719); + } + say("exynos_camera: 5: the D-PHY: SC_06 %#x, SC_02 %#x; lane 0: SD_08 %#x, SD_05 %#x, SD_06 %#x (settle), SD_13 %#x; " + "lane 1: %#x %#x %#x %#x\n", rd(dphy, DPHY_ACTRL_SC_06), rd(dphy, DPHY_DCTRL_SC_02), rd(dphy, DPHY_ACTRL_SD_08), + rd(dphy, DPHY_DCTRL_SD_05), rd(dphy, DPHY_DCTRL_SD_06), rd(dphy, DPHY_DCTRL_SD_13), + rd(dphy, DPHY_ACTRL_SD_08 + DPHY_LANE(1)), rd(dphy, DPHY_DCTRL_SD_05 + DPHY_LANE(1)), + rd(dphy, DPHY_DCTRL_SD_06 + DPHY_LANE(1)), rd(dphy, DPHY_DCTRL_SD_13 + DPHY_LANE(1))); +} + +/* the mean of a 64 x 64 patch in the middle, read as RAW10 in 16 bits (the WDMA's) at the stride given */ +static void +patch_mean(uint32_t stride) +{ + const uint8_t *b = (const uint8_t *)frame; + uint64_t sum = 0; + uint32_t lo = 1023, hi = 0, n = 0, over = 0; + + if (stride < LINE_BYTES || (uint64_t)stride * FRAME_H > BUF_BYTES) { + say("exynos_camera: 5: a stride of %u does not hold the frame: no patch\n", stride); + return; + } + for (uint32_t y = FRAME_H / 2 - 32; y < FRAME_H / 2 + 32; y++) { + const uint8_t *line = b + (uint64_t)y * stride; + + for (uint32_t x = FRAME_W / 2 - 32; x < FRAME_W / 2 + 32; x++) { + uint32_t w = (uint32_t)line[2 * x] | ((uint32_t)line[2 * x + 1] << 8); + uint32_t p = w & 0x3ff; + + over += w > 0x3ff; + sum += p; + lo = p < lo ? p : lo; + hi = p > hi ? p : hi; + n++; + } + } + say("exynos_camera: 5: THE PATCH (64 x 64 in the middle, 16-bit RAW10 at a stride of %u): mean %llu.%02llu of 1023 " + "(about 64 is black), %u to %u; %u samples above 10 bits (the fill's 0xA5A5, or not RAW10 in 16 bits)\n", stride, + sum / n, (sum % n) * 100 / n, lo, hi, over); +} + +/* + * ---- the exposure: the 3H1's coarse integration time, analog gain and digital gain over I2C ---- + * + * Registers (fimc-is-cis-3h1.c, as facts): 0x0202 the coarse integration time in lines (at least 2, at most the frame's + * length 0x0340 less 4), 0x0204 the analog gain (0x20 is 1x; the sensor's own least and most at 0x0084 and 0x0086), + * 0x020E the digital gain (0x100 is 1x, 8.8; its most at 0x1086), 0x0104 the grouped parameter hold around them. The + * mode's frame stays 1266 lines (30 frames a second): the exposure is at most 1262 lines of 26.3 us, 33.2 ms. + * + * The meter: the mean of the green of every 16th 2 x 2 cell across and down the middle half of the frame (about 1900 + * cells), the black level off; the aim 18 % of the range above it (173 of 959). The exposure asked is the one before + * times aim / mean (a step of at most 8x either way, half the step while streaming), spent on time first, then on + * analog gain, then on digital gain (at most cam_s9_dgain, 4x unless said). A change written after frame N's end + * shows in frame N + 3 (the sensor latches it at a frame start, and the next frame has + * begun integrating already), so each one is measured on the third frame after it. + */ +#define AE_AIM ((1023 - BLACK_LEVEL) * 18 / 100) +#define BLACK_LEVEL 64 +#define AE_CLIP 1000 /* a sample at or above this is clipped */ + +static uint32_t ae_lines, ae_again = 0x20, ae_dgain = 0x100; /* as last written */ +static uint32_t again_min = 0x20, again_max = 0x200, dgain_max = 0x400; +static uint32_t cam_ae = 1, cam_ae_frames = 15, cam_dgain = 0x400; +static uint32_t shot_slot; /* the ring's slot of the frame taken */ +static uint32_t dma_errors, slot_fix, ring_written, ring_checks; /* frame_wait()'s */ + +struct meter { + uint32_t mean[3]; /* R, G, B less the black level */ + uint32_t clipped; /* per mille of the green samples */ + uint32_t cells; +}; + +static uint32_t cam_bayer = 1, cam_stride; +static const uint8_t bayer_place[4][4] = { /* the colour of a cell's four places (0 R, 1 G, 2 B), row by row */ + { 0, 1, 1, 2 }, { 1, 0, 2, 1 }, { 1, 2, 0, 1 }, { 2, 1, 1, 0 }, +}; + +static void +meter(vm_offset_t raw, struct meter *m) +{ + const uint8_t *place = bayer_place[cam_bayer & 3]; + uint64_t sum[3] = { 0, 0, 0 }; + uint32_t n = 0, clip = 0; + + for (uint32_t y = FRAME_H / 4; y < FRAME_H * 3 / 4; y += 16) { + const volatile uint32_t *top = (const volatile uint32_t *)(raw + (uint64_t)y * LINE_BYTES); + const volatile uint32_t *bottom = (const volatile uint32_t *)(raw + (uint64_t)(y + 1) * LINE_BYTES); + + for (uint32_t x = FRAME_W / 4; x < FRAME_W * 3 / 4; x += 16) { + uint32_t a = top[x / 2], b = bottom[x / 2]; + uint32_t v[4] = { a & 0x3ff, (a >> 16) & 0x3ff, b & 0x3ff, (b >> 16) & 0x3ff }; + + for (uint32_t i = 0; i < 4; i++) { + sum[place[i]] += v[i] > BLACK_LEVEL ? v[i] - BLACK_LEVEL : 0; + clip += place[i] == 1 && v[i] >= AE_CLIP; + } + n++; + } + } + m->cells = n; + m->mean[0] = (uint32_t)(sum[0] / n); + m->mean[1] = (uint32_t)(sum[1] / (2 * n)); + m->mean[2] = (uint32_t)(sum[2] / n); + m->clipped = clip * 1000 / (2 * n); +} + +static bool +exposure_put(uint32_t lines, uint32_t again, uint32_t dgain) +{ + bool ok; + + ok = sensor_put(0x0104, 0x01, 1) && sensor_put(0x0202, (uint16_t)lines, 2) && sensor_put(0x0204, (uint16_t)again, 2) + && sensor_put(0x020E, (uint16_t)dgain, 2); + (void)sensor_put(0x0104, 0x00, 1); + if (ok) { + ae_lines = lines; + ae_again = again; + ae_dgain = dgain; + } + return ok; +} + +/* the exposure in 1/256 of a line at 1x: lines x analog x digital */ +static uint64_t +exposure_now(void) +{ + return (uint64_t)ae_lines * ae_again * ae_dgain / 32; +} + +/* the exposure for an amount (lines x gain x 256): time first, then analog gain, then digital gain */ +static void +exposure_split(uint64_t want, uint32_t *lines, uint32_t *again, uint32_t *dgain) +{ + uint32_t max_lines = MODE_LINES - MODE_MARGIN, l, a, d; + uint64_t g; + + l = (uint32_t)(want / 256 > max_lines ? max_lines : want / 256); + l = l < 2 ? 2 : l; + g = want / l; /* the gain left, 256 = 1x */ + a = (uint32_t)(g * 32 / 256); + a = a < again_min ? again_min : (a > again_max ? again_max : a); + d = (uint32_t)(g * 32 / a); + d = d < 0x100 ? 0x100 : (d > dgain_max ? dgain_max : d); + *lines = l; + *again = a; + *dgain = d; +} + +/* one step of the meter's: true when the green is near the aim (or the exposure can go no further that way) */ +static bool +ae_step(const struct meter *m, bool half) +{ + uint64_t now = exposure_now(), want; + uint32_t g = m->mean[1] + 1, l, a, d; + bool near = g * 100 >= AE_AIM * 85 && g * 100 <= AE_AIM * 118; + + if (m->clipped > 30 && g > AE_AIM / 2) { + near = false; + want = now / 2; /* highlights clipped: down, whatever the mean says */ + } else { + want = now * AE_AIM / g; + } + if (near) { + return true; + } + want = want > now * 8 ? now * 8 : (want < now / 8 ? now / 8 : want); + if (half) { + want = (want + now) / 2; + } + exposure_split(want, &l, &a, &d); + if (l == ae_lines && a == ae_again && d == ae_dgain) { + return true; /* at an end of the range */ + } + (void)exposure_put(l, a, d); + return false; +} + +/* the gains' range as the sensor gives it (0x0084, 0x0086, 0x1086), sanity-checked */ +static void +gains_read(void) +{ + uint32_t lo = sensor_get16(0x0084), hi = sensor_get16(0x0086), dhi = sensor_get16(0x1086); + + again_min = lo >= 0x20 && lo <= 0x40 ? lo : 0x20; + again_max = hi > again_min && hi <= 0x400 ? hi : 0x200; + dgain_max = dhi >= 0x100 && dhi <= 0x1000 ? dhi : 0x400; + dgain_max = dgain_max > cam_dgain ? cam_dgain : dgain_max; + say("exynos_camera: 5: the gains: analog %#x to %#x (0x0084, 0x0086: %#x, %#x), digital to %#x (0x1086: %#x, " + "capped at cam_s9_dgain %#x)\n", again_min, again_max, lo, hi, dgain_max, dhi, cam_dgain); +} + +/* the mode's table, then the exposure; false if the sensor did not take them */ +static bool +mode_load(void) +{ + uint32_t exp = cam_exp; + + exp = exp < 2 ? 2 : exp; + exp = exp > MODE_LINES - MODE_MARGIN ? MODE_LINES - MODE_MARGIN : exp; + say("exynos_camera: 5: the sensor's mode: setfile A's 1632 x 1224 at 30 frames a second, %u 16-bit writes; then " + "0x3092 = 0, exposure %u lines (0x0202), analog gain %#x (0x0204, 0x20 is 1x), digital 1x; auto-exposure %s\n", + (unsigned)MODE_ENTRIES, exp, cam_gain, cam_ae ? "on" : "off (cam_s9_ae=0)"); + delay_for_interval(3, NSEC_PER_MSEC); + for (uint32_t i = 0; i < MODE_ENTRIES; i++) { + if (!sensor_put(mode_1632x1224[i][0], mode_1632x1224[i][1], 2)) { + return false; + } + } + if (!sensor_put(0x3092, 0x0000, 2) || !sensor_put(0x6028, 0x2000, 2)) { + return false; + } + gains_read(); + if (!exposure_put(exp, cam_gain, 0x100)) { + return false; + } + say("exynos_camera: 5: read back: output %u x %u, frame %u lines of %u clocks, exposure %u, gain %#x, digital %#x\n", + sensor_get16(0x034C), sensor_get16(0x034E), sensor_get16(0x0340), sensor_get16(0x0342), sensor_get16(0x0202), + sensor_get16(0x0204), sensor_get16(0x020E)); + return true; +} + +/* the link up and the ring given to the WDMA, then the sensor streaming; false if a step failed */ +static bool +stream_start(void) +{ + uint8_t frames = 0xff; + uint64_t until; + + phy_setup(); + say("exynos_camera: 5: CSIS1 reset and set up again; the ring filled; VC0's WDMA on, its eight frame pointers on " + "the ring's %u slots\n", RING_SLOTS); + if (!link_setup()) { + return false; + } + dma_common(); + buffer_fill(); + ring_written = 0; + ring_checks = 0; + wr(wdma, DMA_CTRL, rd(wdma, DMA_CTRL) & ~DMA_FRAMEPTR_MASK); + for (uint32_t i = 0; i < 8; i++) { + wr(wdma, DMA_ADDR(i), (uint32_t)(buffer_pa + (uint64_t)(i % RING_SLOTS) * SLOT_BYTES)); + } + wr(wdma, VCMN + VCMN_INT_SRC, rd(wdma, VCMN + VCMN_INT_SRC)); + dma_on = true; + wr(wdma, DMA_CTRL, rd(wdma, DMA_CTRL) | DMA_ON | DMA_UPDT_PTR_EN); + say("exynos_camera: 5: the link on (UPDATE_SHADOW, D-PHY, clock lane, no Q-channel, CSI_EN)\n"); + wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) | CMN_UPDATE_SHADOW); + wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) & ~CMN_PHY_SEL); + wr(csis, CSIS_PHY_CMN_CTRL, rd(csis, CSIS_PHY_CMN_CTRL) | PHY_ENABLE_CLK); + wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) & ~CMN_QCHANNEL_EN); + wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) | CMN_CSI_EN); + say("exynos_camera: 5: STREAM ON: 0x0104 = 0, 8 ms, 0x6028 = 0x4000, 0x0100 = 1, 0x0216 = 0\n"); + if (!sensor_put(0x0104, 0x00, 1)) { + return false; + } + delay_for_interval(8, NSEC_PER_MSEC); + streaming = true; + if (!sensor_put(0x6028, 0x4000, 2) || !sensor_put(0x0100, 0x01, 1) || !sensor_put(0x0216, 0x00, 1)) { + return false; + } + nanoseconds_to_absolutetime(300 * NSEC_PER_MSEC, &until); + until += mach_absolute_time(); + while (mach_absolute_time() < until) { + if (sensor_get(0x0005, &frames, 1) == 0 && frames != 0xff) { + break; + } + delay_for_interval(2, NSEC_PER_MSEC); + } + say("exynos_camera: 5: the sensor's frame count %#x (0xff: not streaming); PHY_STATUS %#x\n", frames, + rd(csis, CSIS_PHY_STATUS)); + return true; +} + +/* the sensor stopped and seen to be (frame count 0xff), the WDMA's frame dropped, the link down, the WDMA idle */ +static void +frames_halt(void) +{ + uint8_t frames = 0xff; + uint64_t until; + + stream_stop(); + nanoseconds_to_absolutetime(300 * NSEC_PER_MSEC, &until); + until += mach_absolute_time(); + while (mach_absolute_time() < until) { + if (sensor_get(0x0005, &frames, 1) == 0 && frames == 0xff) { + break; + } + delay_for_interval(2, NSEC_PER_MSEC); + } + say("exynos_camera: 5: the sensor's frame count %#x after the stop\n", frames); + dma_abort(); + dma_log("5"); + link_stop(); + if (!dma_quiet()) { + say("exynos_camera: 5: the WDMA still reads active (ACT_CTRL %#x): no more DMA into the buffer this run\n", + rd(wdma, DMA_ACT_CTRL)); + buffer_busy = true; + } +} + +/* + * The next frame end of VC0's WDMA, waited for (until `until'): the ring's slot it went to, or -1. Polled every + * `poll_us' (0: 20 us spinning). The WDMA's active frame pointer (ACT_CTRL [4:2]) is read at the frame's end: a + * frame start seen after it (the next frame's, its bit still set) means the pointer has moved on, and the frame done + * is the one before. A frame start alone is taken as it comes. The first four frames after the ring was filled are + * also checked against the fill (the last line of each slot): the slot that changed is the truth, and the pointer's + * offset from it (slot_fix) is kept for the frames after. + */ +static uint32_t +slot_written(void) +{ + uint32_t mask = 0; + + for (uint32_t i = 0; i < RING_SLOTS; i++) { + uint64_t w = *(const volatile uint64_t *)(buffer + (uint64_t)i * SLOT_BYTES + (uint64_t)FRAME_H * LINE_BYTES - 8); + + mask |= w != 0xA5A5A5A5A5A5A5A5ULL ? 1u << i : 0; + } + return mask; +} + +static int +frame_wait(uint64_t until, uint32_t poll_us) +{ + while (mach_absolute_time() < until) { + uint32_t src = rd(wdma, VCMN + VCMN_INT_SRC), ptr, src2, slot, fresh; + + if (src & DMA_ERROR) { + wr(wdma, VCMN + VCMN_INT_SRC, DMA_ERROR); + dma_errors++; + } + if ((src & (DMA_FRM_START0 | DMA_FRM_END0)) == DMA_FRM_START0) { + wr(wdma, VCMN + VCMN_INT_SRC, DMA_FRM_START0); + src = rd(wdma, VCMN + VCMN_INT_SRC); + } + if (src & DMA_FRM_END0) { + ptr = (rd(wdma, DMA_ACT_CTRL) >> 2) & 7; + src2 = rd(wdma, VCMN + VCMN_INT_SRC); + if (src2 & DMA_FRM_START0) { + ptr = (ptr + 7) & 7; + } + wr(wdma, VCMN + VCMN_INT_SRC, DMA_FRM_END0 | (src2 & DMA_FRM_START0)); + slot = (ptr + slot_fix) % RING_SLOTS; + if (ring_checks < RING_SLOTS) { + fresh = slot_written() & ~ring_written; + ring_written |= fresh; + ring_checks++; + if (fresh != 0 && (fresh & (fresh - 1)) == 0) { + uint32_t truth = (uint32_t)__builtin_ctz(fresh); + + if (truth != slot) { + slot_fix = (slot_fix + truth + RING_SLOTS - slot) % RING_SLOTS; + } + say("exynos_camera: the ring: frame end %u, frame pointer %u (INT_SRC %#x then %#x): slot %u by the " + "pointer, slot %u by the fill (the pointer's offset now %u)\n", ring_checks, ptr, src, src2, slot, + truth, slot_fix); + slot = truth; + } else { + say("exynos_camera: the ring: frame end %u, frame pointer %u (INT_SRC %#x then %#x): slot %u; the " + "fill shows slots %#x new\n", ring_checks, ptr, src, src2, slot, fresh); + } + } + return (int)slot; + } + if (poll_us == 0) { + spin_us(20); + } else { + delay_for_interval(poll_us, NSEC_PER_USEC); + } + } + return -1; +} + +/* + * One frame of the sensor's: the mode, the ring, streaming; the auto-exposure over up to cam_s9_ae_frames frames + * (measured on every third one); the frame where the green is at the aim (or the last) is the one taken: the WDMA + * off, streaming off, `frame' the ring's slot that holds it. + */ +static bool +sensor_frame(void) +{ + uint32_t ends = 0, last, stride, measure_at = 2, limit = cam_ae ? cam_ae_frames : 2; + uint64_t until, start, per_frame; + struct meter m = { { 0, 0, 0 }, 0, 0 }; + int slot = -1, taken = -1; + + frame_ends = 0; + dma_errors = 0; + if (mmu_translating()) { + say("exynos_camera: 5: the SysMMU is on (MMU_CTRL %#x): no DMA\n", rd(mmu, MMU_CTRL)); + return false; + } + if (!buffer_get() || !mode_load() || !stream_start()) { + return false; + } + start = mach_absolute_time(); + nanoseconds_to_absolutetime(200 * NSEC_PER_MSEC, &per_frame); + until = start + per_frame * 3; /* the first frame: the stream's start */ + while (ends < limit + 4) { + slot = frame_wait(until, 0); + if (slot < 0) { + break; + } + ends++; + until = mach_absolute_time() + per_frame; + if (ends < measure_at) { + continue; + } + meter(buffer + (uint64_t)slot * SLOT_BYTES, &m); + if (ends <= 6 || ends >= limit) { + say("exynos_camera: 5: frame %u (slot %d, CSIS1 FRM_CNT_CH0 %u): green %u, red %u, blue %u of %u (aim %u), " + "%u per mille clipped; exposure %u lines, gain %#x, digital %#x\n", ends, slot, + rd(csis, CSIS_FRM_CNT_CH0), m.mean[1], m.mean[0], m.mean[2], 1023 - BLACK_LEVEL, AE_AIM, m.clipped, + ae_lines, ae_again, ae_dgain); + } + if (!cam_ae || ends >= limit || ae_step(&m, false)) { + taken = slot; + break; + } + measure_at = ends + 3; + } + dma_stop(); + frame_ends = ends; + say("exynos_camera: 5: %u frame end(s) in %llu ms, %u with the WDMA's FIFO error; the frame taken: %u (slot %d), " + "exposure %u lines (%u us), gain %#x, digital %#x; the WDMA off; streaming off\n", ends, + absolutetime_ms(mach_absolute_time() - start), dma_errors, ends, taken, ae_lines, + ae_lines * 263 / 10, ae_again, ae_dgain); + frames_halt(); + if (taken < 0) { + taken = slot; + } + if (taken < 0) { + say("exynos_camera: 5: no frame end\n"); + return false; + } + shot_slot = (uint32_t)taken; + frame = buffer + (uint64_t)shot_slot * SLOT_BYTES; + last = buffer_report("5"); + if (last == 0) { + say("exynos_camera: 5: nothing reached the buffer\n"); + return false; + } + stride = last / FRAME_H; + patch_mean(stride < LINE_BYTES ? LINE_BYTES : stride); + return ends > 0; +} + +/* ---- the run ---- */ + +/* camshot (cam_s9=6): the USB console's request, taken by the camera's thread (hakios_exynos_camera_shot) */ +static volatile uint32_t shot_ready; /* the thread waits for a request */ +static volatile uint32_t shot_asked; /* a request is there */ +static volatile uint32_t shots_closed; /* the run is ending: no more */ +static volatile uint32_t stream_live; /* cam_s9=7: the stream's thread owns the camera ... */ +static volatile uint32_t stream_done; /* ... and has put it all down again */ +static uint32_t shot_n, shot_pattern, n_camshots; +static bool shots_enabled; /* cam_s9=6 */ + +/* On the way back to the loader (a panic's too): everything this run switched on, off again, the DMA first. */ +void +hakios_exynos_camera_quiesce(void) +{ + shots_closed = 1; + if (stream_live && !stream_done) { + /* the stream's thread puts the camera down itself when it sees the end (it polls every millisecond) */ + for (uint32_t i = 0; i < 20000 && !__atomic_load_n(&stream_done, __ATOMIC_ACQUIRE); i++) { + spin_us(10); + } + if (stream_done) { + return; + } + } + if (!powered && !domain_mine && !phy_mine && !dma_on && !pattern_on && !top_gate_mine) { + return; + } + leaving = true; + say("exynos_camera: at the run's end: the WDMA and the pattern stopped, the front camera powered down\n"); + dma_stop(); + if (streaming && peric0 != 0) { + stream_stop(); + } + if (powered && peric0 != 0 && peric1 != 0) { + power_down(); + } + isp_down(); +} + +/* step 1's blocks: the pins, CMU_TOP, the two buses (once a run) */ +static bool +map_blocks(void) +{ + if (peric0 != 0) { + return true; + } + say("exynos_camera: 1: map the pins' GPIO blocks, CMU_TOP and the two buses (USI10 of PERIC1 for the S2MPB02, " + "USI08_I2C for the sensor)\n"); + peric0 = ml_io_map(GPIO_PERIC0_PA, 0x1000); + peric1 = ml_io_map(GPIO_PERIC1_PA, 0x1000); + cmu_top = ml_io_map(CMU_TOP_PA, 0x4000); + if (peric0 == 0 || peric1 == 0 || cmu_top == 0 || !hakios_hsi2c_map(&pmic_bus, say) || + !hakios_hsi2c_map(&sensor_bus, say)) { + say("exynos_camera: 1: a block could not be mapped\n"); + peric0 = 0; + return false; + } + return true; +} + +/* step 1's reads: the S2MPB02's ID and the four LDOs as found (what ldo_on() and ldo_off() go by) */ +static bool +look_supplies(void) +{ + uint8_t id = 0; + + hakios_hsi2c_power(&pmic_bus); + hakios_hsi2c_reset(&pmic_bus); + if (pmic_get(PMIC_ID, &id) != 0) { + say("exynos_camera: 1: the S2MPB02 does not answer at 0x%x (%x)\n", PMIC, pmic_bus.last_error); + return false; + } + say("exynos_camera: 1: S2MPB02 ID %#x\n", id); + if (pmic_get(B2CTRL1, &b2ctrl1_found) != 0 || pmic_get(B2CTRL2, &b2ctrl2_found) != 0) { + say("exynos_camera: 1: BUCK2's registers cannot be read (%x)\n", pmic_bus.last_error); + return false; + } + say("exynos_camera: 1: VDD_EXT_1P3_PB02 (BUCK2): B2CTRL1 %#x (%s), B2CTRL2 %#x (voltage code; Android's %#x, 1.3 V)\n", + b2ctrl1_found, (b2ctrl1_found & BUCK_ON) == BUCK_ON ? "on" : (b2ctrl1_found & BUCK_ON) ? "in another mode" : "off", + b2ctrl2_found, BUCK2_VSEL); + for (uint32_t i = 0; i < NLDOS; i++) { + if (pmic_get(ldos[i].reg, &found[i]) != 0) { + say("exynos_camera: 1: %s cannot be read (%x)\n", ldos[i].name, pmic_bus.last_error); + return false; + } + say("exynos_camera: 1: %s: %#x (%s, voltage code %#x, Android's %#x)\n", ldos[i].name, found[i], + (found[i] & LDO_ON) ? "on" : "off", found[i] & LDO_VSEL, ldos[i].vsel); + } + return true; +} + +/* the sensor's bus as it stands: the HSI2C's registers, its USI and CMU_PERIC1 clocks, the pins, CIS_CLK1 */ +static void +bus_look(const char *step) +{ + vm_offset_t r = sensor_bus.regs, c = sensor_bus.cmu; + + say("exynos_camera: %s: the sensor's HSI2C (0x108F0000): CTL %x FIFO_CTL %x INT_EN %x INT_STAT %x FIFO_STAT %x CONF %x " + "TIMEOUT %x TRANS_STATUS %x TIMING_FS1 %x FS2 %x FS3 %x USI_CON %x USI_OPT %x; %llu transfers, %llu errors, the " + "last %x (INT_STAT: 0x100 abort, 0x200 no ack, 0x400 no device, 0x800 time-out)\n", step, rd(r, 0x00), + rd(r, 0x04), rd(r, 0x20), rd(r, 0x24), rd(r, 0x30), rd(r, 0x40), rd(r, 0x48), rd(r, 0x50), rd(r, 0x60), + rd(r, 0x64), rd(r, 0x68), rd(r, 0xC4), rd(r, 0xC8), sensor_bus.transfers, sensor_bus.errors, + sensor_bus.last_error); + say("exynos_camera: %s: its USI mode word (0x1081102C) %x (4: I2C); CMU_PERIC1: mux %x, divider %x, gates %x %x %x %x " + "(bit 21 on, 20 manual), Q-channel %x\n", step, *(volatile uint32_t *)sensor_bus.usi, rd(c, sensor_bus.mux), + rd(c, sensor_bus.div), rd(c, sensor_bus.gates[0]), rd(c, sensor_bus.gates[1]), rd(c, sensor_bus.gates[2]), + rd(c, sensor_bus.gates[3]), rd(c, sensor_bus.qch)); + say("exynos_camera: %s: pins (CON DAT PUD DRV): gpp5 (the bus, pins 2 and 3) %x %x %x %x; gpc1 (MCLK1, pin 5) %x %x %x " + "%x; gpg2 (the reset, pin 5) %x %x %x %x\n", step, rd(peric1 + GPP5, 0), rd(peric1 + GPP5, 4), + rd(peric1 + GPP5, 8), rd(peric1 + GPP5, 0xC), rd(peric1 + GPC1, 0), rd(peric1 + GPC1, 4), rd(peric1 + GPC1, 8), + rd(peric1 + GPC1, 0xC), rd(peric0 + GPG2, 0), rd(peric0 + GPG2, 4), rd(peric0 + GPG2, 8), rd(peric0 + GPG2, 0xC)); + say("exynos_camera: %s: CIS_CLK1 (CMU_TOP): mux %x (bit 0: 0 OSCCLK), divider %x ([4:0] + 1), gate %x (bit 21 on, 20 " + "manual), Q-channel %x: MCLK %s\n", step, rd(cmu_top, CIS1_MUX), rd(cmu_top, CIS1_DIV), rd(cmu_top, CIS1_GATE), + rd(cmu_top, CIS1_QCH), ((rd(cmu_top, CIS1_MUX) & 1) == 0 && (rd(cmu_top, CIS1_DIV) & 0x1f) == 0 && + (rd(cmu_top, CIS1_GATE) & (GATE_MANUAL | GATE_ON)) == (GATE_MANUAL | GATE_ON)) ? "26 MHz, gated on" : + "NOT 26 MHz and on"); +} + +/* + * The bus scanned, reads only: a one-byte read at each 7-bit address, the ones that acknowledge listed. Then only the + * other addresses a 3H1 may sit at are asked for the model ID (two address bytes written, two read: never at 0x50-0x57, + * where an EEPROM would take the second byte as data). true: the sensor found, sensor_addr set. + */ +static const uint8_t sensor_alternatives[] = { 0x10, 0x1A, 0x20, 0x2D, 0x36, 0x5A }; + +static bool +bus_scan(void) +{ + char list[3 * 112 + 1]; + uint8_t seen[0x78] = { 0 }, b, model[2]; + uint32_t n = 0, len = 0; + + for (uint8_t a = 0x08; a <= 0x77; a++) { + if (hakios_hsi2c_message(&sensor_bus, a, true, &b, 1, true) == 0) { + seen[a] = 1; + n++; + len += (uint32_t)snprintf(list + len, sizeof(list) - len, " %02x", a); + } + } + say("exynos_camera: 2: the bus scanned (a one-byte read at 0x08-0x77, nothing written): %u address(es) answer%s\n", + n, n ? list : ": none (no device on the bus is powered, or the bus itself is wrong: the registers above)"); + for (uint32_t i = 0; i < sizeof(sensor_alternatives); i++) { + uint8_t a = sensor_alternatives[i]; + + if (!seen[a]) { + continue; + } + sensor_addr = a; + model[0] = model[1] = 0; + if (sensor_get(0x0000, model, 2) == 0 && ((model[0] << 8) | model[1]) != 0 && + ((model[0] << 8) | model[1]) != 0xffff) { + say("exynos_camera: 2: at %#x: model ID %#06x: THE SENSOR IS AT %#x (not the device tree's 0x35); used for the " + "run\n", a, (model[0] << 8) | model[1], a); + return true; + } + say("exynos_camera: 2: at %#x: no model ID (%02x %02x)\n", a, model[0], model[1]); + } + sensor_addr = sensor_addr_given; + return false; +} + +/* step 2: the front camera's power-up in Android's order, and its ID read; false (and powered down) if it does not answer */ +static bool +sensor_up(void) +{ + uint8_t model[2] = { 0 }, revision = 0, frames = 0, mode = 0; + uint32_t error; + + for (uint32_t i = 0; i < NLDOS; i++) { + if (ldos[i].switched && (found[i] & LDO_ON) && (found[i] & LDO_VSEL) != ldos[i].vsel) { + say("exynos_camera: 2: %s is on at another voltage: someone else has it; nothing is written\n", ldos[i].name); + return false; + } + } + if ((b2ctrl1_found & BUCK_ON) != 0 && ((b2ctrl1_found & BUCK_ON) != BUCK_ON || b2ctrl2_found != BUCK2_VSEL)) { + say("exynos_camera: 2: BUCK2 is on in another mode or at another voltage (%#x, %#x): someone else has it; " + "nothing is written\n", b2ctrl1_found, b2ctrl2_found); + return false; + } + say("exynos_camera: 2: the front camera's power-up (Android's order for star: BUCK2, VDDAF, VDDA, VDDIO, VDDD)\n"); + powered = true; + clock_on(); + pin_out(peric0 + GPG2, RESET_PIN, false); + if (!buck2_on()) { + power_down(); + return false; + } + spin_us(200); + if (!ldo_on(VDDAF) || !ldo_on(VDDA) || !ldo_on(VDDIO) || !ldo_on(VDDD)) { + power_down(); + return false; + } + spin_us(1000); + hakios_hsi2c_power(&sensor_bus); /* "on_i2c": the pins to function 3 (and the bus's clocks) */ + hakios_hsi2c_reset(&sensor_bus); + spin_us(10); + pin_out(peric0 + GPG2, RESET_PIN, true); + pin_function(peric1 + GPC1, MCLK_PIN, 2); + delay_for_interval(9, NSEC_PER_MSEC); + look_pins("2"); + + error = sensor_get(0x0000, model, 2); + if (error != 0) { + say("exynos_camera: 2: the sensor does not answer at %#x (%x): 20 ms more, and again\n", sensor_addr, error); + delay_for_interval(20, NSEC_PER_MSEC); + error = sensor_get(0x0000, model, 2); + } + if (error != 0) { + say("exynos_camera: 2: the sensor does not answer at %#x (%x)\n", sensor_addr, error); + bus_look("2"); + if (!bus_scan()) { + power_down(); + return false; + } + error = sensor_get(0x0000, model, 2); + if (error != 0) { + power_down(); + return false; + } + } + (void)sensor_get(0x0002, &revision, 1); + (void)sensor_get(0x0005, &frames, 1); + (void)sensor_get(0x0100, &mode, 1); + say("exynos_camera: 2: THE SENSOR ANSWERS: model ID %#06x, revision %#x, frame count %u, mode_select %#x " + "(0: standby)\n", (model[0] << 8) | model[1], revision, frames, mode); + return true; +} + +/* everything a run or a shot switched on, off again (the DMA first); the buffer is reused, never given back */ +static void +all_down(void) +{ + dma_stop(); + stream_stop(); + if (powered) { + power_down(); + } + isp_down(); + if (buffer != 0 && (dma_on || pattern_on || !dma_quiet())) { + say("exynos_camera: the WDMA was not seen to stop: no more DMA into %#llx this run\n", buffer_pa); + buffer_busy = true; + } +} + +/* + * ---- camshot: the frame taken as an RGB picture, for the USB console ---- + * + * A bilinear demosaic of the whole frame, 1632 x 1224 (camshot N: every Nth pixel of it across and down): at a green + * place the green as it is and the red and blue from the two neighbours on the line and the two above and below; at a + * red (blue) place the green from its four neighbours and the blue (red) from its four diagonal ones. The pattern's + * order is the sensor's GRBG (fimc-is-cis-3h1.c's cis->bayer_order, OTF_INPUT_ORDER_BAYER_GR_BG; cam_s9_bayer=0-3 for + * RGGB, GRBG, GBRG, BGGR); the frame's edges mirrored. The black level, 64 of 1023, taken off; a grey-world white + * balance from the meter's cells (red and blue to the green, each gain kept within 1/4x and 4x of the green's); the green brought to + * the meter's aim if the exposure could not get it there (at most 4x more); a saturation of cam_s9_sat per cent about + * the luminance (140 unless said: no colour matrix of the sensor's is in the GPL sources, whose setfile A is the + * sensor's registers only; the ISP's tuning, setfile_3h1.bin, is a vendor binary and not read); then a gamma of about + * 1/2.2 (a table of 4096). RAW10 as the WDMA writes it with PACK12 off: a pixel in 16 bits, little-endian, the value in + * [9:0], 3264 bytes a line. The front camera's picture comes out as the sensor sees it (mirrored): no flip is made here. + */ +static const char *const bayer_name[4] = { "RGGB", "GRBG", "GBRG", "BGGR" }; +static uint16_t rows[3][FRAME_W]; /* three lines of the frame, the black level off */ +static int32_t row_of[3] = { -1, -1, -1 }; +static uint8_t raw_line[LINE_BYTES]; /* a line copied out of the uncached mapping */ +static uint8_t out_row[FRAME_W * 3]; +static uint8_t gamma_lut[4096]; +static uint32_t cam_sat = 140; + +static const uint16_t * +row_get(int32_t y, uint32_t stride) +{ + uint32_t i; + + y = y < 0 ? 1 : (y >= FRAME_H ? FRAME_H - 2 : y); /* mirrored: the same colours */ + i = (uint32_t)y % 3; + if (row_of[i] != y) { + memcpy(raw_line, (const void *)(frame + (uint64_t)y * stride), LINE_BYTES); + for (uint32_t x = 0; x < FRAME_W; x++) { + uint32_t v = ((uint32_t)raw_line[2 * x] | ((uint32_t)raw_line[2 * x + 1] << 8)) & 0x3ff; + + rows[i][x] = (uint16_t)(v > BLACK_LEVEL ? v - BLACK_LEVEL : 0); + } + row_of[i] = y; + } + return rows[i]; +} + +static uint32_t +isqrt(uint64_t v) +{ + uint64_t r = 0, bit = 1ULL << 62; + + while (bit > v) { + bit >>= 2; + } + while (bit != 0) { + if (v >= r + bit) { + v -= r + bit; + r = (r >> 1) + bit; + } else { + r >>= 1; + } + bit >>= 2; + } + return (uint32_t)r; +} + +static uint32_t +icbrt(uint64_t v) +{ + uint32_t lo = 0, hi = 2097151; + + while (lo < hi) { + uint32_t mid = (lo + hi + 1) / 2; + + if ((uint64_t)mid * mid * mid <= v) { + lo = mid; + } else { + hi = mid - 1; + } + } + return lo; +} + +/* the gamma: about t^(1/2.2) as 0.73 sqrt(t) + 0.27 cbrt(t), with sRGB's straight foot (12.92 t) near black */ +static void +gamma_make(void) +{ + for (uint32_t i = 0; i < 4096; i++) { + uint32_t s = isqrt((uint64_t)i * (1u << 20) / 4095), c = icbrt((uint64_t)i * (1u << 30) / 4095); + uint32_t y = (73 * s + 27 * c) * 255 / (100 * 1024), foot = i * 1292 * 255 / (4095 * 100); + + y = foot < y ? foot : y; + gamma_lut[i] = (uint8_t)(y > 255 ? 255 : y); + } +} + +/* the picture into RGB_OFF of MEM_PA's mapping, from `frame'; its width and height back */ +static bool +picture(uint32_t n, uint32_t *wp, uint32_t *hp) +{ + const uint8_t *place = bayer_place[cam_bayer & 3]; + uint32_t stride = cam_stride, w = FRAME_W / n, h = FRAME_H / n, k[3]; + uint8_t *out = (uint8_t *)(buffer + RGB_OFF); + struct meter m; + uint64_t t0 = mach_absolute_time(), t1; + + if (stride == 0) { + stride = LINE_BYTES; /* the WDMA's RAW10 with PACK12 off: two bytes a pixel, 3264 a line */ + } + if (stride < LINE_BYTES || (uint64_t)stride * FRAME_H > SLOT_BYTES) { + say("usb: camshot failed: a stride of %u bytes does not hold %u pixels of RAW10 in 16 bits\n", stride, FRAME_W); + return false; + } + if (frame_last != 0 && frame_last < (uint64_t)stride * (FRAME_H - 1)) { + say("exynos_camera: camshot: the DMA wrote %u bytes, %u lines' worth at %u a line: the rest of the picture is " + "the buffer's fill (0xA5)\n", frame_last, frame_last / stride, stride); + } + gamma_make(); + meter(frame, &m); + /* + * gains in 1/256: the green to the aim (1x to 4x more), red and blue to the green's mean, each kept within 1/4x + * and 4x of the green's (a warm lamp's red is well above the green in the raw frame: its gain goes below 1x) + */ + k[1] = AE_AIM * 256 / (m.mean[1] + 1); + k[1] = k[1] < 256 ? 256 : (k[1] > 1024 ? 1024 : k[1]); + for (uint32_t c = 0; c < 3; c += 2) { + uint32_t g = (m.mean[1] + 1) * 256 / (m.mean[c] + 1); + + g = g < 64 ? 64 : (g > 1024 ? 1024 : g); + k[c] = g * k[1] / 256; + } + row_of[0] = row_of[1] = row_of[2] = -1; + for (uint32_t oy = 0; oy < h; oy++) { + int32_t y = (int32_t)(oy * n); + const uint16_t *up = row_get(y - 1, stride), *mid = row_get(y, stride), *dn = row_get(y + 1, stride); + const uint8_t *pl = place + (y & 1) * 2, *pv = place + ((y + 1) & 1) * 2; + uint8_t *o = out_row; + + for (uint32_t ox = 0; ox < w; ox++) { + uint32_t x = ox * n, xl = x ? x - 1 : 1, xr = x + 1 < FRAME_W ? x + 1 : FRAME_W - 2, c = pl[x & 1]; + int32_t v[3], yl; + + if (c == 1) { + v[1] = mid[x]; + v[pl[(x + 1) & 1]] = (mid[xl] + mid[xr]) / 2; + v[pv[x & 1]] = (up[x] + dn[x]) / 2; + } else { + v[c] = mid[x]; + v[1] = (mid[xl] + mid[xr] + up[x] + dn[x]) / 4; + v[2 - c] = (up[xl] + up[xr] + dn[xl] + dn[xr]) / 4; + } + for (uint32_t i = 0; i < 3; i++) { + v[i] = (int32_t)(((uint32_t)v[i] * k[i]) >> 8); + } + yl = (77 * v[0] + 150 * v[1] + 29 * v[2]) >> 8; + for (uint32_t i = 0; i < 3; i++) { + int32_t s = yl + (v[i] - yl) * (int32_t)cam_sat / 100; + + s = s < 0 ? 0 : s * 4; + *o++ = gamma_lut[s > 4095 ? 4095 : s]; + } + } + memcpy(out + (size_t)oy * w * 3, out_row, w * 3); + } + t1 = mach_absolute_time(); + say("exynos_camera: camshot: RAW10 %s at a stride of %u (%u bytes written); the meter's means less the black level " + "R %u G %u B %u of %u (aim %u, %u per mille clipped); gains R %u.%02u G %u.%02u B %u.%02u; saturation %u %%; " + "%u x %u RGB (N = %u: every Nth pixel of the demosaic), made in %llu ms\n", bayer_name[cam_bayer & 3], stride, + frame_last, m.mean[0], m.mean[1], m.mean[2], 1023 - BLACK_LEVEL, AE_AIM, m.clipped, k[0] / 256, + k[0] % 256 * 100 / 256, k[1] / 256, k[1] % 256 * 100 / 256, k[2] / 256, k[2] % 256 * 100 / 256, cam_sat, w, h, + n, absolutetime_ms(t1 - t0)); + *wp = w; + *hp = h; + return true; +} + +extern int hakios_usb_picture(uintptr_t rgb, uint32_t width, uint32_t height, const char *what); /* hakios.dwc3.c */ + +static void +shot_failed(const char *why) +{ + if (snap.taken) { + say("usb: camshot failed: %s; CSIS1 INT_SRC0 %#x INT_SRC1 %#x FRM_CNT_CH0 %u PHY_STATUS %#x; WDMA CTRL %#x " + "ACT_CTRL %#x FCNTSEQ_STAT %#x INT_SRC %#x ERR_CODE %#x; %u frame end(s), %u bytes written\n", why, snap.src0, + snap.src1, snap.frm, snap.phy, snap.ctrl, snap.act, snap.fcnt, snap.isrc, snap.err, frame_ends, frame_last); + } else { + say("usb: camshot failed: %s (no CSIS1/WDMA status: the shot stopped before its DMA; the lines above say " + "where)\n", why); + } +} + +/* the picture made from `frame' and handed to the USB link */ +static void +shot_send(uint32_t n, bool pattern) +{ + uint32_t w = 0, h = 0; + + if (!picture(n, &w, &h)) { + return; + } + if (hakios_usb_picture(buffer + RGB_OFF, w, h, pattern ? "the test pattern" : "the front camera") != 0) { + say("usb: camshot failed: the USB link is not up, or is sending another picture\n"); + } +} + +/* one camshot: steps 2 to 5 again (or 3 and 4: the test pattern), everything off again, the picture to the USB link */ +static void +shot_run(uint32_t n, bool pattern) +{ + const char *why = NULL; + bool got; + + n_camshots++; + snap.taken = false; + frame_last = 0; + frame_ends = 0; + sensor_broken = false; + say("exynos_camera: camshot %u: %s, then the debayered frame (N = %u) to the USB console\n", n_camshots, + pattern ? "one frame of CSIS1's test pattern (steps 3 and 4 again)" : "one frame of the sensor's (steps 2 to 5 " + "again)", n); + if (pattern) { + got = isp_up() && pattern_frame(); + why = got ? NULL : "no frame of the test pattern"; + } else if (!sensor_up()) { + got = false; + why = "the sensor did not come up (its supplies or its ID: the lines above)"; + } else if (!isp_up()) { + got = false; + why = "the ISPPRE domain or its blocks (step 3)"; + } else { + if (mmu_translating()) { + got = false; + why = "the SysMMU is on"; + } else { + phy_on(); + got = sensor_frame() && frame_last != 0; + why = got ? NULL : (frame_last == 0 ? "no byte of the frame reached the buffer" : "no frame end"); + } + } + if (!snap.taken) { + snap_take(); + } + all_down(); + if (!got) { + shot_failed(why); + return; + } + shot_send(n, pattern); +} + +const char *hakios_exynos_camera_shot(uint32_t n, int pattern); + +/* asked by the USB console's "camshot [N] [tp]" (hakios.dwc3.c), from its thread call: never waits. NULL: taken */ +const char * +hakios_exynos_camera_shot(uint32_t n, int pattern) +{ + uint32_t ready = 1; + + if (shots_closed) { + return "the run is ending"; + } + if (n == 0 || n > 16) { + return "N is a number from 1 to 16 (camshot 1: the whole frame, 1632 x 1224; 2: every second pixel, 816 x 612)"; + } + if (!shots_enabled) { + return "not in this run: boot-arg cam_s9=6 (and a kernel with the S9's camera driver)"; + } + if (!__atomic_compare_exchange_n(&shot_ready, &ready, 0, false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { + return "the camera is busy: the boot's camera run (steps 1 to 5) or the last camshot is not finished, or a step " + "failed for good (the log says)"; + } + shot_n = n; + shot_pattern = pattern ? 1 : 0; + __atomic_store_n(&shot_asked, 1, __ATOMIC_RELEASE); + return NULL; +} + +/* cam_s9=6: after the boot's run, the thread stays for the console's camshots (one at a time) */ +/* + * ---- cam_s9=7: the stream, and /dev/hakios-camera ---- + * + * The sensor streams for the rest of the run into the ring (four slots, the WDMA's frame pointers in turn); each + * frame end is taken by the camera's thread, which polls the WDMA every millisecond (no interrupt is wired), gives + * the frame its number (CSIS1's frame counter: frames the thread was too busy to see are counted as lost, not + * renumbered), meters every third frame and steps the exposure half-way to the aim (cam_s9_ae=0: as asked). Every + * ten seconds the log says the frame rate and how many were lost. camshot copies the newest frame out of the ring to + * the still (the ring has three frames' grace, 100 ms, for the copy) and makes the picture from there. + * + * /dev/hakios-camera, as the POCO's driver makes it (bsd/sys/hakios.camera.h, the same structure, so that + * libhakios.media's capture device and camera_test read either): read() waits (a second at the most) for the newest + * frame not read yet and gives a struct hakios_camera_frame, then the frame as MIPI RAW10 (four pixels in five bytes, + * 2040 a line: packed here from the WDMA's 16 bits a pixel, line by line, and turned half a turn), GBRG, black level + * 64; a buffer of the header's size learns the size. write(): HAKIOS_CAMERA_EXPOSURE (a lines, b analog gain; the auto-exposure off while + * they come, back on two seconds after the last, or at once with a = 0), HAKIOS_CAMERA_SENSOR (there is one camera here: taken, and the frames carry the + * number asked for, so that a capture device that asks for its back camera gets these frames), HAKIOS_CAMERA_FOCUS + * (no lens motor: taken, nothing done). EIO once the stream has failed. + */ +#define RAW10_LINE (FRAME_W * 5 / 4) /* 2040 */ +#define LINE_NS 26283 /* 10608 clocks of 403.6 MHz */ +#define STREAM_IDLE_MS 2000 /* a stream goes down this long after the last read */ + +static LCK_GRP_DECLARE(s9cam_grp, "hakios-exynos-camera"); +static LCK_MTX_DECLARE(s9cam_lock, &s9cam_grp); /* the ring's frames' numbers and the commands */ +static LCK_MTX_DECLARE(s9read_lock, &s9cam_grp); /* a reader's copy (the line buffers): never the thread's */ +static struct { + uint64_t sequence, time; + uint32_t lines, again, dgain; +} slot_info[RING_SLOTS]; +static uint64_t stream_newest; /* the newest frame's number (0: none yet) */ +static int32_t stream_newest_slot = -1; +static uint64_t stream_given; /* the newest frame read */ +static bool stream_running, stream_failed; +static uint32_t asked_sensor = 2; /* the frames' "sensor": 2, the front camera, unless asked for another */ +static uint32_t manual_lines, manual_again; +static bool manual, manual_pending; +static uint64_t manual_until; /* the reader's exposure holds until then: two seconds after its last */ +static uint32_t stream_readers; +static uint32_t cam_always; /* cam_s9_always */ +static volatile bool cam_open; /* /dev/hakios-camera is open */ +static uint64_t last_read; /* the time of the last read's start or wake (a reader waiting is a reader) */ +static uint8_t pack_line[LINE_BYTES], packed[RAW10_LINE]; + +static int +s9dev_open(__unused dev_t dev, __unused int flags, __unused int type, __unused struct proc *p) +{ + if (shots_closed) { + return EIO; + } + lck_mtx_lock(&s9cam_lock); + stream_failed = false; /* (a stream that failed is tried again by the next open) */ + cam_open = true; + lck_mtx_unlock(&s9cam_lock); + return 0; +} + +static int +s9dev_close(__unused dev_t dev, __unused int flags, __unused int type, __unused struct proc *p) +{ + cam_open = false; /* (the last close: the stream's thread sees it within a frame) */ + return 0; +} + +static int +s9dev_read(__unused dev_t dev, struct uio *uio, __unused int ioflag) +{ + struct hakios_camera_frame f; + uint64_t since = mach_absolute_time(), up, running; + int32_t slot; + int error = 0; + + nanoseconds_to_absolutetime(NSEC_PER_SEC, &running); + nanoseconds_to_absolutetime(5 * NSEC_PER_SEC, &up); /* (the camera's power-up, mode and first frames) */ + lck_mtx_lock(&s9cam_lock); + __atomic_store_n(&last_read, since, __ATOMIC_RELEASE); + if (!stream_running) { + wakeup(&last_read); + } + while (!stream_failed && (stream_newest_slot < 0 || stream_newest <= stream_given)) { + struct timespec ts = { 0, 100 * NSEC_PER_MSEC }; + + if (mach_absolute_time() - since > (stream_running ? running : up)) { + lck_mtx_unlock(&s9cam_lock); + return EAGAIN; + } + error = msleep(&stream_newest, &s9cam_lock, PZERO | PCATCH, "hakios-camera", &ts); + if (error != 0 && error != EWOULDBLOCK) { + lck_mtx_unlock(&s9cam_lock); + return error; + } + __atomic_store_n(&last_read, mach_absolute_time(), __ATOMIC_RELEASE); + } + if (stream_failed) { + lck_mtx_unlock(&s9cam_lock); + return EIO; + } + slot = stream_newest_slot; + stream_given = stream_newest; + bzero(&f, sizeof(f)); + memcpy(f.magic, HAKIOS_CAMERA_MAGIC, 8); + f.header_bytes = sizeof(f); + f.width = FRAME_W; + f.height = FRAME_H; + f.stride = RAW10_LINE; + f.format = 1; + f.bayer = 3 - (cam_bayer & 3); /* (turned half a turn below: the pattern's order reversed) */ + f.black = BLACK_LEVEL; + f.sequence = slot_info[slot].sequence; + f.time = slot_info[slot].time; + f.exposure_lines = slot_info[slot].lines; + f.line_ns = LINE_NS; + f.gain = slot_info[slot].again * slot_info[slot].dgain / 0x100; + f.gain_unity = 0x20; + f.gain_max = again_max; + f.frame_lines = MODE_LINES; + f.frame_lines_min = MODE_LINES; + f.frame_lines_max = MODE_LINES; + f.margin = MODE_MARGIN; + f.sensor = asked_sensor; + f.focus = HAKIOS_CAMERA_NO_FOCUS; + stream_readers++; + lck_mtx_unlock(&s9cam_lock); + + error = uiomove((caddr_t)&f, (int)MIN((user_ssize_t)sizeof(f), uio_resid(uio)), uio); + /* + * the frame, packed a line at a time (one reader at a time: the line buffers are shared), turned half a turn: the + * S9's front sensor sits a half turn from the iPhone's, and the Camera app showed its frames upside down + */ + lck_mtx_lock(&s9read_lock); + for (uint32_t y = 0; error == 0 && y < FRAME_H && uio_resid(uio) > 0; y++) { + memcpy(pack_line, (const void *)(buffer + (uint64_t)slot * SLOT_BYTES + (uint64_t)(FRAME_H - 1 - y) * LINE_BYTES), + LINE_BYTES); + for (uint32_t x = 0, o = 0; x < FRAME_W; x += 4, o += 5) { + uint32_t v[4]; + + for (uint32_t k = 0; k < 4; k++) { + uint32_t p = FRAME_W - 1 - (x + k); + + v[k] = ((uint32_t)pack_line[2 * p] | ((uint32_t)pack_line[2 * p + 1] << 8)) & 0x3ff; + packed[o + k] = (uint8_t)(v[k] >> 2); + } + packed[o + 4] = (uint8_t)((v[0] & 3) | ((v[1] & 3) << 2) | ((v[2] & 3) << 4) | ((v[3] & 3) << 6)); + } + error = uiomove((caddr_t)packed, (int)MIN((user_ssize_t)RAW10_LINE, uio_resid(uio)), uio); + } + lck_mtx_unlock(&s9read_lock); + lck_mtx_lock(&s9cam_lock); + stream_readers--; + lck_mtx_unlock(&s9cam_lock); + wakeup(&stream_readers); + return error; +} + +static int +s9dev_write(__unused dev_t dev, struct uio *uio, __unused int ioflag) +{ + struct hakios_camera_command c; + + if (uio_resid(uio) < (user_ssize_t)sizeof(c) || uiomove((caddr_t)&c, sizeof(c), uio) != 0) { + return EINVAL; + } + lck_mtx_lock(&s9cam_lock); + switch (c.command) { + case HAKIOS_CAMERA_EXPOSURE: + nanoseconds_to_absolutetime(2 * NSEC_PER_SEC, &manual_until); + manual_until += mach_absolute_time(); + manual = c.a != 0; + manual_lines = c.a; + manual_again = c.b; + manual_pending = manual; + break; + case HAKIOS_CAMERA_SENSOR: + if (c.a <= 3) { + asked_sensor = c.a; + } + break; + case HAKIOS_CAMERA_FOCUS: + case HAKIOS_CAMERA_KEEP: + break; + default: + lck_mtx_unlock(&s9cam_lock); + return EINVAL; + } + lck_mtx_unlock(&s9cam_lock); + return 0; +} + +static const struct cdevsw s9dev_cdevsw = { + .d_open = s9dev_open, .d_close = s9dev_close, .d_read = s9dev_read, .d_write = s9dev_write, + .d_ioctl = eno_ioctl, .d_stop = eno_stop, .d_reset = eno_reset, .d_select = eno_select, + .d_mmap = eno_mmap, .d_strategy = eno_strat, .d_type = 0 +}; + +static void +s9dev_make(void) +{ + int major = cdevsw_add(-1, &s9dev_cdevsw); + + if (major < 0 || devfs_make_node(makedev(major, 0), DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0666, "hakios-camera") == NULL) { + say("exynos_camera: 7: /dev/hakios-camera could not be made (%d)\n", major); + return; + } + say("exynos_camera: 7: /dev/hakios-camera made (major %d): the frames as the POCO's driver gives them\n", major); +} + +/* a stream is wanted: always (cam_s9_always), for a camshot, or by a reader that has read lately */ +static bool +stream_wanted(void) +{ + uint64_t idle; + + nanoseconds_to_absolutetime(STREAM_IDLE_MS * NSEC_PER_MSEC, &idle); + return __atomic_load_n(&shot_asked, __ATOMIC_ACQUIRE) || (!stream_failed && (cam_always || + (cam_open && mach_absolute_time() - __atomic_load_n(&last_read, __ATOMIC_ACQUIRE) < idle))); +} + +/* one stream: the camera up, the frames coming until nobody wants them, everything down again; false if it failed */ +static bool +stream_session(void) +{ + uint64_t start, until, wait, log_at, ten, frm_base, frm_last = 0, frm_now; + uint64_t log_frames = 0, seen = 0, lost = 0, next_meter = 4; + uint32_t timeouts = 0; + struct meter m = { { 0, 0, 0 }, 0, 0 }; + static uint64_t numbered; /* the frames of the earlier streams: the numbers go on */ + bool ok = false; + int slot; + + say("exynos_camera: 7: THE STREAM: the front camera up, 1632 x 1224 RAW10 at 30 frames a second into the ring " + "(%u slots of 4 MB at %#llx), the auto-exposure %s\n", RING_SLOTS, MEM_PA, cam_ae ? "on" : "off"); + dma_errors = 0; + if (!sensor_up() || !isp_up() || mmu_translating()) { + goto down; + } + phy_on(); + if (!buffer_get() || !mode_load() || !stream_start()) { + goto down; + } + frm_base = rd(csis, CSIS_FRM_CNT_CH0); + start = log_at = mach_absolute_time(); + nanoseconds_to_absolutetime(300 * NSEC_PER_MSEC, &wait); + nanoseconds_to_absolutetime(10 * NSEC_PER_SEC, &ten); + log_at += ten; + lck_mtx_lock(&s9cam_lock); + stream_running = true; + lck_mtx_unlock(&s9cam_lock); + while (!shots_closed && stream_wanted()) { + until = mach_absolute_time() + wait; + slot = frame_wait(until, 1000); + if (slot < 0) { + say("exynos_camera: 7: no frame end in 300 ms (CSIS1 INT_SRC0 %#x, FRM_CNT_CH0 %u; WDMA ACT_CTRL %#x)\n", + rd(csis, CSIS_INT_SRC0), rd(csis, CSIS_FRM_CNT_CH0), rd(wdma, DMA_ACT_CTRL)); + if (++timeouts >= 5) { + break; + } + continue; + } + timeouts = 0; + frm_now = (uint32_t)(rd(csis, CSIS_FRM_CNT_CH0) - (uint32_t)frm_base); + frm_now = frm_now <= frm_last ? frm_last + 1 : frm_now; + lost += frm_now - frm_last - 1; + frm_last = frm_now; + seen++; + lck_mtx_lock(&s9cam_lock); + slot_info[slot].sequence = numbered + frm_now; + slot_info[slot].time = mach_absolute_time(); + slot_info[slot].lines = ae_lines; + slot_info[slot].again = ae_again; + slot_info[slot].dgain = ae_dgain; + stream_newest = numbered + frm_now; + stream_newest_slot = slot; + if (manual_pending) { + uint32_t l = manual_lines, a = manual_again; + + manual_pending = false; + lck_mtx_unlock(&s9cam_lock); + l = l < 2 ? 2 : (l > MODE_LINES - MODE_MARGIN ? MODE_LINES - MODE_MARGIN : l); + a = a < again_min ? again_min : (a > again_max ? again_max : a); + (void)exposure_put(l, a, 0x100); + } else { + lck_mtx_unlock(&s9cam_lock); + } + wakeup(&stream_newest); + if (manual && mach_absolute_time() > manual_until) { + manual = false; /* the reader that asked has gone quiet: the kernel's own exposure again */ + say("exynos_camera: 7: no exposure asked for two seconds: the auto-exposure again\n"); + } + if (cam_ae && !manual && frm_now >= next_meter) { + meter(buffer + (uint64_t)slot * SLOT_BYTES, &m); + next_meter = frm_now + (ae_step(&m, true) ? 6 : 3); + } + if (__atomic_load_n(&shot_asked, __ATOMIC_ACQUIRE) && (seen >= cam_ae_frames || !cam_ae)) { + /* camshot: the newest frame out of the ring (the auto-exposure has had its frames), then the picture */ + memcpy((void *)(buffer + STILL_OFF), (const void *)(buffer + (uint64_t)slot * SLOT_BYTES), SLOT_BYTES); + frame = buffer + STILL_OFF; + frame_last = FRAME_H * LINE_BYTES; + say("exynos_camera: camshot %u: frame %llu of the stream (slot %d; exposure %u lines, gain %#x, digital " + "%#x)\n", ++n_camshots, frm_now, slot, ae_lines, ae_again, ae_dgain); + shot_send(shot_n, false); + __atomic_store_n(&shot_asked, 0, __ATOMIC_RELEASE); + __atomic_store_n(&shot_ready, 1, __ATOMIC_RELEASE); + } + if (mach_absolute_time() >= log_at) { + uint64_t ms = absolutetime_ms(mach_absolute_time() - start), frames = frm_now - log_frames; + + meter(buffer + (uint64_t)slot * SLOT_BYTES, &m); + + say("exynos_camera: 7: the stream: frame %llu after %llu ms, %llu.%02llu frames a second over the last ten " + "seconds; %llu seen, %llu lost (the thread busy), %u with the WDMA's FIFO error; exposure %u lines, gain " + "%#x, digital %#x%s; green %u (aim %u)\n", frm_now, ms, frames / 10, frames * 10 % 100, seen, lost, + dma_errors, ae_lines, ae_again, ae_dgain, manual ? " (asked)" : "", m.mean[1], AE_AIM); + log_frames = frm_now; + log_at += ten; + } + } + ok = timeouts < 5; + numbered += frm_last; + dma_stop(); + frames_halt(); + say("exynos_camera: 7: the stream ends (%s): %llu frames, %llu seen, %llu lost, %u FIFO errors\n", + ok ? "nobody reads" : "no frames", frm_last, seen, lost, dma_errors); +down: + all_down(); + lck_mtx_lock(&s9cam_lock); + stream_running = false; + stream_newest_slot = -1; /* (the ring's frames are old by the next stream) */ + while (stream_readers != 0) { + struct timespec ts = { 0, 10 * NSEC_PER_MSEC }; + + (void)msleep(&stream_readers, &s9cam_lock, PZERO, "hakios-camera", &ts); + } + lck_mtx_unlock(&s9cam_lock); + return ok; +} + +/* the thread's wait for a stream to be wanted: a read wakes it, a camshot (or the run's end) is seen within 50 ms */ +static void +stream_wait(void) +{ + lck_mtx_lock(&s9cam_lock); + while (!shots_closed && !stream_wanted()) { + struct timespec ts = { 0, 50 * NSEC_PER_MSEC }; + + (void)msleep(&last_read, &s9cam_lock, PZERO, "hakios-camera-idle", &ts); + } + lck_mtx_unlock(&s9cam_lock); +} + +/* the streams, one after another as they are wanted, until the run ends; readers get EIO from one that failed */ +static void +stream_loop(void) +{ + for (stream_wait(); !shots_closed; stream_wait()) { + if (stream_session()) { + continue; + } + lck_mtx_lock(&s9cam_lock); + stream_failed = true; + lck_mtx_unlock(&s9cam_lock); + wakeup(&stream_newest); + if (__atomic_load_n(&shot_asked, __ATOMIC_ACQUIRE)) { + shot_failed("the stream did not come up (the lines above)"); + __atomic_store_n(&shot_asked, 0, __ATOMIC_RELEASE); + __atomic_store_n(&shot_ready, 1, __ATOMIC_RELEASE); + } + } + lck_mtx_lock(&s9cam_lock); + stream_failed = true; + lck_mtx_unlock(&s9cam_lock); + wakeup(&stream_newest); + __atomic_store_n(&stream_done, 1, __ATOMIC_RELEASE); +} + +static void +shot_loop(void) +{ + if (broken) { + say("exynos_camera: camshot: not taken in this run (a write to the S2MPB02 did not read back)\n"); + return; + } + say("exynos_camera: 6: CAMSHOT READY: the USB console's \"camshot [N]\" (tools/usb-shot.py --cam) powers the front " + "camera up, takes one frame and powers it down again; \"camshot N tp\": CSIS1's test pattern instead\n"); + __atomic_store_n(&shot_ready, 1, __ATOMIC_RELEASE); + while (!shots_closed) { + if (!__atomic_load_n(&shot_asked, __ATOMIC_ACQUIRE)) { + delay_for_interval(20, NSEC_PER_MSEC); + continue; + } + if (broken || buffer_busy) { + say("usb: camshot failed: %s\n", broken ? "a write to the S2MPB02 did not read back earlier in this run" : + "the WDMA was not seen to stop earlier in this run"); + } else { + shot_run(shot_n, shot_pattern != 0); + } + __atomic_store_n(&shot_asked, 0, __ATOMIC_RELEASE); + __atomic_store_n(&shot_ready, 1, __ATOMIC_RELEASE); + } +} + +static void +camera_thread(void *arg, __unused wait_result_t result) +{ + uint32_t steps = (uint32_t)(uintptr_t)arg, shots = steps >= 6, stream = steps >= 7; + bool kept = false; + + steps = steps > 5 ? 5 : steps; + if (!map_blocks()) { + goto out; + } + if (stream) { + look_pins("1"); + if (look_supplies()) { + s9dev_make(); + __atomic_store_n(&shot_ready, 1, __ATOMIC_RELEASE); + say("exynos_camera: 7: the camera is powered when /dev/hakios-camera is read or a camshot is asked for%s\n", + cam_always ? " (cam_s9_always=1: from now to the run's end)" : ""); + stream_live = 1; + stream_loop(); + stream_live = 0; + } + goto out; + } + look_pins("1"); + say("exynos_camera: 1: CIS_CLK1: mux %x, divider %x, gate %x, Q-channel %x\n", rd(cmu_top, CIS1_MUX), + rd(cmu_top, CIS1_DIV), rd(cmu_top, CIS1_GATE), rd(cmu_top, CIS1_QCH)); + if (!look_supplies()) { + goto out; + } + if (steps < 2) { + goto out; + } + + if (!sensor_up()) { + goto down; + } + kept = steps >= 5; + if (kept) { + say("exynos_camera: 2: the sensor stays powered for step 5\n"); + } else { + power_down(); + } + if (steps < 3) { + goto out; + } + + if (!isp_up() || steps < 4) { + goto down; + } + if (!pattern_frame() || steps < 5 || !kept) { + goto down; + } + if (sensor_frame()) { + say("exynos_camera: 5: A FRAME OF THE SENSOR'S IS IN MEMORY (the patch's mean tells a picture from black)\n"); + } +down: + all_down(); +out: + if (shots) { + shot_loop(); + } + thread_terminate(current_thread()); +} + +void +hakios_exynos_camera_init(void) +{ + uint32_t steps = 0, ipclk = 400; + thread_t thread; + + if (!PE_parse_boot_argn("cam_s9", &steps, sizeof(steps)) || steps == 0) { + return; + } + if (pe_arm_get_soc_base_phys() != EXYNOS_SOC_BASE_PA) { + return; /* not the Exynos 9810 */ + } + PE_parse_boot_argn("cam_s9_ipclk", &ipclk, sizeof(ipclk)); + if (ipclk < 26 || ipclk > 400) { + ipclk = 400; + } + pmic_bus.ipclk_mhz = sensor_bus.ipclk_mhz = ipclk; + PE_parse_boot_argn("cam_s9_exp", &cam_exp, sizeof(cam_exp)); + PE_parse_boot_argn("cam_s9_gain", &cam_gain, sizeof(cam_gain)); + if (cam_gain < 0x20 || cam_gain > 0x200) { + cam_gain = 0x40; + } + PE_parse_boot_argn("cam_s9_pack12", &cam_pack12, sizeof(cam_pack12)); + PE_parse_boot_argn("cam_s9_bayer", &cam_bayer, sizeof(cam_bayer)); + PE_parse_boot_argn("cam_s9_stride", &cam_stride, sizeof(cam_stride)); + PE_parse_boot_argn("cam_s9_ispdiv", &cam_ispdiv, sizeof(cam_ispdiv)); + if (cam_ispdiv > 15) { + cam_ispdiv = 1; + } + PE_parse_boot_argn("cam_s9_probe", &cam_probe, sizeof(cam_probe)); + PE_parse_boot_argn("cam_s9_mmu_probe", &cam_mmu_probe, sizeof(cam_mmu_probe)); + { + uint32_t addr = 0; + + if (PE_parse_boot_argn("cam_s9_addr", &addr, sizeof(addr)) && addr >= 0x08 && addr <= 0x77 && + (addr < 0x50 || addr > 0x57)) { + sensor_addr = sensor_addr_given = (uint8_t)addr; + } + } + PE_parse_boot_argn("cam_s9_always", &cam_always, sizeof(cam_always)); + PE_parse_boot_argn("cam_s9_ae", &cam_ae, sizeof(cam_ae)); + PE_parse_boot_argn("cam_s9_ae_frames", &cam_ae_frames, sizeof(cam_ae_frames)); + if (cam_ae_frames < 2 || cam_ae_frames > 30) { + cam_ae_frames = 15; + } + PE_parse_boot_argn("cam_s9_sat", &cam_sat, sizeof(cam_sat)); + if (cam_sat > 300) { + cam_sat = 140; + } + PE_parse_boot_argn("cam_s9_dgain", &cam_dgain, sizeof(cam_dgain)); + if (cam_dgain < 0x100 || cam_dgain > 0x1000) { + cam_dgain = 0x400; + } + if (steps > 7) { + steps = 7; + } + shots_enabled = steps >= 6; + if (kernel_thread_start(camera_thread, (void *)(uintptr_t)steps, &thread) == KERN_SUCCESS) { + thread_deallocate(thread); + } +} From 1ea4bbd8988e0d6a59373990efd8c60ea7394a67 Mon Sep 17 00:00:00 2001 From: Anan Jaser Date: Thu, 12 Nov 2026 16:09:29 +0400 Subject: [PATCH 2/6] Camera (kernel): Star2 rear S5K2L3, front S5K3H1 and telephoto S5K3M3 streams, AK737x focus, CSIS DMA input mux - Rear S5K2L3 on CSIS0 and telephoto S5K3M3 on CSIS2 from Apollo's star2 tree (rails, resets, D-PHY isolation, setfiles A/B extracted from Apollo's fimc-is-cis headers by tools/extract-*-setfile.py) - Lens motors: rear OIS logic rails, front USI09 selector 0x34 (was USI08's 0x30), telephoto on I2C_CAM3 - SYSREG_ISPPRE SC_CON2/4/6: each CSIS DMA takes its own CSIS (fimc-is-v6_0_0 hw-chain); the telephoto's DMA2 had no input and wrote nothing. SC_CON8 is the secure camera's and is never touched (reading it aborts) - The S2MPB02 bus shared with the flashlight is locked; the flashlight starts after the camera; boot flash test off (cherry picked from commit 74ad07789dbcbe3c17b433c8e4d591ec11bd04e3; applied to hakios.exynos_camera_star2.c, the copy of the file this was written on, without its drivers/map and hakios.exynos_hsi2c.c hunks and without the two generated files hakios.s5k2l3_setfiles.h and hakios.s5k3m3_setfiles.h, which hold the sensors' RAM patches: firmware is not committed to this tree; the build makes them with the two extract tools of this commit. The commit after his three says how) --- .../exynos/hakios.exynos_camera_star2.c | 1588 ++++++++++++++++- drivers/camera/exynos/hakios.s2mpb02_torch.c | 96 +- drivers/core/hakios.stubs.c | 5 + drivers/input/hakios.input.c | 3 +- tools/extract-2l3-setfile.py | 134 ++ tools/extract-3m3-setfile.py | 124 ++ tools/hakios-config.py | 5 + 7 files changed, 1850 insertions(+), 105 deletions(-) create mode 100644 tools/extract-2l3-setfile.py create mode 100755 tools/extract-3m3-setfile.py mode change 100755 => 100644 tools/hakios-config.py diff --git a/drivers/camera/exynos/hakios.exynos_camera_star2.c b/drivers/camera/exynos/hakios.exynos_camera_star2.c index 35b0c50ea..8c9b79a96 100644 --- a/drivers/camera/exynos/hakios.exynos_camera_star2.c +++ b/drivers/camera/exynos/hakios.exynos_camera_star2.c @@ -187,6 +187,55 @@ * found translating stops the DMA. Off by default (its clock or Q-channel may be off, and a read * then aborts, as the PCIe SysMMU's did): the SysMMU is taken as off, pass-through, and the DMA * starts without asking it + * cam_s9_rear_probe=N Star2's primary rear camera (S5K2L3 at 0x10 on hsi2c@10860000): N=2 powers it up at the start + * exactly as its stream does (rear_up()), reads its model ID and revision, and powers it down again. + * Only in a star2lte kernel. Telephoto, OIS, AF and aperture are never powered + * cam_s9_rear=1 with 7: the rear camera's stream too (Star2 only): a reader's HAKIOS_CAMERA_SENSOR 1 ends the + * front camera's stream and starts the rear's, 2016 x 1512 RAW10 at 30 from CSIS0 (four lanes at + * 1352 Mb/s), and back. A rear stream that fails is not tried again in the run: the front's + * frames go on, so the front camera stays usable + * cam_s9_rear_exp=L the rear stream's first exposure in lines of 815 ns (default 30000, about 24 ms; its gain starts at 4x) + * cam_s9_rear_ispdiv=D the ISPPRE bus clock for the rear stream, 466 MHz / (D + 1); default 0, Android's rate. + * (Four lanes at 1352 Mb/s are more than the front camera's 233 MHz can take.) + * cam_s9_rear_bayer=N the rear frames' Bayer order (default 1, GRBG: fimc-is-cis-2l3.c's GR_BG with 0x0101 = 3) + * cam_s9_rear_turn=1 the rear frames turned half round for user space (default 0: the sensor flips itself) + * cam_s9_rear_tp=N the rear sensor's MIPI CCS test pattern (0x0600: 1 solid colour, 2 colour bars) for its picture + * cam_s9_tele_probe=1 Star2's telephoto camera (S5K3M3, "rear2", at 0x2D on hsi2c@10880000): powered up at the start + * exactly as its stream does (tele_up()), its model ID and revision read, powered down again. A tele + * that does not answer is not used for the run (kern.hakios.camera_tele = 0). Star2 only + * cam_s9_tele=1 with 7: the telephoto's stream (Star2 only): a reader's HAKIOS_CAMERA_SENSOR 4 starts it, 2016 x + * 1512 RAW10 at 30 (Apollo's mode 4, 2x2 binned) from CSIS2 (four lanes at 1196 Mb/s on the M4S4 + * "mod" D-PHY, 0x16170000). A tele stream that fails is not tried again in the run; the sysctl + * kern.hakios.camera_tele says 0 then, and user space shows the wide camera's frames in its place + * cam_s9_tele_exp=L the tele stream's first exposure in lines of 20.3 us (default 1000; its gain starts at 4x) + * cam_s9_tele_tp=N the tele sensor's MIPI CCS test pattern (0x0600: 1 solid colour, 2 colour bars) for its picture + * cam_s9_tele_bayer=N the tele frames' Bayer order (default 1, GRBG: fimc-is-cis-3m3.c's GR_BG) + * cam_s9_tele_turn=1 the tele frames turned half round for user space (default 0, as Apollo streams it: no flip) + * (0x0101 = 0, no mirror and no flip; whether its module sits the other way up from the wide + * one's is not in the sources: the phone's picture will say) + * + * TELEPHOTO (Star2's "rear2", SENSOR_POSITION_REAR2), from Apollo: fimc-is_sensor_3m3@5A (position 2, id 2: the sensor + * device fimc_is_sensor@16250000, CSIS2; mclk_ch 0; sensor_i2c_ch 2; gpio_reset gpg2-7; gpio_mclk gpc1-4; af on + * i2c_ch 3), fimc-is-3m3@2D on hsi2c@10880000 (setfile "setA"), fimc-is-actuator@0F (ak737x) on hsi2c@10890000, + * fimc-is-device-module-3m3.c (power_setpin, star2: USE_BUCK2_REGULATOR_CONTROL in fimc-is-vendor-config_star2.h), + * fimc-is-cis-3m3.c/.h, -setA.h, dphy_m4s4_csis2 (isolation PMU 0x710 -- its own, the display's DSI0 is 0x70C --, + * reset bit 1 of 0x16210500), fimc-is-hw-csi-v5_1.h (CSI #2 "M4S4_MODULE": the CSIS0 PHY layout, so + 0x1400 on the + * CSIS1 one; csi_hw_s_phy_default_value writes the bias for CSI_ID_A only), the settle 59 (fimc-is-hw-settle-10nm-lpp.h, + * 1196 aligned to 1200), cmucal-sfr.c (I2C_CAM2/3 gates, Q-channels, dividers): + * supplies all on the S2MPB02, none shared with the wide camera: VDD_EXT_1P3_PB02 (BUCK2, 1.3 V; the front camera's + * too), VDDA_2.8V_SUB (LDO11), VDDD_1.05V_SUB (LDO3, 1.05 V), VDDAF_2.8V_SUB (LDO12, and 2 ms), + * VDDIO_1.8V_SUB (LDO7, and 100 us). Apollo (CONFIG_OIS_USE) also powers the OIS here (VDDD_1.8V_OIS, + * VDD_VM_2.8V_OIS on the S2MPB03, VDDD_2.8V_OIS, its reset gpg0-6), shared with the wide camera's module: + * NOT done here. The OIS sits on I2C_CAM1, the wide's lens bus; the tele's sensor (I2C_CAM2) and lens motor + * (I2C_CAM3) have buses of their own, so neither waits on it + * the rest the bus pins gpc0-4/5 to I2C (function 2, pull-ups), 10 us; MCLK0 (CIS_CLK0, gpc1-4: the WIDE camera's + * MCLK too -- one rear sensor is powered at a time) at 26 MHz; the reset gpg2-7 high, 8 ms. Off: the reset + * low, VDDAF, the bus pins inputs, VDDIO, VDDA, VDDD, MCLK0's pin low, BUCK2; CIS_CLK0 as found + * the mode Global (soft reset, 3 ms, the firmware patch), then mode 4's table, then 0x30DA = 0x30DC = 0x0016 (not + * the live-focus dual sync); stream on 0x0100 = 0x0100 (16 bits), off 0x0100 = 0. No group hold + * (USE_GROUP_PARAM_HOLD 0). Line 0x2628 clocks of 481 MHz (20308 ns), frame 0x667 lines, margin 0x1C. + * Its sensor-shift crop (CAMERA_REAR2_SENSOR_SHIFT_CROP, from the module's calibration) is not applied + * the focus its own AK737x at 0x0F on I2C_CAM3 (gpc0-6/7), supplied by VDDAF_2.8V_SUB: af_up() as the others' * * Limits, whatever asks. The S2MPB02 makes other supplies too (the flash LED, the rear camera's lens motor, the grip * sensor, the proximity LED): every write to it goes through one function that takes only the four LDO registers and @@ -235,6 +284,13 @@ #include #include #include +#include +#include "hakios.s5k2l3_setfiles.h" +#include "hakios.s5k3m3_setfiles.h" + +/* the S2MPB02's I2C controller is the flashlight's too (hakios.s2mpb02_torch.c): one transfer at a time */ +void hakios_s2mpb02_bus_lock(void); +void hakios_s2mpb02_bus_unlock(void); static bool leaving; @@ -277,11 +333,37 @@ static const struct ldo { { "VDDIO_1.8V_VT (LDO6)", 0x23, 0x30, true }, { "VDDA_2.8V_VT (LDO13)", 0x2A, 0x58, true }, { "VDDAF_2.8V_VT (LDO16)", 0x2D, 0x58, true }, + /* Star2's rear camera: VDDAF_COMMON_CAM, 3.3 V = 108 (0x6C), its module's common lens-motor supply */ + { "VDDAF_COMMON_CAM 3.3V (LDO14)", 0x2B, 0x6C, true }, + /* + * ... and the logic supplies of its module's OIS controller (ois@24), which sits on the lens motor's bus: unpowered + * it held that bus (12 Nov: the AK737x's every transfer timed out). VDDD_1.8V_OIS (LDO10, 1.8 V = 48) and + * VDDD_2.8V_OIS (LDO15, 2.8 V = 88); the OIS motor's VDD_VM_2.8V_OIS and its reset are left alone + */ + { "VDDD_1.8V_OIS (LDO10)", 0x27, 0x30, true }, + { "VDDD_2.8V_OIS (LDO15)", 0x2C, 0x58, true }, + /* + * Star2's telephoto camera (S5K3M3), Apollo's fimc-is-device-module-3m3.c with the star2 device tree's regulators: + * VDDD_1.05V_SUB (LDO3, 1.05 V = 36 of 12.5 mV), VDDIO_1.8V_SUB (LDO7, 48 of 25 mV), VDDA_2.8V_SUB (LDO11, 88), + * VDDAF_2.8V_SUB (LDO12, 88). LDOn's control register is 0x1D + n (s2mpb02-private.h: L1CTRL to L18CTRL in a row) + */ + { "VDDD_1.05V_SUB (LDO3)", 0x20, 0x24, true }, + { "VDDIO_1.8V_SUB (LDO7)", 0x24, 0x30, true }, + { "VDDA_2.8V_SUB (LDO11)", 0x28, 0x58, true }, + { "VDDAF_2.8V_SUB (LDO12)", 0x29, 0x58, true }, }; #define VDDD 0 #define VDDIO 1 #define VDDA 2 #define VDDAF 3 +#define VDDAF_COMMON 4 +#define OIS_1V8 5 +#define OIS_2V8 6 +#define SUB_VDDD 7 /* the telephoto's */ +#define SUB_VDDIO 8 +#define SUB_VDDA 9 +#define SUB_VDDAF 10 +#define FRONT_LDOS 4 /* the front camera's: the first four */ #define NLDOS (sizeof(ldos) / sizeof(ldos[0])) static struct hakios_hsi2c pmic_bus = { @@ -301,6 +383,7 @@ static struct hakios_hsi2c pmic_bus = { #define SENSOR 0x35 /* the device tree's (fimc-is-3h1@35, reg = <53>): 7 bits */ static uint8_t sensor_addr = SENSOR; /* the one used: the scan's or cam_s9_addr's, if not 0x35 */ static uint8_t sensor_addr_given = SENSOR; /* cam_s9_addr's, or 0x35 */ +static uint32_t rear_probe; /* cam_s9_rear_probe: 1 topology, 2 power and read S5K2L3 ID */ static struct hakios_hsi2c sensor_bus = { .name = "cam-3h1", @@ -315,6 +398,93 @@ static struct hakios_hsi2c sensor_bus = { .ipclk_mhz = 400, .bus_hz = 400000, /* clock-frequency = <400000> */ }; +/* Star2 primary rear camera: dedicated camera I2C0 and its S2MPB03 rail PMIC. */ +static struct hakios_hsi2c rear_bus = { + .name = "cam-2l3", .regs_pa = 0x10860000, + .cmu_pa = EXYNOS_CMU_PERIC1_PA, .cmu_size = 0x4000, + .gates = { 0x2020, 0x2004, 0x2060, 0x2064 }, .ngates = 4, + .qch = 0x3008, .div = 0x1800, .mux = 0x0120, + .pins_pa = EXYNOS_GPIO_PERIC1_PA + 0x60, .pins_mask = 0xff, .pins_want = 0x22, + .ipclk_mhz = 400, .bus_hz = 400000, +}; +static struct hakios_hsi2c rear_pmic_bus = { + .name = "cam-s2mpb03", .regs_pa = 0x14300000, + .usi_mode_pa = 0x14212000, /* SYSREG_CMGP: USI_CMGP00 protocol selector */ + .cmu_pa = EXYNOS_CMU_CMGP_PA, .cmu_size = 0x4000, + /* GATE_CLK_USI_CMGP00, its IPCLK/PCLK and reset synchronizer. */ + .gates = { 0x2008, 0x2090, 0x2094, 0x2068 }, .ngates = 4, + .qch = 0x3044, .div = 0x1808, .mux = 0x100c, + /* GPM0-0 and GPM1-0 are separate banks of CMGP's GPIO (0x14220000 + 0x000, + 0x020): rear_pins() sets them */ + .pins_pa = 0, .ipclk_mhz = 400, .bus_hz = 400000, +}; +static uint8_t rear_addr = 0x10; /* fimc-is-2l3@10 */ + +/* + * Star2's telephoto camera: its own camera I2C2 (hsi2c@10880000, I2C_CAM2 of PERIC1, no USI selector), gpc0-4 SCL and + * gpc0-5 SDA (function 2, "hsi2c2-bus", pull-ups pud 3: set by tele_up()). cmucal-sfr.c: GATE_CLK_PERIC1_I2C_CAM2 + * 0x2028, its RSTNSYNC 0x200C, IPCLK 0x2070, PCLK 0x2074; QCH_CON_I2C_CAM2 0x3010; DIV_CLK_PERIC1_I2C_CAM2 0x1808 + */ +static struct hakios_hsi2c tele_bus = { + .name = "cam-3m3", .regs_pa = 0x10880000, + .cmu_pa = EXYNOS_CMU_PERIC1_PA, .cmu_size = 0x4000, + .gates = { 0x2028, 0x200c, 0x2070, 0x2074 }, .ngates = 4, + .qch = 0x3010, .div = 0x1808, .mux = 0x0120, + .pins_pa = EXYNOS_GPIO_PERIC1_PA + 0x60, .pins_mask = 0x00ff0000u, .pins_want = 0x00220000u, + .ipclk_mhz = 400, .bus_hz = 400000, +}; +static uint8_t tele_addr = 0x2D; /* fimc-is-3m3@2D */ + +/* + * ---- the two sensors' paths ---- + * + * The front camera (S5K3H1 on CSIS1, everything above and below) and, on Star2 with cam_s9_rear=1, the primary rear + * camera (S5K2L3 on CSIS0). The stream has one of them at a time (`cur'): the one the frames' reader asked for with + * HAKIOS_CAMERA_SENSOR. What differs between them is here; what they share (the ISPPRE domain, its clocks, the DMA + * common, the ring, the auto-exposure, /dev/hakios-camera) is the same code for both. + */ +struct cam_path { + const char *name; + uint32_t sensor; /* the frames' sensor (hakios.camera.h): 2 the front, 1 the wide (rear), 4 the tele */ + struct hakios_hsi2c *bus; + uint8_t *addr; + uint64_t csis_pa, phy_pa, wdma_pa; + uint32_t phy_size; + uint32_t phy_off; /* the D-PHY's slave registers from the M0S4S2 (CSIS1) layout: 0x1400 on M4S4 */ + uint32_t iso_off; /* its isolation in the PMU */ + uint32_t reset_bit; /* its reset in SYSREG_ISPPRE 0x500 */ + bool iso_shared; /* the isolation is the display's DSI's too: set if need be, never cleared */ + bool bias; /* BIAS_CTRL_01/03/05/06 as csi_hw_s_phy_default_value has them for CSI_ID_A */ + uint32_t lanes, settle, interleave; + uint32_t w, h, slot_bytes; + uint32_t mode_lines, margin, line_ns; + uint32_t bayer; /* the order the sensor sends: 0 RGGB, 1 GRBG, 2 GBRG, 3 BGGR */ + bool turn; /* the frames turned half round for user space */ + uint32_t exp, gain; /* the first exposure (lines) and analog gain of a stream */ + vm_offset_t csis, dphy, wdma; /* mapped */ +}; +enum { FRONT, REAR, TELE, NPATHS }; +static struct cam_path paths[NPATHS]; +static struct cam_path *cur = &paths[FRONT]; +static uint32_t cam_rear; /* cam_s9_rear: the rear camera's stream */ +static bool rear_broken; /* it failed this run: the front camera's frames only from now on */ +static void rear_down(void); +static bool rear_up(void); +#define TELE_SENSOR 4 /* the telephoto's number in the frames' header and HAKIOS_CAMERA_SENSOR */ +static uint32_t cam_tele; /* cam_s9_tele: the telephoto's stream */ +static uint32_t tele_probe; /* cam_s9_tele_probe: power it up at the start and read its ID */ +static bool tele_broken; /* it failed (or did not answer) this run: never again until the next boot */ +static void tele_down(void); +static bool tele_up(void); +/* + * kern.hakios.camera_tele, for user space (camera_device.c: whether to offer a telephoto stream): 0 none (not Star2, + * cam_s9_tele=0, or it failed: its probe or a stream), 1 its stream is offered (not probed at the start), 2 it answered + * at the start's probe and its stream is offered + */ +static uint32_t tele_state; +SYSCTL_DECL(_kern_hakios); +SYSCTL_UINT(_kern_hakios, OID_AUTO, camera_tele, CTLFLAG_RD | CTLFLAG_LOCKED | CTLFLAG_KERN, &tele_state, 0, + "Star2's telephoto camera: 0 none or failed, 1 offered, 2 offered and answered at the start"); + /* ---- pins and the clock ---- */ #define GPIO_PERIC0_PA EXYNOS_GPIO_PERIC0_PA @@ -330,6 +500,10 @@ static struct hakios_hsi2c sensor_bus = { #define CIS1_DIV EXYNOS_CAM_CIS1_DIV /* [4:0]: the ratio less one */ #define CIS1_GATE EXYNOS_CAM_CIS1_GATE /* bit 20 manual, bit 21 on */ #define CIS1_QCH EXYNOS_CAM_CIS1_QCH /* DMYQCH_CON_DFTMUX_TOP_QCH_CIS_CLK1: [0] enable, [1] clock request */ +#define CIS0_MUX 0x1018 +#define CIS0_DIV 0x1814 +#define CIS0_GATE 0x2018 +#define CIS0_QCH 0x3004 #define GATE_MANUAL (1u << 20) #define GATE_ON (1u << 21) @@ -376,12 +550,40 @@ spin_us(uint32_t us) } } +/* the flashlight's driver shares the controller; on the run's way out (one processor, no locks) it is not asked */ +static void +pmic_lock(void) +{ + if (!leaving) { + hakios_s2mpb02_bus_lock(); + } +} + +static void +pmic_unlock(void) +{ + if (!leaving) { + hakios_s2mpb02_bus_unlock(); + } +} + static uint32_t -pmic_get(uint8_t reg, uint8_t *value) +pmic_get_locked(uint8_t reg, uint8_t *value) { return hakios_hsi2c_read_reg(&pmic_bus, PMIC, reg, value, 1); } +static uint32_t +pmic_get(uint8_t reg, uint8_t *value) +{ + uint32_t error; + + pmic_lock(); + error = pmic_get_locked(reg, value); + pmic_unlock(); + return error; +} + /* what may be written to the S2MPB02: the four LDOs at Android's voltage, on or off; BUCK2 at 1.3 V, on or off */ static bool pmic_allowed(uint8_t reg, uint8_t value) @@ -415,9 +617,12 @@ pmic_put(uint8_t reg, uint8_t value, const char *name) } buf[0] = reg; buf[1] = value; - if (hakios_hsi2c_message(&pmic_bus, PMIC, false, buf, 2, true) != 0 || pmic_get(reg, &back) != 0) { + pmic_lock(); + if (hakios_hsi2c_message(&pmic_bus, PMIC, false, buf, 2, true) != 0 || pmic_get_locked(reg, &back) != 0) { + pmic_unlock(); same = false; } else { + pmic_unlock(); /* BUCK2's control register: its enable bits read back (the rest is as found and not ours) */ same = reg == B2CTRL1 ? (back & BUCK_ON) == (value & BUCK_ON) : back == value; } @@ -550,6 +755,14 @@ power_down(void) if (!powered) { return; } + if (cur == &paths[REAR]) { + rear_down(); + return; + } + if (cur == &paths[TELE]) { + tele_down(); + return; + } pin_out(peric1 + GPC1, MCLK_PIN, false); pin_out(peric0 + GPG2, RESET_PIN, false); spin_us(1000); @@ -578,9 +791,9 @@ static uint32_t sensor_get(uint16_t reg, uint8_t *buf, uint32_t len) { uint8_t address[2] = { (uint8_t)(reg >> 8), (uint8_t)reg }; - uint32_t error = hakios_hsi2c_message(&sensor_bus, sensor_addr, false, address, 2, false); + uint32_t error = hakios_hsi2c_message(cur->bus, *cur->addr, false, address, 2, false); - return error ? error : hakios_hsi2c_message(&sensor_bus, sensor_addr, true, buf, len, true); + return error ? error : hakios_hsi2c_message(cur->bus, *cur->addr, true, buf, len, true); } static void @@ -721,9 +934,11 @@ static const uint32_t qchs[] = { 0x3020, QCH_CSIS1, 0x3028, 0x302C, 0x3038, QCH_ #define HW_FORMAT_RAW10 0x2B #define HW_FORMAT_EMBEDDED 0x12 #define HW_FORMAT_UNKNOWN 0x3F -#define FRAME_W 1632 -#define FRAME_H 1224 +#define FRAME_W (cur->w) /* the stream's sensor's: 1632 x 1224 front, 2016 x 1512 rear */ +#define FRAME_H (cur->h) +#define FRAME_W_MAX 2016 #define LINE_BYTES (FRAME_W * 2) /* the WDMA's RAW10 (PACK12 off): a pixel in 16 bits, little-endian, [9:0] */ +#define LINE_BYTES_MAX (FRAME_W_MAX * 2) #define BUF_BYTES (4u << 20) #define CANARY 0xA5 /* @@ -741,13 +956,69 @@ static const uint32_t qchs[] = { 0x3020, QCH_CSIS1, 0x3028, 0x302C, 0x3038, QCH_ #define MEM_FLOOR EXYNOS9810_MEM_DPU_SCAN_END /* the display's scan-out buffers end here */ #define MEM_CEILING EXYNOS9810_MEM_CAMERA_END /* UFS's own-mode memory */ #define RING_SLOTS 4 -#define SLOT_BYTES BUF_BYTES /* a raw frame's place: 3264 x 1224 = 3995136 bytes, in 4 MB */ +#define SLOT_BYTES (cur->slot_bytes) /* a raw frame's place: 3264 x 1224 in 4 MB front, 4032 x 1512 in 6 MB rear */ #define STILL_OFF (RING_SLOTS * SLOT_BYTES) /* camshot's copy of a frame of the stream */ #define RGB_OFF (STILL_OFF + SLOT_BYTES) /* camshot's picture */ #define RGB_BYTES (FRAME_W * FRAME_H * 3) static const uint64_t other_cmn_pa[3] = { 0x162B0400, 0x162D0400, 0x162E0400 }; /* CSIS0, 2, 3: read only */ +/* + * Star2's rear camera, from Apollo (exynos9810-star2lte_*.dts: fimc_is_sensor@16230000 -- CSIS0, its D-PHY's registers at + * 0x16160000, VC0's WDMA at 0x162B0000 -- and dphy_m4s4_csis0: isolation PMU 0x70C, shared with phy_m4s4top_dsi0, the + * display's; reset bit 0 of 0x16210500; fimc-is-hw-csi-v5_1.h's CSI_ID_A PHY registers, the CSIS1 ones + 0x1400), the + * module file (fimc-is-device-module-2l3.c: mode 14, 2016 x 1512 at 30, four lanes at 1352 Mb/s, CSI_MODE_VC_ONLY, + * PD_NONE) and fimc-is-cis-2l3.c/.h (GR_BG, the integration margin 0x30; the line is 0x338 clocks of 1011.1 MHz, 815 ns). + * The settle, 66, is fimc-is-hw-settle-10nm-lpp.h's at 1350-1360 Mb/s. + */ +#define REAR_CSIS_PA 0x16230000ULL +#define REAR_PHY_PA 0x16160000ULL +#define REAR_WDMA_PA 0x162B0000ULL +#define REAR_ISO 0x70C +#define REAR_SETTLE 66 +#define REAR_LINE_NS 815 +#define REAR_MARGIN 0x30 +/* + * Star2's telephoto, from Apollo: fimc_is_sensor@16250000 (CSIS2; its D-PHY's registers at 0x16170000, VC0's WDMA at + * 0x162D0000, its DMA common at 0x162D0400), dphy_m4s4_csis2 (isolation PMU 0x710, reset bit 1 of 0x16210500), the + * settle of 1196 Mb/s (59), SENSOR_3M3_COARSE_INTEGRATION_TIME_MAX_MARGIN 0x1C + */ +#define TELE_CSIS_PA 0x16250000ULL +#define TELE_PHY_PA 0x16170000ULL +#define TELE_WDMA_PA 0x162D0000ULL +#define TELE_ISO 0x710 +#define TELE_SETTLE 59 +#define TELE_MARGIN 0x1C + +static void +paths_init(void) +{ + paths[FRONT] = (struct cam_path){ + .name = "the front camera (S5K3H1 on CSIS1)", .sensor = 2, .bus = &sensor_bus, .addr = &sensor_addr, + .csis_pa = CSIS1_PA, .phy_pa = CSIS1_PHY_PA, .wdma_pa = CSIS1_WDMA_PA, .phy_size = 0x3000, .phy_off = 0, + .iso_off = PMU_DPHY_ISO_CSIS1, .reset_bit = 2, .iso_shared = false, .bias = false, + .lanes = 2, .settle = SETTLE_719, .interleave = 3, .w = 1632, .h = 1224, .slot_bytes = BUF_BYTES, + .mode_lines = 0x04F2, .margin = 4, .line_ns = 26283, .bayer = 1, .turn = true, .exp = 1000, .gain = 0x40, + }; + paths[REAR] = (struct cam_path){ + .name = "the rear camera (S5K2L3 on CSIS0)", .sensor = 1, .bus = &rear_bus, .addr = &rear_addr, + .csis_pa = REAR_CSIS_PA, .phy_pa = REAR_PHY_PA, .wdma_pa = REAR_WDMA_PA, .phy_size = 0x4000, .phy_off = 0x1400, + .iso_off = REAR_ISO, .reset_bit = 0, .iso_shared = true, .bias = true, + .lanes = 4, .settle = REAR_SETTLE, .interleave = 0, .w = 2016, .h = 1512, .slot_bytes = 6u << 20, + .mode_lines = S2L3_MODE_LINES, .margin = REAR_MARGIN, .line_ns = REAR_LINE_NS, .bayer = 1, .turn = false, + .exp = 30000, .gain = 0x80, /* (indoors the auto-exposure settled at 40656 lines and ~13x: start nearer) */ + }; + /* (the telephoto: see the head of this file; slot as the rear's, 2016 x 1512 x 2 bytes in 6 MB) */ + paths[TELE] = (struct cam_path){ + .name = "the telephoto camera (S5K3M3 on CSIS2)", .sensor = TELE_SENSOR, .bus = &tele_bus, .addr = &tele_addr, + .csis_pa = TELE_CSIS_PA, .phy_pa = TELE_PHY_PA, .wdma_pa = TELE_WDMA_PA, .phy_size = 0x4000, .phy_off = 0x1400, + .iso_off = TELE_ISO, .reset_bit = 1, .iso_shared = false, .bias = false, + .lanes = 4, .settle = TELE_SETTLE, .interleave = 0, .w = 2016, .h = 1512, .slot_bytes = 6u << 20, + .mode_lines = S3M3_MODE_LINES, .margin = TELE_MARGIN, .line_ns = S3M3_MODE_LINE_NS, .bayer = 1, .turn = false, + .exp = 1000, .gain = 0x80, + }; +} + static vm_offset_t pmu, cmu_isppre, sysreg_isppre, csis, dphy, wdma, cdma, mmu, other_cmn[3]; static bool domain_mine; /* this run switched ISPPRE on */ static bool domain_up; /* ISPPRE is on and its blocks may be read */ @@ -757,6 +1028,7 @@ static bool top_gate_mine, cdma_mine, clocks_mine; static uint32_t top_mux_found, top_div_found; static bool top_rate_mine; /* CLKCMU_ISPPRE_BUS's mux and divider are this run's */ static uint32_t cam_ispdiv = 1; /* cam_s9_ispdiv: CLKCMU_ISPPRE_BUS = SHARED1_DIV4 (466 MHz) / (1 + this) */ +#define ISPDIV_NOW (cur != &paths[FRONT] ? rear_ispdiv : cam_ispdiv) /* (the tele: as the rear's) */ static uint32_t user_found, isppre_qch_found[NQCHS]; static vm_offset_t buffer; /* the frame's (write-combined: MEM_PA's mapping) */ static vm_offset_t frame; /* the frame looked at: the ring's slot of the frame taken, or the still */ @@ -837,7 +1109,7 @@ sensor_put(uint16_t reg, uint16_t value, uint32_t bytes) } else { buf[2] = (uint8_t)value; } - error = hakios_hsi2c_message(&sensor_bus, sensor_addr, false, buf, 2 + bytes, true); + error = hakios_hsi2c_message(cur->bus, *cur->addr, false, buf, 2 + bytes, true); if (error != 0) { say("exynos_camera: the sensor did not take %#06x = %#x (%x)\n", reg, value, error); sensor_broken = true; @@ -870,8 +1142,793 @@ static const uint16_t mode_1632x1224[][2] = { { 0xB134, 0x0000 }, { 0x307C, 0x0340 }, { 0x3052, 0x0104 }, { 0x6214, 0x7970 }, { 0x0216, 0x0000 }, }; #define MODE_ENTRIES (sizeof(mode_1632x1224) / sizeof(mode_1632x1224[0])) -#define MODE_LINES 0x04F2 /* 0x0340 as the table leaves it */ -#define MODE_MARGIN 4 /* SENSOR_3H1_COARSE_INTEGRATION_TIME_MAX_MARGIN */ +#define MODE_LINES (cur->mode_lines) /* 0x0340 as the mode table leaves it (3H1 0x04F2, 2L3 0x9F00) */ +#define MODE_MARGIN (cur->margin) /* SENSOR_3H1_ / SENSOR_2L3_COARSE_INTEGRATION_TIME_MAX_MARGIN */ + +/* + * ---- Star2's rear camera: its supplies, its pins, its setfile ---- + * + * Apollo's fimc-is-device-module-2l3.c (power_seq_id 1, the star2 boards), GPIO_SCENARIO_ON: the reset low; on the + * S2MPB03 (0x56 on hsi2c@14300000, USI_CMGP00): VDDD_RET 1.025 V, VDDA 2.95 V, VDDA 2.15 V, VDDD 1.025 V; the S2MPB02's + * VDDAF_COMMON 3.3 V (LDO14); VDDIO 1.8 V, VDDQ 1.1 V and 1 ms; the bus pins to I2C ("on_i2c": gpc0-0/1 function 2 with + * their pull-ups, pud 3); MCLK0 out on gpc1-4 (function 2), 1.5 ms; the reset high. OFF: VDDAF_COMMON, VDDQ, VDDD, VDDA + * 2.95 V, VDDA 2.15 V, MCLK0's pin low, the reset low, the bus pins inputs, VDDD_RET, VDDIO. The OIS, the lens motor, + * the aperture and the telephoto camera (3M3) are not powered: the sensor alone. The S2MPB03's LDOn_CTRL is register + * 2 + n, bit 7 on, [5:0] the voltage (s2mpb03.c: LDO1, 2 and 4 from 700 mV in 12.5 mV steps, 3 from 700 mV in 25 mV, + * 5-7 from 1.8 V in 25 mV). Every write is read back; a rail found on at another voltage is someone else's, and + * nothing is written. Its bus's pins (gpm0-0, gpm1-0 of CMGP's GPIO at 0x14220000, function 2: hsi2c34-bus) are set + * if the boot did not leave them so. + * + * The setfile: Reset_TnP, Global and mode 14's table (2016 x 1512 at 30, PD_NONE), of setfile A for revisions + * 0xA2-0xA4 and of setfile B for 0xA5 (sensor_2l3_cis_select_setfile), from hakios.s5k2l3_setfiles.h. Stream on is + * 0x0100 = 0x0103 in 16 bits (streaming, the picture mirrored and flipped: sensor_2l3_cis_stream_on), off 0x0100 = 0. + * No group hold (USE_GROUP_PARAM_HOLD 0 in fimc-is-cis-2l3.h). + */ +#define S2MPB03 0x56 +#define S2MPB03_ID 0x00 +#define S2MPB03_ON 0x80 +#define S2MPB03_VSEL 0x3f +#define CMGP_GPIO_PA 0x14220000ULL /* pinctrl@14220000: gpm0 at +0x000, gpm1 at +0x020 (CON, DAT, PUD) */ +#define GPC0 0x060 /* PERIC1: the rear sensor's bus, gpc0-0 SCL, gpc0-1 SDA */ +#define REAR_RESET_PIN 6 /* gpg2-6 */ +#define REAR_MCLK_PIN 4 /* gpc1-4: MCLK0 */ +#define REAR_BURST_WORDS 64 /* a burst's words in one message at most */ + +static const struct rear_rail { + const char *name; + uint8_t reg, value; + uint32_t after_us; +} rear_rails[] = { + { "VDDD_RET_1.0V_CAM (LDO4, 1.025 V)", 0x06, 0x9a, 200 }, + { "VDDA_2.95V_CAM (LDO6)", 0x08, 0xae, 200 }, + { "VDDA_2.15V_CAM (LDO5)", 0x07, 0x8e, 200 }, + { "VDDD_1.0V_CAM (LDO1, 1.025 V)", 0x03, 0x9a, 200 }, + { "VDDIO_1.8V_CAM (LDO3)", 0x05, 0xac, 200 }, + { "VDDQ_1.1V_CAM (LDO2)", 0x04, 0xa0, 1000 }, +}; +#define NREAR_RAILS (sizeof(rear_rails) / sizeof(rear_rails[0])) +#define RAIL_VDDD_RET 0 +#define RAIL_VDDA295 1 +#define RAIL_VDDA215 2 +#define RAIL_VDDD 3 +#define RAIL_VDDIO 4 +#define RAIL_VDDQ 5 + +static uint8_t rear_found[NREAR_RAILS]; +static bool rear_mine[NREAR_RAILS]; +static bool rear_mapped, rear_pmic_broken; +static vm_offset_t cmgp_gpio; +static uint32_t c0_found[4]; /* CIS_CLK0's mux, divider, gate and Q-channel as found */ +static bool c0_mine; +static uint32_t rear_rev; /* the sensor's revision, 0x0002 */ +static uint32_t rear_ispdiv; /* the rear stream's CLKCMU_ISPPRE_BUS divider (0: 466 MHz) */ +static uint32_t rear_tp; /* the sensor's test pattern instead of its picture (0: none) */ + +static void pin_out(vm_offset_t bank, uint32_t number, bool high); +static void pin_function(vm_offset_t bank, uint32_t number, uint32_t function); +static bool sensor_put(uint16_t reg, uint16_t value, uint32_t bytes); +static uint32_t sensor_get(uint16_t reg, uint8_t *buf, uint32_t len); +static bool exposure_put(uint32_t lines, uint32_t again, uint32_t dgain); +static void gains_read(void); +static uint32_t sensor_get16(uint16_t reg); + +static bool +rear_map(void) +{ + if (rear_mapped) { + return true; + } + cmgp_gpio = ml_io_map(CMGP_GPIO_PA, 0x1000); + if (cmgp_gpio == 0 || !hakios_hsi2c_map(&rear_pmic_bus, say) || !hakios_hsi2c_map(&rear_bus, say)) { + say("exynos_camera: rear: CMGP's GPIO or a bus could not be mapped\n"); + return false; + } + rear_mapped = true; + return true; +} + +/* the pins as they are, for the log */ +static void +rear_pins_look(const char *when) +{ + say("exynos_camera: rear: %s: gpm0-0/gpm1-0 (S2MPB03 SCL/SDA) CON %x %x PUD %x %x; gpc0 (the sensor's bus) CON %x DAT " + "%x PUD %x; MCLK0 gpc1-4 CON %x; reset gpg2-6 CON %x DAT %u\n", when, rd(cmgp_gpio, 0x000) & 0xf, + rd(cmgp_gpio, 0x020) & 0xf, rd(cmgp_gpio, 0x008) & 0xf, rd(cmgp_gpio, 0x028) & 0xf, rd(peric1 + GPC0, 0) & 0xff, + rd(peric1 + GPC0, 4) & 3, rd(peric1 + GPC0, 8) & 0xff, (rd(peric1 + GPC1, 0) >> (4 * REAR_MCLK_PIN)) & 0xf, + (rd(peric0 + GPG2, 0) >> (4 * REAR_RESET_PIN)) & 0xf, (rd(peric0 + GPG2, 4) >> REAR_RESET_PIN) & 1); +} + +/* a bus's controller as it stands, for the log, when a transfer failed */ +static void +rear_bus_look(struct hakios_hsi2c *b) +{ + vm_offset_t r = b->regs; + + say("exynos_camera: rear: %s: CTL %x FIFO_CTL %x INT_STAT %x FIFO_STAT %x CONF %x TIMEOUT %x TRANS_STATUS %x FS1 %x FS2 " + "%x FS3 %x USI_CON %x; %s %x; %llu transfers, %llu errors, the last %x (INT_STAT: 0x100 abort, 0x200 no ack, 0x400 " + "no device, 0x800 time-out; 1 late)\n", b->name, rd(r, 0x00), rd(r, 0x04), rd(r, 0x24), rd(r, 0x30), rd(r, 0x40), + rd(r, 0x48), rd(r, 0x50), rd(r, 0x60), rd(r, 0x64), rd(r, 0x68), rd(r, 0xC4), b->usi ? "USI mode" : "no USI", + b->usi ? *(volatile uint32_t *)b->usi : 0, b->transfers, b->errors, b->last_error); +} + +static uint32_t +rear_pmic_get(uint8_t reg, uint8_t *value) +{ + uint32_t error = hakios_hsi2c_read_reg(&rear_pmic_bus, S2MPB03, reg, value, 1); + + if (error != 0) { + say("exynos_camera: rear: the S2MPB03's register %#x cannot be read (%#x)\n", reg, error); + } + return error; +} + +/* a rail: on at Apollo's voltage, or back as it was found; read back */ +static bool +rear_rail_put(uint32_t i, bool on) +{ + uint8_t value = on ? rear_rails[i].value : rear_found[i], buf[2] = { rear_rails[i].reg, value }, back = 0; + uint32_t error; + + if (rear_pmic_broken) { + return false; + } + error = hakios_hsi2c_message(&rear_pmic_bus, S2MPB03, false, buf, 2, true); + if (error == 0) { + error = hakios_hsi2c_read_reg(&rear_pmic_bus, S2MPB03, rear_rails[i].reg, &back, 1); + } + if (error != 0 || back != value) { + say("exynos_camera: rear: %s: wrote %#x, read back %#x (error %#x): no more writing to the S2MPB03 this run\n", + rear_rails[i].name, value, back, error); + rear_pmic_broken = true; + return false; + } + return true; +} + +static bool +rear_rail_on(uint32_t i) +{ + if (rear_found[i] & S2MPB03_ON) { + say("exynos_camera: rear: %s was on already (%#x): left as it is\n", rear_rails[i].name, rear_found[i]); + return true; + } + rear_mine[i] = true; + if (!rear_rail_put(i, true)) { + return false; + } + say("exynos_camera: rear: %s on (%#x)\n", rear_rails[i].name, rear_rails[i].value); + spin_us(rear_rails[i].after_us); + return true; +} + +static void +rear_rail_off(uint32_t i) +{ + if (rear_mine[i]) { + bool was = rear_pmic_broken; + + rear_pmic_broken = false; /* switching off is always tried */ + if (rear_rail_put(i, false)) { + rear_mine[i] = false; + } + rear_pmic_broken = rear_pmic_broken || was; + } +} + +/* CIS_CLK0 (MCLK0) at 26 MHz: OSCCLK, undivided, gated on, as clock_on() does CIS_CLK1 */ +static void +clock0_on(void) +{ + c0_found[0] = rd(cmu_top, CIS0_MUX); + c0_found[1] = rd(cmu_top, CIS0_DIV); + c0_found[2] = rd(cmu_top, CIS0_GATE); + c0_found[3] = rd(cmu_top, CIS0_QCH); + if ((c0_found[0] & 1) != 0 || (c0_found[1] & 0x1f) != 0) { + wr(cmu_top, CIS0_MUX, c0_found[0] & ~1u); + wr(cmu_top, CIS0_DIV, c0_found[1] & ~0x1fu); + } + wr(cmu_top, CIS0_QCH, c0_found[3] | 3); + wr(cmu_top, CIS0_GATE, c0_found[2] | GATE_MANUAL | GATE_ON); + c0_mine = true; + say("exynos_camera: rear: CIS_CLK0: mux %x -> %x, divider %x -> %x, gate %x -> %x (26 MHz from OSCCLK)\n", c0_found[0], + rd(cmu_top, CIS0_MUX), c0_found[1], rd(cmu_top, CIS0_DIV), c0_found[2], rd(cmu_top, CIS0_GATE)); +} + +static void +clock0_off(void) +{ + if (c0_mine) { + wr(cmu_top, CIS0_GATE, c0_found[2]); + wr(cmu_top, CIS0_QCH, c0_found[3]); + wr(cmu_top, CIS0_DIV, c0_found[1]); + wr(cmu_top, CIS0_MUX, c0_found[0]); + c0_mine = false; + } +} + +/* the power-down, in Apollo's GPIO_SCENARIO_OFF order; only what this run switched on */ +static void +rear_down(void) +{ + if (!rear_mapped) { + return; + } + ldo_off(VDDAF_COMMON); + rear_rail_off(RAIL_VDDQ); + rear_rail_off(RAIL_VDDD); + rear_rail_off(RAIL_VDDA295); + rear_rail_off(RAIL_VDDA215); + pin_out(peric1 + GPC1, REAR_MCLK_PIN, false); + pin_out(peric0 + GPG2, REAR_RESET_PIN, false); + spin_us(10); + /* "off_i2c" (hsi2c0-bus-in): the two pins inputs, no pull */ + wr(peric1 + GPC0, 0, rd(peric1 + GPC0, 0) & ~0xffu); + wr(peric1 + GPC0, 8, rd(peric1 + GPC0, 8) & ~0xffu); + ldo_off(OIS_2V8); + ldo_off(OIS_1V8); + rear_rail_off(RAIL_VDDD_RET); + rear_rail_off(RAIL_VDDIO); + clock0_off(); + powered = false; + { + bool left = mine[VDDAF_COMMON]; + + for (uint32_t i = 0; i < NREAR_RAILS; i++) { + left = left || rear_mine[i]; + } + say("exynos_camera: rear: powered down: MCLK0 and the reset low, the bus pins inputs, the rails %s; CIS_CLK0 as " + "found\n", left ? "NOT ALL OFF (a write failed)" : "back as found"); + } +} + +/* the rear camera up in Apollo's order and its model ID read; false (and powered down) if anything does not answer */ +static bool +rear_up(void) +{ + uint8_t id = 0, model[2] = { 0 }, rev = 0, frames = 0; + uint32_t error; + + if (rear_pmic_broken || broken) { + say("exynos_camera: rear: not powered: a PMIC write did not read back earlier in this run\n"); + return false; + } + if (!rear_map()) { + return false; + } + rear_pins_look("as found"); + if ((rd(cmgp_gpio, 0x000) & 0xf) != 2 || (rd(cmgp_gpio, 0x020) & 0xf) != 2) { + say("exynos_camera: rear: the S2MPB03's bus pins are not on I2C (function 2): set so, without pulls (hsi2c34-bus)\n"); + wr(cmgp_gpio, 0x008, rd(cmgp_gpio, 0x008) & ~0xfu); + wr(cmgp_gpio, 0x028, rd(cmgp_gpio, 0x028) & ~0xfu); + wr(cmgp_gpio, 0x000, (rd(cmgp_gpio, 0x000) & ~0xfu) | 2u); + wr(cmgp_gpio, 0x020, (rd(cmgp_gpio, 0x020) & ~0xfu) | 2u); + } + hakios_hsi2c_power(&rear_pmic_bus); + hakios_hsi2c_reset(&rear_pmic_bus); + if (rear_pmic_get(S2MPB03_ID, &id) != 0) { + say("exynos_camera: rear: THE S2MPB03 DOES NOT ANSWER at %#x: nothing written\n", S2MPB03); + rear_bus_look(&rear_pmic_bus); + return false; + } + for (uint32_t i = 0; i < NREAR_RAILS; i++) { + if (rear_pmic_get(rear_rails[i].reg, &rear_found[i]) != 0) { + rear_bus_look(&rear_pmic_bus); + return false; + } + if ((rear_found[i] & S2MPB03_ON) && rear_found[i] != rear_rails[i].value) { + say("exynos_camera: rear: %s is on at another voltage (%#x, Apollo's %#x): someone else has it; nothing " + "written\n", rear_rails[i].name, rear_found[i], rear_rails[i].value); + return false; + } + } + if ((found[VDDAF_COMMON] & LDO_ON) && (found[VDDAF_COMMON] & LDO_VSEL) != ldos[VDDAF_COMMON].vsel) { + say("exynos_camera: rear: VDDAF_COMMON_CAM is on at another voltage (%#x): nothing written\n", found[VDDAF_COMMON]); + return false; + } + say("exynos_camera: rear: S2MPB03 ID %#x; LDO4 %#x, LDO6 %#x, LDO5 %#x, LDO1 %#x, LDO3 %#x, LDO2 %#x as found; S2MPB02 " + "LDO14 %#x\n", id, rear_found[0], rear_found[1], rear_found[2], rear_found[3], rear_found[4], rear_found[5], + found[VDDAF_COMMON]); + powered = true; + pin_out(peric0 + GPG2, REAR_RESET_PIN, false); + for (uint32_t i = 0; i < NREAR_RAILS; i++) { + if (i == RAIL_VDDIO && !ldo_on(VDDAF_COMMON)) { + goto fail; + } + if (!rear_rail_on(i)) { + goto fail; + } + } + /* the OIS controller's logic supplies (Apollo: after VDDQ, 1.8 V then 2.8 V), unless someone has them elsewhere */ + for (uint32_t i = OIS_1V8; i <= OIS_2V8; i++) { + if ((found[i] & LDO_ON) && (found[i] & LDO_VSEL) != ldos[i].vsel) { + say("exynos_camera: rear: %s is on at another voltage (%#x): left alone\n", ldos[i].name, found[i]); + } else if (!ldo_on(i)) { + goto fail; + } + spin_us(i == OIS_2V8 ? 1000 : 100); + } + /* "on_i2c" (hsi2c0-bus): the bus's clocks, the pins to function 2, and their pull-ups (pud 3), which + * hakios_hsi2c_power() would leave off */ + hakios_hsi2c_power(&rear_bus); + wr(peric1 + GPC0, 8, (rd(peric1 + GPC0, 8) & ~0xffu) | 0x33u); + hakios_hsi2c_reset(&rear_bus); + clock0_on(); + pin_function(peric1 + GPC1, REAR_MCLK_PIN, 2); + spin_us(1500); + pin_out(peric0 + GPG2, REAR_RESET_PIN, true); + delay_for_interval(10, NSEC_PER_MSEC); + rear_pins_look("powered"); + error = sensor_get(0x0000, model, 2); + if (error != 0) { + say("exynos_camera: rear: the S5K2L3 does not answer at %#x (%#x): 20 ms more, and again\n", rear_addr, error); + delay_for_interval(20, NSEC_PER_MSEC); + error = sensor_get(0x0000, model, 2); + } + if (error != 0) { + say("exynos_camera: rear: THE S5K2L3 DOES NOT ANSWER at %#x (%#x)\n", rear_addr, error); + rear_bus_look(&rear_bus); + goto fail; + } + (void)sensor_get(0x0002, &rev, 1); + (void)sensor_get(0x0005, &frames, 1); + rear_rev = rev; + say("exynos_camera: rear: THE S5K2L3 ANSWERS: model ID %#06x, revision %#x (setfile %s), frame count %u\n", + (model[0] << 8) | model[1], rev, rev >= 0xA2 && rev <= 0xA4 ? "A" : rev == 0xA5 ? "B" : "NONE in Apollo", frames); + return true; +fail: + rear_down(); + return false; +} + +/* one of the setfile's tables; a burst's words in messages of REAR_BURST_WORDS at most (the port increments itself) */ +static bool +rear_table(const struct s2l3_reg *t, uint32_t n, const char *what) +{ + uint8_t buf[2 + 2 * REAR_BURST_WORDS]; + uint32_t i = 0, messages = 0, words = 0, error; + uint64_t start = mach_absolute_time(); + const char *who = cur == &paths[TELE] ? "tele" : "rear"; /* (the telephoto's tables come through here too) */ + + delay_for_interval(3, NSEC_PER_MSEC); /* sensor_cis_set_registers' 3 ms */ + while (i < n) { + if (t[i].op == S2L3_BURST) { + uint16_t port = t[i].addr; + uint32_t k = 0; + + buf[0] = (uint8_t)(port >> 8); + buf[1] = (uint8_t)port; + while (i < n && t[i].op == S2L3_BURST && t[i].addr == port && k < REAR_BURST_WORDS) { + buf[2 + 2 * k] = (uint8_t)(t[i].value >> 8); + buf[3 + 2 * k] = (uint8_t)t[i].value; + k++; + i++; + } + error = hakios_hsi2c_message(cur->bus, *cur->addr, false, buf, 2 + 2 * k, true); + if (error != 0) { + say("exynos_camera: %s: %s: a burst of %u words to %#06x not taken at entry %u (%#x)\n", who, what, k, port, + i - k, error); + sensor_broken = true; + return false; + } + messages++; + words += k; + continue; + } + if (t[i].op == S2L3_DELAY) { + delay_for_interval(t[i].value, NSEC_PER_USEC); + } else if (!sensor_put(t[i].addr, t[i].value, t[i].op == S2L3_W8 ? 1 : 2)) { + say("exynos_camera: %s: %s: entry %u of %u not taken\n", who, what, i, n); + return false; + } else { + messages++; + } + i++; + } + say("exynos_camera: %s: %s: %u entries, %u messages (%u burst words) in %llu ms\n", who, what, n, messages, words, + absolutetime_ms(mach_absolute_time() - start)); + return true; +} + +/* the setfile of the sensor's revision, then the exposure; false if the sensor did not take them */ +static bool +rear_mode_load(void) +{ + const struct s2l3_reg *tnp, *global, *mode; + uint32_t ntnp, nglobal, nmode, exp = cur->exp; + char set; + + switch (rear_rev) { + case 0xA2: case 0xA3: case 0xA4: + set = 'A'; + tnp = s2l3_a_reset_tnp; ntnp = sizeof(s2l3_a_reset_tnp) / sizeof(s2l3_a_reset_tnp[0]); + global = s2l3_a_global; nglobal = sizeof(s2l3_a_global) / sizeof(s2l3_a_global[0]); + mode = s2l3_a_mode_2016x1512_30; nmode = sizeof(s2l3_a_mode_2016x1512_30) / sizeof(s2l3_a_mode_2016x1512_30[0]); + break; + case 0xA5: + set = 'B'; + tnp = s2l3_b_reset_tnp; ntnp = sizeof(s2l3_b_reset_tnp) / sizeof(s2l3_b_reset_tnp[0]); + global = s2l3_b_global; nglobal = sizeof(s2l3_b_global) / sizeof(s2l3_b_global[0]); + mode = s2l3_b_mode_2016x1512_30; nmode = sizeof(s2l3_b_mode_2016x1512_30) / sizeof(s2l3_b_mode_2016x1512_30[0]); + break; + default: + say("exynos_camera: rear: revision %#x: Apollo has no setfile for it (0xA2-0xA4 A, 0xA5 B): no stream\n", rear_rev); + return false; + } + exp = exp < 2 ? 2 : (exp > MODE_LINES - MODE_MARGIN ? MODE_LINES - MODE_MARGIN : exp); + say("exynos_camera: rear: the mode: setfile %c's Reset_TnP, Global and 2016 x 1512 at 30 (mode 14); exposure %u " + "lines of %u ns, gain %#x\n", set, exp, cur->line_ns, cur->gain); + if (!rear_table(tnp, ntnp, "Reset_TnP") || !rear_table(global, nglobal, "Global") || + !rear_table(mode, nmode, "2016 x 1512 at 30")) { + return false; + } + if (!sensor_put(0x0702, 0x0000, 2) || !sensor_put(0x021E, 0x0000, 2)) { /* no CIT shifter; no WDR */ + return false; + } + if (rear_tp != 0) { + /* MIPI CCS test_pattern_mode (0x0600): 1 solid colour, 2 colour bars, ... -- the link checked without the optics */ + say("exynos_camera: rear: cam_s9_rear_tp=%u: the sensor's test pattern (0x0600) instead of the picture\n", rear_tp); + if (!sensor_put(0x0600, (uint16_t)rear_tp, 2)) { + return false; + } + } + gains_read(); + if (!exposure_put(exp, cur->gain, 0x100)) { + return false; + } + say("exynos_camera: rear: read back: output %u x %u, frame %#x lines of %#x clocks, exposure %u, gain %#x, digital %#x\n", + sensor_get16(0x034C), sensor_get16(0x034E), sensor_get16(0x0340), sensor_get16(0x0342), sensor_get16(0x0202), + sensor_get16(0x0204), sensor_get16(0x020E)); + return true; +} + +/* + * ---- Star2's telephoto camera (S5K3M3): its supplies, its pins, its setfile (the head of this file says from where) ---- + */ +#define TELE_RESET_PIN 7 /* gpg2-7 */ +#define GPC0_TELE_MASK 0x00ff0000u /* gpc0-4 SCL, gpc0-5 SDA: the tele sensor's bus */ +#define GPC0_TELE_PUD 0x00330000u /* hsi2c2-bus: pin-pud 3 on both */ +#define S5K3M3_MODEL 0x30D3 /* (what a 3M3 is expected to say; Apollo's driver never checks it) */ + +static bool tele_mapped; +static uint32_t tele_rev; +static uint32_t tele_tp; /* cam_s9_tele_tp: the sensor's test pattern instead of its picture */ + +static void +tele_pins_look(const char *when) +{ + say("exynos_camera: tele: %s: gpc0-4/5 (its bus) CON %x DAT %x PUD %x; MCLK0 gpc1-4 CON %x DAT %u; reset gpg2-7 CON %x " + "DAT %u\n", when, (rd(peric1 + GPC0, 0) >> 16) & 0xff, (rd(peric1 + GPC0, 4) >> 4) & 3, + (rd(peric1 + GPC0, 8) >> 16) & 0xff, (rd(peric1 + GPC1, 0) >> (4 * REAR_MCLK_PIN)) & 0xf, + (rd(peric1 + GPC1, 4) >> REAR_MCLK_PIN) & 1, (rd(peric0 + GPG2, 0) >> (4 * TELE_RESET_PIN)) & 0xf, + (rd(peric0 + GPG2, 4) >> TELE_RESET_PIN) & 1); +} + +/* + * The power-down, in Apollo's GPIO_SCENARIO_OFF order (fimc-is-device-module-3m3.c, less the OIS this driver never + * powers for the tele); only what this run switched on goes off (ldo_off, buck2_off), CIS_CLK0 back as found + */ +static void +tele_down(void) +{ + pin_out(peric0 + GPG2, TELE_RESET_PIN, false); + ldo_off(SUB_VDDAF); + spin_us(10); + /* "off_i2c" (hsi2c2-bus-in): the two pins inputs, no pull */ + wr(peric1 + GPC0, 0, rd(peric1 + GPC0, 0) & ~GPC0_TELE_MASK); + wr(peric1 + GPC0, 8, rd(peric1 + GPC0, 8) & ~GPC0_TELE_MASK); + ldo_off(SUB_VDDIO); + ldo_off(SUB_VDDA); + ldo_off(SUB_VDDD); + pin_out(peric1 + GPC1, REAR_MCLK_PIN, false); + buck2_off(); + clock0_off(); + powered = false; + say("exynos_camera: tele: powered down: the reset and MCLK0 low, the bus pins inputs, VDDAF/VDDIO/VDDA/VDDD_SUB and " + "BUCK2 %s; CIS_CLK0 as found\n", (mine[SUB_VDDAF] || mine[SUB_VDDIO] || mine[SUB_VDDA] || mine[SUB_VDDD] || + buck2_mine) ? "NOT ALL OFF (a write failed)" : "back as found"); +} + +/* the telephoto up in Apollo's order and its model ID read; false (and powered down) if anything does not answer */ +static bool +tele_up(void) +{ + static const uint32_t rails[] = { SUB_VDDA, SUB_VDDD, SUB_VDDAF, SUB_VDDIO }; + uint8_t model[2] = { 0 }, rev = 0, frames = 0; + uint32_t error, id; + + if (broken) { + say("exynos_camera: tele: not powered: a write to the S2MPB02 did not read back earlier in this run\n"); + return false; + } + if (!tele_mapped) { + if (!hakios_hsi2c_map(&tele_bus, say)) { + say("exynos_camera: tele: its bus (hsi2c@10880000) could not be mapped\n"); + return false; + } + tele_mapped = true; + } + tele_pins_look("as found"); + for (uint32_t k = 0; k < sizeof(rails) / sizeof(rails[0]); k++) { + uint32_t i = rails[k]; + + if ((found[i] & LDO_ON) && (found[i] & LDO_VSEL) != ldos[i].vsel) { + say("exynos_camera: tele: %s is on at another voltage (%#x, Apollo's %#x): someone else has it; nothing " + "written\n", ldos[i].name, found[i], ldos[i].vsel); + return false; + } + } + if ((b2ctrl1_found & BUCK_ON) != 0 && ((b2ctrl1_found & BUCK_ON) != BUCK_ON || b2ctrl2_found != BUCK2_VSEL)) { + say("exynos_camera: tele: BUCK2 is on in another mode or at another voltage (%#x, %#x): someone else has it; " + "nothing written\n", b2ctrl1_found, b2ctrl2_found); + return false; + } + say("exynos_camera: tele: S2MPB02 as found: BUCK2 %#x (voltage %#x), LDO11 VDDA %#x, LDO3 VDDD %#x, LDO12 VDDAF %#x, " + "LDO7 VDDIO %#x; the OIS is not powered (its bus is the wide camera's lens bus)\n", b2ctrl1_found, b2ctrl2_found, + found[SUB_VDDA], found[SUB_VDDD], found[SUB_VDDAF], found[SUB_VDDIO]); + powered = true; + pin_out(peric0 + GPG2, TELE_RESET_PIN, false); + if (!buck2_on()) { + goto fail; + } + spin_us(200); + if (!ldo_on(SUB_VDDA) || !ldo_on(SUB_VDDD) || !ldo_on(SUB_VDDAF)) { + goto fail; + } + delay_for_interval(2, NSEC_PER_MSEC); /* VDDAF_2.8V_SUB's 2000 us */ + if (!ldo_on(SUB_VDDIO)) { + goto fail; + } + spin_us(100); + /* "on_i2c" (hsi2c2-bus): the bus's clocks, the pins to function 2, their pull-ups (pud 3); 10 us */ + hakios_hsi2c_power(&tele_bus); + wr(peric1 + GPC0, 8, (rd(peric1 + GPC0, 8) & ~GPC0_TELE_MASK) | GPC0_TELE_PUD); + hakios_hsi2c_reset(&tele_bus); + spin_us(10); + /* "pin" function 2: fimc_is_mclk0_fn, with CIS_CLK0 at 26 MHz behind it (the wide camera's MCLK too) */ + clock0_on(); + pin_function(peric1 + GPC1, REAR_MCLK_PIN, 2); + /* the reset high, 8 ms ("10ms delay is needed for I2C communication of the AK7371 actuator") */ + pin_out(peric0 + GPG2, TELE_RESET_PIN, true); + delay_for_interval(8, NSEC_PER_MSEC); + tele_pins_look("powered"); + error = sensor_get(0x0000, model, 2); + if (error != 0) { + say("exynos_camera: tele: the S5K3M3 does not answer at %#x (%#x): 20 ms more, and again\n", tele_addr, error); + delay_for_interval(20, NSEC_PER_MSEC); + error = sensor_get(0x0000, model, 2); + } + if (error != 0) { + say("exynos_camera: tele: THE S5K3M3 DOES NOT ANSWER at %#x (%#x)\n", tele_addr, error); + rear_bus_look(&tele_bus); + goto fail; + } + id = (uint32_t)(model[0] << 8) | model[1]; + if (id == 0 || id == 0xffff) { + say("exynos_camera: tele: model ID %#06x: nobody real answered\n", id); + goto fail; + } + (void)sensor_get(0x0002, &rev, 1); + (void)sensor_get(0x0005, &frames, 1); + tele_rev = rev; + say("exynos_camera: tele: THE S5K3M3 ANSWERS: model ID %#06x%s, revision %#x (setfile A, as Apollo's star2 tree " + "says), frame count %u\n", id, id == S5K3M3_MODEL ? "" : " (NOT 0x30d3, which a 3M3 is expected to say: used all " + "the same)", rev, frames); + return true; +fail: + tele_down(); + return false; +} + +/* Global, then mode 4's table, then the exposure; false if the sensor did not take them */ +static bool +tele_mode_load(void) +{ + uint32_t exp = cur->exp; + + exp = exp < 2 ? 2 : (exp > MODE_LINES - MODE_MARGIN ? MODE_LINES - MODE_MARGIN : exp); + say("exynos_camera: tele: the mode: setfile A's Global and 2016 x 1512 at 30 (mode 4, %u Mb/s a lane, settle %u); " + "exposure %u lines of %u ns, gain %#x\n", S3M3_MODE_MIPI_MBPS, cur->settle, exp, cur->line_ns, cur->gain); + if (!rear_table(s3m3_global, sizeof(s3m3_global) / sizeof(s3m3_global[0]), "Global") || + !rear_table(s3m3_mode_2016x1512_30, sizeof(s3m3_mode_2016x1512_30) / sizeof(s3m3_mode_2016x1512_30[0]), + "2016 x 1512 at 30")) { + return false; + } + /* sensor_3m3_cis_mode_change, CAMERA_REAR2: not the live focus's 24 frames a second, so no dual sync */ + if (!sensor_put(0x30DA, 0x0016, 2) || !sensor_put(0x30DC, 0x0016, 2)) { + return false; + } + if (tele_tp != 0) { + say("exynos_camera: tele: cam_s9_tele_tp=%u: the sensor's test pattern (0x0600) instead of the picture\n", tele_tp); + if (!sensor_put(0x0600, (uint16_t)tele_tp, 2)) { + return false; + } + } + gains_read(); + if (!exposure_put(exp, cur->gain, 0x100)) { + return false; + } + say("exynos_camera: tele: read back: output %u x %u, frame %#x lines of %#x clocks, exposure %u, gain %#x, digital %#x\n", + sensor_get16(0x034C), sensor_get16(0x034E), sensor_get16(0x0340), sensor_get16(0x0342), sensor_get16(0x0202), + sensor_get16(0x0204), sensor_get16(0x020E)); + return true; +} + +/* + * ---- the lens motors (focus) ---- + * + * Both cameras' modules have an AKM AK737x at 0x0C (Apollo's star2 device tree: fimc-is-actuator@0C, "ak737x", on + * hsi2c@10870000 -- the camera's own I2C1, gpc0-2/3 function 2 with pull-ups -- for the rear, id 0; on hsi2c@10900000 -- + * USI09 of PERIC1, gpp5-4/5 function 2, no pull, its USI selector 0x10811034 -- for the front, id 1). Their supplies + * are the camera's (the rear's VDDAF_COMMON, the front's VDDAF_2.8V_VT), on while it is. fimc-is-actuator-ak737x.c: + * a 9-bit position, register 0x00 = position >> 1, 0x01 = (position & 1) << 7; register 0x02 the mode, 0 active, 0x20 + * sleep; at start the first position (vendor_first_pos 120 of 511) and then active. User space asks positions of 0 to + * 1023 (HAKIOS_CAMERA_FOCUS), taken here as 0 to 511. On the way down the lens is walked back towards its stop in + * steps (thrown there it knocks) and the chip put to sleep. Not powered by this driver: the rear module's OIS, which + * sits on the same bus (ois@24): if the rear's motor does not answer, that is the first thing to suspect. + * Star2's telephoto has its own: fimc-is-actuator@0F ("ak737x", id 2, vendor_first_pos 0x78 as the others) on + * hsi2c@10890000 -- I2C_CAM3, gpc0-6/7 function 2 with pull-ups ("hsi2c3-bus") --, nothing else on that bus, supplied + * by VDDAF_2.8V_SUB with the tele (cmucal-sfr.c: gates 0x202C, 0x2010, 0x2078, 0x207C; Q-channel 0x3014; divider 0x180C). + * cam_s9_af=0 no focus (the lens left where its spring holds it) + */ +#define AK737X 0x0C +#define AK737X_TELE 0x0F +#define AK737X_MODE 0x02 +#define AK737X_ACTIVE 0x00 +#define AK737X_SLEEP 0x20 +#define AF_FIRST 240 /* vendor_first_pos 0x78 of 511, in user space's 0-1023 */ +#define AF_PARK 32 +#define AF_PARK_STEP 48 +/* + * usi@10811034: USI09's mode word. The words go two to a USI from USI06's at 0x1081101C (USIn, then USIn_I2C): + * 0x10811030 is USI08_I2C's (disabled in Apollo's tree); with it written instead, USI09 stayed unselected and its + * HSI2C registers all read 0 (12 Nov: the front lens motor "does not answer", CTL 0 just after CTL_MASTER was written). + */ +#define FRONT_AF_USI_PA 0x10811034ULL +#define GPP5_AF_MASK 0x00ff0000u /* gpp5-4 SCL, gpp5-5 SDA */ +#define GPC0_AF_MASK 0x0000ff00u /* gpc0-2 SCL, gpc0-3 SDA */ +#define GPC0_TELE_AF_MASK 0xff000000u /* gpc0-6 SCL, gpc0-7 SDA */ + +static struct hakios_hsi2c rear_af_bus = { + .name = "cam-2l3-af", .regs_pa = 0x10870000, /* I2C_CAM1 of PERIC1: no USI selector */ + .cmu_pa = EXYNOS_CMU_PERIC1_PA, .cmu_size = 0x4000, + /* GATE_CLK_PERIC1_I2C_CAM1, its RSTNSYNC, IPCLK and PCLK; QCH_CON_I2C_CAM1; DIV_CLK_PERIC1_I2C_CAM1 */ + .gates = { 0x2024, 0x2008, 0x2068, 0x206c }, .ngates = 4, + .qch = 0x300c, .div = 0x1804, .mux = 0x0120, + .pins_pa = EXYNOS_GPIO_PERIC1_PA + GPC0, .pins_mask = GPC0_AF_MASK, .pins_want = 0x2200u, + .ipclk_mhz = 400, .bus_hz = 400000, +}; +static struct hakios_hsi2c front_af_bus = { + .name = "cam-3h1-af", .regs_pa = 0x10900000, .usi_mode_pa = FRONT_AF_USI_PA, + .cmu_pa = EXYNOS_CMU_PERIC1_PA, .cmu_size = 0x4000, + /* GATE_CLK_PERIC1_USI09_USI, its RSTNSYNC, IPCLK and PCLK; QCH_CON_USI09_USI; DIV_CLK_PERIC1_USI09_USI */ + .gates = { 0x2044, 0x20a4, 0x2104, 0x2108 }, .ngates = 4, + .qch = 0x3048, .div = 0x1824, .mux = 0x0120, + .pins_pa = EXYNOS_GPIO_PERIC1_PA + GPP5, .pins_mask = GPP5_AF_MASK, .pins_want = 0x00220000u, + .ipclk_mhz = 400, .bus_hz = 400000, +}; +static struct hakios_hsi2c tele_af_bus = { + .name = "cam-3m3-af", .regs_pa = 0x10890000, /* I2C_CAM3 of PERIC1: no USI selector */ + .cmu_pa = EXYNOS_CMU_PERIC1_PA, .cmu_size = 0x4000, + /* GATE_CLK_PERIC1_I2C_CAM3, its RSTNSYNC, IPCLK and PCLK; QCH_CON_I2C_CAM3; DIV_CLK_PERIC1_I2C_CAM3 */ + .gates = { 0x202c, 0x2010, 0x2078, 0x207c }, .ngates = 4, + .qch = 0x3014, .div = 0x180c, .mux = 0x0120, + .pins_pa = EXYNOS_GPIO_PERIC1_PA + GPC0, .pins_mask = GPC0_TELE_AF_MASK, .pins_want = 0x22000000u, + .ipclk_mhz = 400, .bus_hz = 400000, +}; +static uint32_t cam_af = 1; /* cam_s9_af */ +static bool af_ok; /* the stream's lens motor answered and is active */ +static uint32_t af_at = HAKIOS_CAMERA_NO_FOCUS; /* where it was put, 0-1023 */ +static uint32_t af_pending = ~0u; /* a position asked for, not yet written */ + +static struct hakios_hsi2c * +af_bus(void) +{ + return cur == &paths[REAR] ? &rear_af_bus : cur == &paths[TELE] ? &tele_af_bus : &front_af_bus; +} + +/* the stream's lens motor's address: the tele's is 0x0F, the other two 0x0C */ +static uint8_t +af_addr(void) +{ + return cur == &paths[TELE] ? AK737X_TELE : AK737X; +} + +static bool +af_write(uint8_t reg, uint8_t value) +{ + uint8_t b[2] = { reg, value }; + + return hakios_hsi2c_message(af_bus(), af_addr(), false, b, 2, true) == 0; +} + +static bool +af_move(uint32_t at) +{ + uint32_t p = (at > 1023 ? 1023 : at) * 511 / 1023; + + return af_write(0x00, (uint8_t)(p >> 1)) && af_write(0x01, (uint8_t)((p & 1) << 7)); +} + +/* the motor of the stream's camera up and at its first position; af_ok says whether it answered */ +static void +af_up(void) +{ + static bool mapped[NPATHS]; + int i = (int)(cur - paths); + struct hakios_hsi2c *b = af_bus(); + uint8_t mode = 0xff; + uint32_t error; + + af_ok = false; + af_at = HAKIOS_CAMERA_NO_FOCUS; + af_pending = ~0u; + if (!cam_af) { + return; + } + if (!mapped[i]) { + if (!hakios_hsi2c_map(b, say)) { + say("exynos_camera: af: %s: the lens motor's bus could not be mapped: no focus\n", cur->name); + return; + } + mapped[i] = true; + } + hakios_hsi2c_power(b); + if (i == REAR) { + wr(peric1 + GPC0, 8, (rd(peric1 + GPC0, 8) & ~GPC0_AF_MASK) | 0x3300u); /* hsi2c1-bus: pud 3 */ + } else if (i == TELE) { + wr(peric1 + GPC0, 8, (rd(peric1 + GPC0, 8) & ~GPC0_TELE_AF_MASK) | 0x33000000u); /* hsi2c3-bus: pud 3 */ + } + hakios_hsi2c_reset(b); + error = hakios_hsi2c_read_reg(b, af_addr(), AK737X_MODE, &mode, 1); + if (error != 0) { + say("exynos_camera: af: %s: THE LENS MOTOR DOES NOT ANSWER (AK737x at %#x on %s, %#x): no focus%s\n", cur->name, + af_addr(), b->name, error, i == REAR ? " (the module's OIS on the same bus is not powered: suspect it)" : ""); + rear_bus_look(b); + return; + } + if (!af_move(AF_FIRST)) { + say("exynos_camera: af: %s: the first position not taken: no focus\n", cur->name); + return; + } + delay_for_interval(5, NSEC_PER_MSEC); + if (!af_write(AK737X_MODE, AK737X_ACTIVE)) { + say("exynos_camera: af: %s: active mode not taken: no focus\n", cur->name); + return; + } + af_ok = true; + af_at = AF_FIRST; + say("exynos_camera: af: %s: the lens motor answers (mode %#x as found), active at %u of 1023\n", cur->name, mode, + AF_FIRST); +} + +/* walked back towards its stop, asleep, its bus's pins inputs (before the supplies go) */ +static void +af_down(void) +{ + if (af_ok) { + uint32_t at = af_at; + + while (at > AF_PARK) { + at = at > AF_PARK + AF_PARK_STEP ? at - AF_PARK_STEP : AF_PARK; + if (!af_move(at)) { + break; + } + delay_for_interval(4, NSEC_PER_MSEC); + } + (void)af_write(AK737X_MODE, AK737X_SLEEP); + } + af_ok = false; + af_at = HAKIOS_CAMERA_NO_FOCUS; + if (cur == &paths[REAR]) { + wr(peric1 + GPC0, 0, rd(peric1 + GPC0, 0) & ~GPC0_AF_MASK); + wr(peric1 + GPC0, 8, rd(peric1 + GPC0, 8) & ~GPC0_AF_MASK); + } else if (cur == &paths[TELE]) { + wr(peric1 + GPC0, 0, rd(peric1 + GPC0, 0) & ~GPC0_TELE_AF_MASK); /* hsi2c3-bus-in */ + wr(peric1 + GPC0, 8, rd(peric1 + GPC0, 8) & ~GPC0_TELE_AF_MASK); + } else { + wr(peric1 + GPP5, 0, rd(peric1 + GPP5, 0) & ~GPP5_AF_MASK); + } +} /* ---- stopping things (also on the way out: no sleeping, bounded) ---- */ @@ -924,6 +1981,18 @@ stream_stop(void) if (!streaming) { return; } + if (cur == &paths[REAR]) { + (void)sensor_put(0x0100, 0x00, 1); /* sensor_2l3_cis_stream_off */ + streaming = false; + say("exynos_camera: rear: the sensor told to stop streaming (0x0100 = 0)\n"); + return; + } + if (cur == &paths[TELE]) { + (void)sensor_put(0x0100, 0x0000, 2); /* sensor_3m3_cis_stream_off: write16(0x0100, 0) */ + streaming = false; + say("exynos_camera: tele: the sensor told to stop streaming (0x0100 = 0)\n"); + return; + } (void)sensor_put(0x0104, 0x00, 1); (void)sensor_put(0x6028, 0x4000, 2); (void)sensor_put(0x0100, 0x00, 1); @@ -953,11 +2022,12 @@ phy_off(void) if (!phy_mine) { return; } - if (domain_up && (dphy_reset_found & DPHY_RESET_CSIS1) == 0) { - wr(sysreg_isppre, DPHY_RESET, rd(sysreg_isppre, DPHY_RESET) & ~DPHY_RESET_CSIS1); + if (domain_up && (dphy_reset_found & (1u << cur->reset_bit)) == 0) { + wr(sysreg_isppre, DPHY_RESET, rd(sysreg_isppre, DPHY_RESET) & ~(1u << cur->reset_bit)); } - if ((iso_found & ISO_BYPASS) == 0) { - wr(pmu, PMU_DPHY_ISO_CSIS1, rd(pmu, PMU_DPHY_ISO_CSIS1) & ~ISO_BYPASS); + /* (an isolation shared with the display's DSI is the display's: never set back here) */ + if ((iso_found & ISO_BYPASS) == 0 && !cur->iso_shared) { + wr(pmu, cur->iso_off, rd(pmu, cur->iso_off) & ~ISO_BYPASS); } phy_mine = false; } @@ -1080,11 +2150,11 @@ isp_up(void) say("exynos_camera: 3: the PMU could not be mapped\n"); return false; } - iso_found = rd(pmu, PMU_DPHY_ISO_CSIS1); + iso_found = rd(pmu, cur->iso_off); status = rd(pmu, PMU_ISPPRE_STATUS); - say("exynos_camera: 3: DPU status %#x; ISPPRE configuration %#x, status %#x; MEMORY_ISPPRE_OPTION %#x; the D-PHY's " - "isolation (0x14060730) %#x\n", rd(pmu, PMU_DPU_STATUS), rd(pmu, PMU_ISPPRE_CONFIG), status, - rd(pmu, PMU_ISPPRE_MEMORY), iso_found); + say("exynos_camera: 3: %s: DPU status %#x; ISPPRE configuration %#x, status %#x; MEMORY_ISPPRE_OPTION %#x; the D-PHY's " + "isolation (PMU %#x) %#x\n", cur->name, rd(pmu, PMU_DPU_STATUS), rd(pmu, PMU_ISPPRE_CONFIG), status, + rd(pmu, PMU_ISPPRE_MEMORY), cur->iso_off, iso_found); if ((rd(pmu, PMU_DPU_STATUS) & PD_ON) != PD_ON) { say("exynos_camera: 3: the parent domain (DPU) is off: ISPPRE is not switched on under it\n"); return false; @@ -1124,9 +2194,14 @@ isp_up(void) if (cmu_isppre == 0) { cmu_isppre = ml_io_map(CMU_ISPPRE_PA, 0x8000); /* the Q-channels' debug states are at +0x7010 */ sysreg_isppre = ml_io_map(SYSREG_ISPPRE_PA, 0x1000); - csis = ml_io_map(CSIS1_PA, 0x1000); - dphy = ml_io_map(CSIS1_PHY_PA, 0x3000); - wdma = ml_io_map(CSIS1_WDMA_PA, 0x1000); + for (uint32_t i = 0; i < NPATHS; i++) { + if ((i == REAR && !cam_rear) || (i == TELE && !cam_tele)) { + continue; + } + paths[i].csis = ml_io_map(paths[i].csis_pa, 0x1000); + paths[i].dphy = ml_io_map(paths[i].phy_pa, paths[i].phy_size); + paths[i].wdma = ml_io_map(paths[i].wdma_pa, 0x1000); + } cdma = ml_io_map(CSIS_DMA_PA, 0x1000); if (cam_mmu_probe) { mmu = ml_io_map(SYSMMU_PA, 0x1000); @@ -1137,6 +2212,9 @@ isp_up(void) } } } + csis = cur->csis; + dphy = cur->dphy; + wdma = cur->wdma; if (cmu_isppre == 0 || sysreg_isppre == 0 || csis == 0 || dphy == 0 || wdma == 0 || cdma == 0 || (cam_mmu_probe && mmu == 0) || (cam_probe && (other_cmn[0] == 0 || other_cmn[1] == 0 || other_cmn[2] == 0))) { @@ -1162,19 +2240,19 @@ isp_up(void) */ top_mux_found = rd(cmu_top, TOP_ISPPRE_MUX); top_div_found = rd(cmu_top, TOP_ISPPRE_DIV); - if ((top_mux_found & 3u) != ISPPRE_MUX_SHARED1_DIV4 || (top_div_found & 0xfu) != cam_ispdiv) { + if ((top_mux_found & 3u) != ISPPRE_MUX_SHARED1_DIV4 || (top_div_found & 0xfu) != ISPDIV_NOW) { top_rate_mine = true; wr(cmu_top, TOP_ISPPRE_MUX, (top_mux_found & ~3u) | ISPPRE_MUX_SHARED1_DIV4); if (!wait_for(cmu_top, TOP_ISPPRE_MUX, TOP_BUSY, 0, 1000, true)) { say("exynos_camera: 3: CLKCMU_ISPPRE_BUS's mux stays busy (%#x)\n", rd(cmu_top, TOP_ISPPRE_MUX)); } - wr(cmu_top, TOP_ISPPRE_DIV, (top_div_found & ~0xfu) | cam_ispdiv); + wr(cmu_top, TOP_ISPPRE_DIV, (top_div_found & ~0xfu) | ISPDIV_NOW); if (!wait_for(cmu_top, TOP_ISPPRE_DIV, TOP_BUSY, 0, 1000, true)) { say("exynos_camera: 3: CLKCMU_ISPPRE_BUS's divider stays busy (%#x)\n", rd(cmu_top, TOP_ISPPRE_DIV)); } } say("exynos_camera: 3: CLKCMU_ISPPRE_BUS: mux %#x -> %#x, divider %#x -> %#x (SHARED1_DIV4, 466 MHz, / %u)\n", - top_mux_found, rd(cmu_top, TOP_ISPPRE_MUX), top_div_found, rd(cmu_top, TOP_ISPPRE_DIV), cam_ispdiv + 1); + top_mux_found, rd(cmu_top, TOP_ISPPRE_MUX), top_div_found, rd(cmu_top, TOP_ISPPRE_DIV), ISPDIV_NOW + 1); user = user_found = rd(cmu_isppre, ISPPRE_BUS_USER); for (uint32_t i = 0; i < NQCHS; i++) { isppre_qch_found[i] = rd(cmu_isppre, qchs[i]); @@ -1294,14 +2372,16 @@ buffer_report(const char *step) static void phy_on(void) { - say("exynos_camera: 4: the D-PHY (M0S4S2, CSIS1): isolation bypass, PMU 0x730 %#x |= 1; reset bit 2 of 0x16210500 " - "(%#x) cleared, then set\n", iso_found, rd(sysreg_isppre, DPHY_RESET)); + say("exynos_camera: 4: the D-PHY of %s: isolation bypass, PMU %#x %#x |= 1%s; reset bit %u of 0x16210500 (%#x) cleared, " + "then set\n", cur->name, cur->iso_off, iso_found, cur->iso_shared ? " (shared with the display's DSI: set already " + "if the panel is lit, never cleared)" : "", cur->reset_bit, rd(sysreg_isppre, DPHY_RESET)); phy_mine = true; - wr(pmu, PMU_DPHY_ISO_CSIS1, rd(pmu, PMU_DPHY_ISO_CSIS1) | ISO_BYPASS); - wr(sysreg_isppre, DPHY_RESET, rd(sysreg_isppre, DPHY_RESET) & ~DPHY_RESET_CSIS1); - wr(sysreg_isppre, DPHY_RESET, rd(sysreg_isppre, DPHY_RESET) | DPHY_RESET_CSIS1); - say("exynos_camera: 4: the D-PHY: isolation %#x, resets %#x\n", rd(pmu, PMU_DPHY_ISO_CSIS1), - rd(sysreg_isppre, DPHY_RESET)); + if ((rd(pmu, cur->iso_off) & ISO_BYPASS) == 0) { + wr(pmu, cur->iso_off, rd(pmu, cur->iso_off) | ISO_BYPASS); + } + wr(sysreg_isppre, DPHY_RESET, rd(sysreg_isppre, DPHY_RESET) & ~(1u << cur->reset_bit)); + wr(sysreg_isppre, DPHY_RESET, rd(sysreg_isppre, DPHY_RESET) | (1u << cur->reset_bit)); + say("exynos_camera: 4: the D-PHY: isolation %#x, resets %#x\n", rd(pmu, cur->iso_off), rd(sysreg_isppre, DPHY_RESET)); } /* CSIS1 reset and set up for the mode (csi_hw_reset, csi_hw_s_lane, _s_control, _s_config, _s_config_dma(_cmn)) */ @@ -1321,15 +2401,28 @@ link_setup(void) wr(csis, CSIS_INT_SRC0, rd(csis, CSIS_INT_SRC0)); wr(csis, CSIS_INT_SRC1, rd(csis, CSIS_INT_SRC1)); v = rd(csis, CSIS_CMN_CTRL) & ~((3u << CMN_LANES_SHIFT) | (3u << CMN_INTERLEAVE_SHIFT)); - wr(csis, CSIS_CMN_CTRL, v | (1u << CMN_LANES_SHIFT) | (3u << CMN_INTERLEAVE_SHIFT)); + wr(csis, CSIS_CMN_CTRL, v | ((cur->lanes - 1) << CMN_LANES_SHIFT) | (cur->interleave << CMN_INTERLEAVE_SHIFT)); wr(csis, CSIS_PHY_CMN_CTRL, (rd(csis, CSIS_PHY_CMN_CTRL) & ~(0xfu << PHY_ENABLE_DAT_SHIFT)) | - (3u << PHY_ENABLE_DAT_SHIFT)); - /* VC0 RAW10 1632 x 1224, two pixels a clock; CH1 nothing; CH2 VC0's embedded data (0 x 0); CH3 nothing */ + (((1u << cur->lanes) - 1) << PHY_ENABLE_DAT_SHIFT)); + /* + * VC0 RAW10 at the mode's size, two pixels a clock; CH1 nothing; CH2 the front's VC0 embedded data (0 x 0) or, rear + * (CSI_MODE_VC_ONLY, PD_NONE), nothing on VC2; CH3 nothing + */ wr(csis, CSIS_ISP_CONFIG(0), (rd(csis, CSIS_ISP_CONFIG(0)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_RAW10 << 2) | 0); wr(csis, CSIS_ISP_RESOL(0), (FRAME_H << 16) | FRAME_W); - wr(csis, CSIS_ISP_CONFIG(1), (rd(csis, CSIS_ISP_CONFIG(1)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_UNKNOWN << 2) | 1); - wr(csis, CSIS_ISP_RESOL(1), 0); - wr(csis, CSIS_ISP_CONFIG(2), (rd(csis, CSIS_ISP_CONFIG(2)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_EMBEDDED << 2) | 0); + if (cur == &paths[TELE]) { + /* config_module_3m3's mode 4: VC1 carries the phase-detection tail, RAW10 128 x 736 (no WDMA takes it here) */ + wr(csis, CSIS_ISP_CONFIG(1), (rd(csis, CSIS_ISP_CONFIG(1)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_RAW10 << 2) | 1); + wr(csis, CSIS_ISP_RESOL(1), (736u << 16) | 128u); + } else { + wr(csis, CSIS_ISP_CONFIG(1), (rd(csis, CSIS_ISP_CONFIG(1)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_UNKNOWN << 2) | 1); + wr(csis, CSIS_ISP_RESOL(1), 0); + } + if (cur != &paths[FRONT]) { + wr(csis, CSIS_ISP_CONFIG(2), (rd(csis, CSIS_ISP_CONFIG(2)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_UNKNOWN << 2) | 2); + } else { + wr(csis, CSIS_ISP_CONFIG(2), (rd(csis, CSIS_ISP_CONFIG(2)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_EMBEDDED << 2) | 0); + } wr(csis, CSIS_ISP_RESOL(2), 0); wr(csis, CSIS_ISP_CONFIG(3), (rd(csis, CSIS_ISP_CONFIG(3)) & ~0x33ffu) | (1u << 12) | (HW_FORMAT_UNKNOWN << 2) | 3); wr(csis, CSIS_ISP_RESOL(3), 0); @@ -1342,6 +2435,29 @@ link_setup(void) wr(wdma, VCMN + VCMN_DATA_CTRL, rd(wdma, VCMN + VCMN_DATA_CTRL) & ~1u); wr(wdma, VCMN + VCMN_INT_MASK, VCMN_IRQ_MASK); wr(wdma, VCMN + VCMN_INT_SRC, rd(wdma, VCMN + VCMN_INT_SRC)); + { + /* + * The DMA input mux (Apollo fimc-is-v6_0_0/fimc-is-hw-chain.c, fimc_is_hw_s_sensor_mux? the "DMA input MUX" + * loop): SYSREG_ISPPRE SC_CON2/4/6/8 (0x40C/0x414/0x41C/0x424) [1:0] pick what DMA0..3 take; 1: their CSIS's + * own (PD_NONE), 2/3: through PDP0/1. CSISn's WDMA is DMA n here (the DT's dma_ch). Never set by us before: + * CSIS0/1 worked with what the boot left, the telephoto's DMA2 then saw frame starts and ends and wrote + * nothing (12 Nov, build 10: every slot still the fill). + */ + static const uint32_t sc_con[3] = { [FRONT] = 0x414, [REAR] = 0x40C, [TELE] = 0x41C }; + uint32_t off = sc_con[cur - paths], was = rd(sysreg_isppre, off); + + if ((was & 3u) != 1u) { + wr(sysreg_isppre, off, (was & ~3u) | 1u); + } + /* + * Not SC_CON8 (DMA3's, +0x424): the secure camera's (Apollo writes it only without CONFIG_SECURE_CAMERA_USE), + * behind the TrustZone -- reading it was an external abort and a panic at the front camera's first start + * (12 Nov, build 11: FAR 0x16210424). Nor MIPI_PHY_MUX (+0x504), which nothing here needs. + */ + say("exynos_camera: the DMA input mux: SC_CON2 %#x, SC_CON4 %#x, SC_CON6 %#x; this camera's (+%#x) %#x -> %#x\n", + rd(sysreg_isppre, 0x40C), rd(sysreg_isppre, 0x414), rd(sysreg_isppre, 0x41C), off, was, + rd(sysreg_isppre, off)); + } say("exynos_camera: CSIS1 set up: CMN_CTRL %#x, PHY_CMN_CTRL %#x, CH0 %#x %#x, CH2 %#x; WDMA FMT %#x, RESOL %#x; " "its common: DATA_CTRL %#x, OTF_FORMAT %#x\n", rd(csis, CSIS_CMN_CTRL), rd(csis, CSIS_PHY_CMN_CTRL), rd(csis, CSIS_ISP_CONFIG(0)), rd(csis, CSIS_ISP_RESOL(0)), rd(csis, CSIS_ISP_CONFIG(2)), rd(wdma, DMA_FMT), @@ -1547,24 +2663,44 @@ sensor_get16(uint16_t reg) return sensor_get(reg, b, 2) ? 0xffffffffu : (uint32_t)((b[0] << 8) | b[1]); } -/* the D-PHY's timing for 719 Mb/s a lane (csi_hw_s_phy_default_value, csi_hw_s_phy_config, not USE_PHY_LINK) */ +/* + * the D-PHY's timing for the path's rate (csi_hw_s_phy_default_value, csi_hw_s_phy_config, not USE_PHY_LINK): below + * 1.5 Gb/s a lane both paths are, so no skew calibration and HS_MODE_SEL set. CSI_ID_A (the rear's M4S4) also has its + * four bias registers set, as Android sets them whenever the rear camera opens with the panel lit + */ +#define DPHY_BIAS_CTRL(n) (0x400 + 4 * (n)) static void phy_setup(void) { - wr(dphy, DPHY_ACTRL_SC_06, 2); - wr(dphy, DPHY_DCTRL_SC_02, (rd(dphy, DPHY_DCTRL_SC_02) & ~0xffu) | 1u); - for (uint32_t lane = 0; lane < 2; lane++) { - wr(dphy, DPHY_ACTRL_SD_08 + DPHY_LANE(lane), rd(dphy, DPHY_ACTRL_SD_08 + DPHY_LANE(lane)) & ~(3u << 5)); - wr(dphy, DPHY_DCTRL_SD_13 + DPHY_LANE(lane), rd(dphy, DPHY_DCTRL_SD_13 + DPHY_LANE(lane)) & ~1u); - wr(dphy, DPHY_DCTRL_SD_05 + DPHY_LANE(lane), rd(dphy, DPHY_DCTRL_SD_05 + DPHY_LANE(lane)) | (1u << 2)); - wr(dphy, DPHY_DCTRL_SD_06 + DPHY_LANE(lane), (rd(dphy, DPHY_DCTRL_SD_06 + DPHY_LANE(lane)) & ~0xffu) | - SETTLE_719); - } - say("exynos_camera: 5: the D-PHY: SC_06 %#x, SC_02 %#x; lane 0: SD_08 %#x, SD_05 %#x, SD_06 %#x (settle), SD_13 %#x; " - "lane 1: %#x %#x %#x %#x\n", rd(dphy, DPHY_ACTRL_SC_06), rd(dphy, DPHY_DCTRL_SC_02), rd(dphy, DPHY_ACTRL_SD_08), - rd(dphy, DPHY_DCTRL_SD_05), rd(dphy, DPHY_DCTRL_SD_06), rd(dphy, DPHY_DCTRL_SD_13), - rd(dphy, DPHY_ACTRL_SD_08 + DPHY_LANE(1)), rd(dphy, DPHY_DCTRL_SD_05 + DPHY_LANE(1)), - rd(dphy, DPHY_DCTRL_SD_06 + DPHY_LANE(1)), rd(dphy, DPHY_DCTRL_SD_13 + DPHY_LANE(1))); + uint32_t o = cur->phy_off; + + if (cur->bias) { + uint32_t before[4] = { rd(dphy, DPHY_BIAS_CTRL(1)), rd(dphy, DPHY_BIAS_CTRL(3)), rd(dphy, DPHY_BIAS_CTRL(5)), + rd(dphy, DPHY_BIAS_CTRL(6)) }; + + wr(dphy, DPHY_BIAS_CTRL(1), 0x10); + wr(dphy, DPHY_BIAS_CTRL(3), 0x05); + wr(dphy, DPHY_BIAS_CTRL(5), 0x68); + wr(dphy, DPHY_BIAS_CTRL(6), 0x6C); + say("exynos_camera: 5: the M4S4 D-PHY's bias 01/03/05/06: %#x %#x %#x %#x -> 0x10 0x5 0x68 0x6c\n", before[0], + before[1], before[2], before[3]); + } + wr(dphy, o + DPHY_ACTRL_SC_06, 2); + wr(dphy, o + DPHY_DCTRL_SC_02, (rd(dphy, o + DPHY_DCTRL_SC_02) & ~0xffu) | 1u); + for (uint32_t lane = 0; lane < cur->lanes; lane++) { + uint32_t l = o + DPHY_LANE(lane); + + wr(dphy, l + DPHY_ACTRL_SD_08, rd(dphy, l + DPHY_ACTRL_SD_08) & ~(3u << 5)); + wr(dphy, l + DPHY_DCTRL_SD_13, rd(dphy, l + DPHY_DCTRL_SD_13) & ~1u); + wr(dphy, l + DPHY_DCTRL_SD_05, rd(dphy, l + DPHY_DCTRL_SD_05) | (1u << 2)); + wr(dphy, l + DPHY_DCTRL_SD_06, (rd(dphy, l + DPHY_DCTRL_SD_06) & ~0xffu) | cur->settle); + } + say("exynos_camera: 5: the D-PHY (%u lanes, settle %u): SC_06 %#x, SC_02 %#x; lane 0: SD_08 %#x, SD_05 %#x, SD_06 %#x, " + "SD_13 %#x; last lane: %#x %#x %#x %#x\n", cur->lanes, cur->settle, rd(dphy, o + DPHY_ACTRL_SC_06), + rd(dphy, o + DPHY_DCTRL_SC_02), rd(dphy, o + DPHY_ACTRL_SD_08), rd(dphy, o + DPHY_DCTRL_SD_05), + rd(dphy, o + DPHY_DCTRL_SD_06), rd(dphy, o + DPHY_DCTRL_SD_13), + rd(dphy, o + DPHY_ACTRL_SD_08 + DPHY_LANE(cur->lanes - 1)), rd(dphy, o + DPHY_DCTRL_SD_05 + DPHY_LANE(cur->lanes - 1)), + rd(dphy, o + DPHY_DCTRL_SD_06 + DPHY_LANE(cur->lanes - 1)), rd(dphy, o + DPHY_DCTRL_SD_13 + DPHY_LANE(cur->lanes - 1))); } /* the mean of a 64 x 64 patch in the middle, read as RAW10 in 16 bits (the WDMA's) at the stride given */ @@ -1637,7 +2773,7 @@ static const uint8_t bayer_place[4][4] = { /* the colour of a cell's four p static void meter(vm_offset_t raw, struct meter *m) { - const uint8_t *place = bayer_place[cam_bayer & 3]; + const uint8_t *place = bayer_place[cur->bayer & 3]; uint64_t sum[3] = { 0, 0, 0 }; uint32_t n = 0, clip = 0; @@ -1666,11 +2802,13 @@ meter(vm_offset_t raw, struct meter *m) static bool exposure_put(uint32_t lines, uint32_t again, uint32_t dgain) { - bool ok; + bool ok, hold = cur == &paths[FRONT]; /* (the 2L3's driver uses no group hold) */ - ok = sensor_put(0x0104, 0x01, 1) && sensor_put(0x0202, (uint16_t)lines, 2) && sensor_put(0x0204, (uint16_t)again, 2) - && sensor_put(0x020E, (uint16_t)dgain, 2); - (void)sensor_put(0x0104, 0x00, 1); + ok = (!hold || sensor_put(0x0104, 0x01, 1)) && sensor_put(0x0202, (uint16_t)lines, 2) && + sensor_put(0x0204, (uint16_t)again, 2) && sensor_put(0x020E, (uint16_t)dgain, 2); + if (hold) { + (void)sensor_put(0x0104, 0x00, 1); + } if (ok) { ae_lines = lines; ae_again = again; @@ -1754,6 +2892,12 @@ mode_load(void) { uint32_t exp = cam_exp; + if (cur == &paths[REAR]) { + return rear_mode_load(); + } + if (cur == &paths[TELE]) { + return tele_mode_load(); + } exp = exp < 2 ? 2 : exp; exp = exp > MODE_LINES - MODE_MARGIN ? MODE_LINES - MODE_MARGIN : exp; say("exynos_camera: 5: the sensor's mode: setfile A's 1632 x 1224 at 30 frames a second, %u 16-bit writes; then " @@ -1808,14 +2952,28 @@ stream_start(void) wr(csis, CSIS_PHY_CMN_CTRL, rd(csis, CSIS_PHY_CMN_CTRL) | PHY_ENABLE_CLK); wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) & ~CMN_QCHANNEL_EN); wr(csis, CSIS_CMN_CTRL, rd(csis, CSIS_CMN_CTRL) | CMN_CSI_EN); - say("exynos_camera: 5: STREAM ON: 0x0104 = 0, 8 ms, 0x6028 = 0x4000, 0x0100 = 1, 0x0216 = 0\n"); - if (!sensor_put(0x0104, 0x00, 1)) { - return false; - } - delay_for_interval(8, NSEC_PER_MSEC); - streaming = true; - if (!sensor_put(0x6028, 0x4000, 2) || !sensor_put(0x0100, 0x01, 1) || !sensor_put(0x0216, 0x00, 1)) { - return false; + if (cur == &paths[REAR]) { + say("exynos_camera: rear: STREAM ON: 0x0100 = 0x0103 (16 bits: streaming, mirrored and flipped, as Android)\n"); + streaming = true; + if (!sensor_put(0x0100, 0x0103, 2)) { + return false; + } + } else if (cur == &paths[TELE]) { + say("exynos_camera: tele: STREAM ON: 0x0100 = 0x0100 (16 bits: streaming, not mirrored or flipped, as Android)\n"); + streaming = true; + if (!sensor_put(0x0100, 0x0100, 2)) { + return false; + } + } else { + say("exynos_camera: 5: STREAM ON: 0x0104 = 0, 8 ms, 0x6028 = 0x4000, 0x0100 = 1, 0x0216 = 0\n"); + if (!sensor_put(0x0104, 0x00, 1)) { + return false; + } + delay_for_interval(8, NSEC_PER_MSEC); + streaming = true; + if (!sensor_put(0x6028, 0x4000, 2) || !sensor_put(0x0100, 0x01, 1) || !sensor_put(0x0216, 0x00, 1)) { + return false; + } } nanoseconds_to_absolutetime(300 * NSEC_PER_MSEC, &until); until += mach_absolute_time(); @@ -2173,7 +3331,13 @@ sensor_up(void) uint8_t model[2] = { 0 }, revision = 0, frames = 0, mode = 0; uint32_t error; - for (uint32_t i = 0; i < NLDOS; i++) { + if (cur == &paths[REAR]) { + return rear_up(); + } + if (cur == &paths[TELE]) { + return tele_up(); + } + for (uint32_t i = 0; i < FRONT_LDOS; i++) { if (ldos[i].switched && (found[i] & LDO_ON) && (found[i] & LDO_VSEL) != ldos[i].vsel) { say("exynos_camera: 2: %s is on at another voltage: someone else has it; nothing is written\n", ldos[i].name); return false; @@ -2265,10 +3429,10 @@ all_down(void) * [9:0], 3264 bytes a line. The front camera's picture comes out as the sensor sees it (mirrored): no flip is made here. */ static const char *const bayer_name[4] = { "RGGB", "GRBG", "GBRG", "BGGR" }; -static uint16_t rows[3][FRAME_W]; /* three lines of the frame, the black level off */ +static uint16_t rows[3][FRAME_W_MAX]; /* three lines of the frame, the black level off */ static int32_t row_of[3] = { -1, -1, -1 }; -static uint8_t raw_line[LINE_BYTES]; /* a line copied out of the uncached mapping */ -static uint8_t out_row[FRAME_W * 3]; +static uint8_t raw_line[LINE_BYTES_MAX]; /* a line copied out of the uncached mapping */ +static uint8_t out_row[FRAME_W_MAX * 3]; static uint8_t gamma_lut[4096]; static uint32_t cam_sat = 140; @@ -2542,8 +3706,9 @@ hakios_exynos_camera_shot(uint32_t n, int pattern) * number asked for, so that a capture device that asks for its back camera gets these frames), HAKIOS_CAMERA_FOCUS * (no lens motor: taken, nothing done). EIO once the stream has failed. */ -#define RAW10_LINE (FRAME_W * 5 / 4) /* 2040 */ -#define LINE_NS 26283 /* 10608 clocks of 403.6 MHz */ +#define RAW10_LINE (FRAME_W * 5 / 4) /* 2040 front, 2520 rear */ +#define RAW10_LINE_MAX (FRAME_W_MAX * 5 / 4) +#define LINE_NS (cur->line_ns) /* front: 10608 clocks of 403.6 MHz */ #define STREAM_IDLE_MS 2000 /* a stream goes down this long after the last read */ static LCK_GRP_DECLARE(s9cam_grp, "hakios-exynos-camera"); @@ -2565,7 +3730,7 @@ static uint32_t stream_readers; static uint32_t cam_always; /* cam_s9_always */ static volatile bool cam_open; /* /dev/hakios-camera is open */ static uint64_t last_read; /* the time of the last read's start or wake (a reader waiting is a reader) */ -static uint8_t pack_line[LINE_BYTES], packed[RAW10_LINE]; +static uint8_t pack_line[LINE_BYTES_MAX], packed[RAW10_LINE_MAX]; static int s9dev_open(__unused dev_t dev, __unused int flags, __unused int type, __unused struct proc *p) @@ -2591,7 +3756,9 @@ static int s9dev_read(__unused dev_t dev, struct uio *uio, __unused int ioflag) { struct hakios_camera_frame f; + const struct cam_path *p; uint64_t since = mach_absolute_time(), up, running; + uint32_t w, h; int32_t slot; int error = 0; @@ -2622,14 +3789,18 @@ s9dev_read(__unused dev_t dev, struct uio *uio, __unused int ioflag) } slot = stream_newest_slot; stream_given = stream_newest; + /* the stream's path stays as it is while a reader holds a slot (a session ends only when readers are done) */ + p = cur; + w = p->w; + h = p->h; bzero(&f, sizeof(f)); memcpy(f.magic, HAKIOS_CAMERA_MAGIC, 8); f.header_bytes = sizeof(f); - f.width = FRAME_W; - f.height = FRAME_H; - f.stride = RAW10_LINE; + f.width = w; + f.height = h; + f.stride = w * 5 / 4; f.format = 1; - f.bayer = 3 - (cam_bayer & 3); /* (turned half a turn below: the pattern's order reversed) */ + f.bayer = p->turn ? 3 - (p->bayer & 3) : (p->bayer & 3); /* (a half turn reverses the pattern's order) */ f.black = BLACK_LEVEL; f.sequence = slot_info[slot].sequence; f.time = slot_info[slot].time; @@ -2642,32 +3813,34 @@ s9dev_read(__unused dev_t dev, struct uio *uio, __unused int ioflag) f.frame_lines_min = MODE_LINES; f.frame_lines_max = MODE_LINES; f.margin = MODE_MARGIN; - f.sensor = asked_sensor; - f.focus = HAKIOS_CAMERA_NO_FOCUS; + f.sensor = p->sensor; /* the frames' own sensor: a reader that asked for another sees it is not yet */ + f.focus = af_ok ? af_at : HAKIOS_CAMERA_NO_FOCUS; stream_readers++; lck_mtx_unlock(&s9cam_lock); error = uiomove((caddr_t)&f, (int)MIN((user_ssize_t)sizeof(f), uio_resid(uio)), uio); /* - * the frame, packed a line at a time (one reader at a time: the line buffers are shared), turned half a turn: the - * S9's front sensor sits a half turn from the iPhone's, and the Camera app showed its frames upside down + * the frame, packed a line at a time (one reader at a time: the line buffers are shared); the front camera's turned + * half a turn (the S9's front sensor sits a half turn from the iPhone's, and the Camera app showed its frames upside + * down); the rear's as the sensor sends it (0x0101 = 3: it mirrors and flips itself, as on Android) */ lck_mtx_lock(&s9read_lock); - for (uint32_t y = 0; error == 0 && y < FRAME_H && uio_resid(uio) > 0; y++) { - memcpy(pack_line, (const void *)(buffer + (uint64_t)slot * SLOT_BYTES + (uint64_t)(FRAME_H - 1 - y) * LINE_BYTES), - LINE_BYTES); - for (uint32_t x = 0, o = 0; x < FRAME_W; x += 4, o += 5) { + for (uint32_t y = 0; error == 0 && y < h && uio_resid(uio) > 0; y++) { + uint32_t row = p->turn ? h - 1 - y : y; + + memcpy(pack_line, (const void *)(buffer + (uint64_t)slot * p->slot_bytes + (uint64_t)row * w * 2), w * 2); + for (uint32_t x = 0, o = 0; x < w; x += 4, o += 5) { uint32_t v[4]; for (uint32_t k = 0; k < 4; k++) { - uint32_t p = FRAME_W - 1 - (x + k); + uint32_t q = p->turn ? w - 1 - (x + k) : x + k; - v[k] = ((uint32_t)pack_line[2 * p] | ((uint32_t)pack_line[2 * p + 1] << 8)) & 0x3ff; + v[k] = ((uint32_t)pack_line[2 * q] | ((uint32_t)pack_line[2 * q + 1] << 8)) & 0x3ff; packed[o + k] = (uint8_t)(v[k] >> 2); } packed[o + 4] = (uint8_t)((v[0] & 3) | ((v[1] & 3) << 2) | ((v[2] & 3) << 4) | ((v[3] & 3) << 6)); } - error = uiomove((caddr_t)packed, (int)MIN((user_ssize_t)RAW10_LINE, uio_resid(uio)), uio); + error = uiomove((caddr_t)packed, (int)MIN((user_ssize_t)(w * 5 / 4), uio_resid(uio)), uio); } lck_mtx_unlock(&s9read_lock); lck_mtx_lock(&s9cam_lock); @@ -2696,11 +3869,31 @@ s9dev_write(__unused dev_t dev, struct uio *uio, __unused int ioflag) manual_pending = manual; break; case HAKIOS_CAMERA_SENSOR: - if (c.a <= 3) { + /* + * the front camera (2), the rear one (1) where it is (cam_s9_rear=1), the telephoto (4) where it is (cam_s9_tele=1), + * each only if it has not failed this run + */ + if (c.a == 2 || (c.a == 1 && cam_rear && !rear_broken) || (c.a == TELE_SENSOR && cam_tele && !tele_broken)) { + if (c.a != asked_sensor) { + say("exynos_camera: 7: the reader asks for %s\n", paths[c.a == 1 ? REAR : c.a == TELE_SENSOR ? TELE : + FRONT].name); + } asked_sensor = c.a; + } else { + static uint32_t refused; + + if (refused++ < 4) { + say("exynos_camera: 7: sensor %u asked for: not in this run (%s); the frames of sensor %u go on\n", c.a, + c.a == 1 ? (rear_broken ? "the rear camera failed earlier" : "cam_s9_rear=0") : + c.a == TELE_SENSOR ? (tele_broken ? "the telephoto failed earlier" : "cam_s9_tele=0") : + "no such camera", asked_sensor); + } } break; case HAKIOS_CAMERA_FOCUS: + /* the lens to c.a (0-1023): written by the stream's thread at the next frame (af_move) */ + af_pending = c.a > 1023 ? 1023 : c.a; + break; case HAKIOS_CAMERA_KEEP: break; default: @@ -2740,6 +3933,38 @@ stream_wanted(void) (cam_open && mach_absolute_time() - __atomic_load_n(&last_read, __ATOMIC_ACQUIRE) < idle))); } +/* + * What a raw frame of the ring holds, for the log: the middle 64 x 64 pixels as the WDMA wrote them (16 bits a pixel), + * their 10-bit values' least, most and mean, how many words have bits above [9:0] (a frame MSB-aligned or packed + * otherwise would), the eight words at the start of the middle line, and the mean of each of the 2x2 cell's places + */ +static void +raw_look(int slot, uint64_t frame_no) +{ + vm_offset_t raw = buffer + (uint64_t)slot * SLOT_BYTES; + uint32_t lo = 0xffff, hi = 0, high_bits = 0, y0 = FRAME_H / 2 - 32, x0 = FRAME_W / 2 - 32; + uint64_t sum = 0, place[4] = { 0, 0, 0, 0 }; + const volatile uint16_t *mid = (const volatile uint16_t *)(raw + (uint64_t)(FRAME_H / 2) * LINE_BYTES); + + for (uint32_t y = y0; y < y0 + 64; y++) { + const volatile uint16_t *row = (const volatile uint16_t *)(raw + (uint64_t)y * LINE_BYTES); + + for (uint32_t x = x0; x < x0 + 64; x++) { + uint16_t w = row[x], v = w & 0x3ff; + + high_bits += (w & 0xfc00) != 0; + lo = v < lo ? v : lo; + hi = v > hi ? v : hi; + sum += v; + place[(y & 1) * 2 + (x & 1)] += v; + } + } + say("exynos_camera: 7: %s frame %llu, raw middle 64x64: 10-bit %u..%u, mean %llu (black %u); %u words with bits above " + "[9:0]; cell means %llu %llu / %llu %llu; middle line: %04x %04x %04x %04x %04x %04x %04x %04x\n", cur->name, frame_no, + lo, hi, sum / 4096, BLACK_LEVEL, high_bits, place[0] / 1024, place[1] / 1024, place[2] / 1024, place[3] / 1024, + mid[0], mid[1], mid[2], mid[3], mid[4], mid[5], mid[6], mid[7]); +} + /* one stream: the camera up, the frames coming until nobody wants them, everything down again; false if it failed */ static bool stream_session(void) @@ -2752,8 +3977,22 @@ stream_session(void) bool ok = false; int slot; - say("exynos_camera: 7: THE STREAM: the front camera up, 1632 x 1224 RAW10 at 30 frames a second into the ring " - "(%u slots of 4 MB at %#llx), the auto-exposure %s\n", RING_SLOTS, MEM_PA, cam_ae ? "on" : "off"); + /* the path the reader asked for (no reader holds a slot between sessions: stream_newest_slot is -1) */ + lck_mtx_lock(&s9cam_lock); + cur = asked_sensor == 1 && cam_rear && !rear_broken ? &paths[REAR] : + asked_sensor == TELE_SENSOR && cam_tele && !tele_broken ? &paths[TELE] : &paths[FRONT]; + lck_mtx_unlock(&s9cam_lock); + if ((uint64_t)RING_SLOTS * SLOT_BYTES + SLOT_BYTES > MEM_BYTES) { + say("exynos_camera: 7: %s: its ring (%u x %u bytes) and still do not fit the camera's %u bytes\n", cur->name, + RING_SLOTS, SLOT_BYTES, MEM_BYTES); + return false; + } + say("exynos_camera: 7: THE STREAM: %s up, %u x %u RAW10 at 30 frames a second into the ring (%u slots of %u MB at " + "%#llx), the auto-exposure %s\n", cur->name, FRAME_W, FRAME_H, RING_SLOTS, SLOT_BYTES >> 20, MEM_PA, + cam_ae ? "on" : "off"); + ae_lines = 0; + ae_again = 0x20; + ae_dgain = 0x100; dma_errors = 0; if (!sensor_up() || !isp_up() || mmu_translating()) { goto down; @@ -2762,6 +4001,7 @@ stream_session(void) if (!buffer_get() || !mode_load() || !stream_start()) { goto down; } + af_up(); frm_base = rd(csis, CSIS_FRM_CNT_CH0); start = log_at = mach_absolute_time(); nanoseconds_to_absolutetime(300 * NSEC_PER_MSEC, &wait); @@ -2771,11 +4011,17 @@ stream_session(void) stream_running = true; lck_mtx_unlock(&s9cam_lock); while (!shots_closed && stream_wanted()) { + if (__atomic_load_n(&asked_sensor, __ATOMIC_ACQUIRE) != cur->sensor) { + say("exynos_camera: 7: the reader asks for the other camera: this stream ends, the next is that one's\n"); + break; + } until = mach_absolute_time() + wait; slot = frame_wait(until, 1000); if (slot < 0) { - say("exynos_camera: 7: no frame end in 300 ms (CSIS1 INT_SRC0 %#x, FRM_CNT_CH0 %u; WDMA ACT_CTRL %#x)\n", - rd(csis, CSIS_INT_SRC0), rd(csis, CSIS_FRM_CNT_CH0), rd(wdma, DMA_ACT_CTRL)); + say("exynos_camera: 7: no frame end in 300 ms (CSIS INT_SRC0 %#x, SRC1 %#x, FRM_CNT_CH0 %u, PHY_STATUS %#x; WDMA " + "ACT_CTRL %#x, its common INT_SRC %#x, ERR_CODE %#x)\n", rd(csis, CSIS_INT_SRC0), rd(csis, CSIS_INT_SRC1), + rd(csis, CSIS_FRM_CNT_CH0), rd(csis, CSIS_PHY_STATUS), rd(wdma, DMA_ACT_CTRL), + rd(wdma, VCMN + VCMN_INT_SRC), rd(wdma, VCMN + VCMN_ERR_CODE)); if (++timeouts >= 5) { break; } @@ -2787,6 +4033,9 @@ stream_session(void) lost += frm_now - frm_last - 1; frm_last = frm_now; seen++; + if (seen == 2 || seen == 30 || seen % 300 == 0) { + raw_look(slot, frm_now); + } lck_mtx_lock(&s9cam_lock); slot_info[slot].sequence = numbered + frm_now; slot_info[slot].time = mach_absolute_time(); @@ -2807,6 +4056,14 @@ stream_session(void) lck_mtx_unlock(&s9cam_lock); } wakeup(&stream_newest); + if (af_ok && af_pending != ~0u) { + uint32_t to = af_pending; + + af_pending = ~0u; + if (af_move(to)) { + af_at = to; + } + } if (manual && mach_absolute_time() > manual_until) { manual = false; /* the reader that asked has gone quiet: the kernel's own exposure again */ say("exynos_camera: 7: no exposure asked for two seconds: the auto-exposure again\n"); @@ -2815,6 +4072,12 @@ stream_session(void) meter(buffer + (uint64_t)slot * SLOT_BYTES, &m); next_meter = frm_now + (ae_step(&m, true) ? 6 : 3); } + if (__atomic_load_n(&shot_asked, __ATOMIC_ACQUIRE) && cur != &paths[FRONT]) { + /* (camshot's picture is made at the front camera's size, and the rear's ring leaves no room for it) */ + shot_failed("camshot takes the front camera only; the stream is the rear camera's"); + __atomic_store_n(&shot_asked, 0, __ATOMIC_RELEASE); + __atomic_store_n(&shot_ready, 1, __ATOMIC_RELEASE); + } if (__atomic_load_n(&shot_asked, __ATOMIC_ACQUIRE) && (seen >= cam_ae_frames || !cam_ae)) { /* camshot: the newest frame out of the ring (the auto-exposure has had its frames), then the picture */ memcpy((void *)(buffer + STILL_OFF), (const void *)(buffer + (uint64_t)slot * SLOT_BYTES), SLOT_BYTES); @@ -2833,8 +4096,9 @@ stream_session(void) say("exynos_camera: 7: the stream: frame %llu after %llu ms, %llu.%02llu frames a second over the last ten " "seconds; %llu seen, %llu lost (the thread busy), %u with the WDMA's FIFO error; exposure %u lines, gain " - "%#x, digital %#x%s; green %u (aim %u)\n", frm_now, ms, frames / 10, frames * 10 % 100, seen, lost, - dma_errors, ae_lines, ae_again, ae_dgain, manual ? " (asked)" : "", m.mean[1], AE_AIM); + "%#x, digital %#x%s; green %u (aim %u); CSIS INT_SRC0 %#x SRC1 %#x\n", frm_now, ms, frames / 10, + frames * 10 % 100, seen, lost, dma_errors, ae_lines, ae_again, ae_dgain, manual ? " (asked)" : "", + m.mean[1], AE_AIM, rd(csis, CSIS_INT_SRC0), rd(csis, CSIS_INT_SRC1)); log_frames = frm_now; log_at += ten; } @@ -2846,6 +4110,7 @@ stream_session(void) say("exynos_camera: 7: the stream ends (%s): %llu frames, %llu seen, %llu lost, %u FIFO errors\n", ok ? "nobody reads" : "no frames", frm_last, seen, lost, dma_errors); down: + af_down(); all_down(); lck_mtx_lock(&s9cam_lock); stream_running = false; @@ -2880,6 +4145,32 @@ stream_loop(void) if (stream_session()) { continue; } + if (cur == &paths[REAR]) { + /* the rear camera failed: not again this run; the front camera stays usable (the readers are not failed) */ + lck_mtx_lock(&s9cam_lock); + rear_broken = true; + asked_sensor = 2; + lck_mtx_unlock(&s9cam_lock); + say("exynos_camera: 7: THE REAR CAMERA'S STREAM FAILED (the lines above): the front camera's frames only for " + "the rest of this run\n"); + cur = &paths[FRONT]; + continue; + } + if (cur == &paths[TELE]) { + /* + * the telephoto failed: not again this run; the wide camera's frames in its place where there are some (user + * space sees kern.hakios.camera_tele = 0 and asks for them itself), else the front's + */ + lck_mtx_lock(&s9cam_lock); + tele_broken = true; + tele_state = 0; + asked_sensor = cam_rear && !rear_broken ? 1 : 2; + lck_mtx_unlock(&s9cam_lock); + say("exynos_camera: 7: THE TELEPHOTO'S STREAM FAILED (the lines above): not tried again this run " + "(kern.hakios.camera_tele = 0); the %s camera's frames instead\n", asked_sensor == 1 ? "wide" : "front"); + cur = &paths[FRONT]; + continue; + } lck_mtx_lock(&s9cam_lock); stream_failed = true; lck_mtx_unlock(&s9cam_lock); @@ -2923,6 +4214,52 @@ shot_loop(void) } } +/* + * cam_s9_rear_probe=2: the rear camera powered up at the start exactly as its stream does it (rear_up()), its model ID + * and revision read, and powered down again (rear_down()): the log says what answers before anybody opens the camera + */ +static void +rear_boot_probe(void) +{ + if (rear_probe < 2) { + return; + } + say("exynos_camera: rear: the start's probe: powered up as the stream would, the ID read, powered down\n"); + cur = &paths[REAR]; + if (rear_up()) { + rear_down(); + } else { + say("exynos_camera: rear: THE START'S PROBE FAILED (the lines above)\n"); + } + cur = &paths[FRONT]; +} + +/* + * cam_s9_tele_probe=1: the telephoto powered up at the start exactly as its stream does it (tele_up()), its model ID and + * revision read, and powered down again (tele_down()). One that does not answer is not offered for the run + */ +static void +tele_boot_probe(void) +{ + if (!tele_probe || tele_broken) { + return; + } + say("exynos_camera: tele: the start's probe: powered up as the stream would, the ID read, powered down\n"); + cur = &paths[TELE]; + if (tele_up()) { + tele_down(); + if (cam_tele) { + tele_state = 2; + } + } else { + tele_broken = true; + tele_state = 0; + say("exynos_camera: tele: THE START'S PROBE FAILED (the lines above): the telephoto is not offered this run " + "(kern.hakios.camera_tele = 0)\n"); + } + cur = &paths[FRONT]; +} + static void camera_thread(void *arg, __unused wait_result_t result) { @@ -2936,6 +4273,8 @@ camera_thread(void *arg, __unused wait_result_t result) if (stream) { look_pins("1"); if (look_supplies()) { + rear_boot_probe(); + tele_boot_probe(); s9dev_make(); __atomic_store_n(&shot_ready, 1, __ATOMIC_RELEASE); say("exynos_camera: 7: the camera is powered when /dev/hakios-camera is read or a camshot is asked for%s\n", @@ -2952,6 +4291,8 @@ camera_thread(void *arg, __unused wait_result_t result) if (!look_supplies()) { goto out; } + rear_boot_probe(); + tele_boot_probe(); if (steps < 2) { goto out; } @@ -2993,6 +4334,59 @@ hakios_exynos_camera_init(void) uint32_t steps = 0, ipclk = 400; thread_t thread; + paths_init(); + PE_parse_boot_argn("cam_s9_rear_probe", &rear_probe, sizeof(rear_probe)); + PE_parse_boot_argn("cam_s9_rear", &cam_rear, sizeof(cam_rear)); + if ((cam_rear || rear_probe) && strcmp(HAKIOS_DEVICE, "star2lte") != 0) { + say("exynos_camera: cam_s9_rear / cam_s9_rear_probe are Star2's (S5K2L3 on CSIS0); this kernel is %s's: left " + "off\n", HAKIOS_DEVICE); + cam_rear = rear_probe = 0; + } + if (rear_probe || cam_rear) { + say("exynos_camera: Star2 rear camera: S5K2L3 at 0x10 on HSI2C0 (0x10860000), S2MPB03 rails, MCLK0, gpg2-6 reset, " + "CSIS0; probe level %u, stream %s (telephoto, OIS and AF untouched)\n", rear_probe, cam_rear ? "on" : "off"); + } + PE_parse_boot_argn("cam_s9_tele_probe", &tele_probe, sizeof(tele_probe)); + PE_parse_boot_argn("cam_s9_tele", &cam_tele, sizeof(cam_tele)); + if ((cam_tele || tele_probe) && strcmp(HAKIOS_DEVICE, "star2lte") != 0) { + say("exynos_camera: cam_s9_tele / cam_s9_tele_probe are Star2's (S5K3M3 on CSIS2); this kernel is %s's: left " + "off\n", HAKIOS_DEVICE); + cam_tele = tele_probe = 0; + } + if (tele_probe || cam_tele) { + say("exynos_camera: Star2 telephoto: S5K3M3 at 0x2D on I2C_CAM2 (0x10880000), S2MPB02 LDO3/7/11/12 and BUCK2, " + "MCLK0, gpg2-7 reset, CSIS2 (D-PHY isolation 0x710, reset bit 1), lens motor 0x0F on I2C_CAM3; probe %s, " + "stream %s (its OIS untouched)\n", tele_probe ? "on" : "off", cam_tele ? "on" : "off"); + } + PE_parse_boot_argn("cam_s9_tele_exp", &paths[TELE].exp, sizeof(paths[TELE].exp)); + PE_parse_boot_argn("cam_s9_tele_tp", &tele_tp, sizeof(tele_tp)); + { + uint32_t b = 1, t = 0; + + if (PE_parse_boot_argn("cam_s9_tele_bayer", &b, sizeof(b))) { + paths[TELE].bayer = b & 3; + } + if (PE_parse_boot_argn("cam_s9_tele_turn", &t, sizeof(t))) { + paths[TELE].turn = t != 0; + } + } + PE_parse_boot_argn("cam_s9_rear_ispdiv", &rear_ispdiv, sizeof(rear_ispdiv)); + if (rear_ispdiv > 15) { + rear_ispdiv = 0; + } + PE_parse_boot_argn("cam_s9_rear_exp", &paths[REAR].exp, sizeof(paths[REAR].exp)); + PE_parse_boot_argn("cam_s9_rear_tp", &rear_tp, sizeof(rear_tp)); + PE_parse_boot_argn("cam_s9_af", &cam_af, sizeof(cam_af)); + { + uint32_t b = 1, t = 0; + + if (PE_parse_boot_argn("cam_s9_rear_bayer", &b, sizeof(b))) { + paths[REAR].bayer = b & 3; + } + if (PE_parse_boot_argn("cam_s9_rear_turn", &t, sizeof(t))) { + paths[REAR].turn = t != 0; + } + } if (!PE_parse_boot_argn("cam_s9", &steps, sizeof(steps)) || steps == 0) { return; } @@ -3011,6 +4405,7 @@ hakios_exynos_camera_init(void) } PE_parse_boot_argn("cam_s9_pack12", &cam_pack12, sizeof(cam_pack12)); PE_parse_boot_argn("cam_s9_bayer", &cam_bayer, sizeof(cam_bayer)); + paths[FRONT].bayer = cam_bayer & 3; PE_parse_boot_argn("cam_s9_stride", &cam_stride, sizeof(cam_stride)); PE_parse_boot_argn("cam_s9_ispdiv", &cam_ispdiv, sizeof(cam_ispdiv)); if (cam_ispdiv > 15) { @@ -3044,6 +4439,11 @@ hakios_exynos_camera_init(void) steps = 7; } shots_enabled = steps >= 6; + /* (the telephoto is offered to user space only by a run that streams: cam_s9=7) */ + tele_state = cam_tele && steps >= 7 ? 1 : 0; + if (tele_state == 0) { + cam_tele = 0; + } if (kernel_thread_start(camera_thread, (void *)(uintptr_t)steps, &thread) == KERN_SUCCESS) { thread_deallocate(thread); } diff --git a/drivers/camera/exynos/hakios.s2mpb02_torch.c b/drivers/camera/exynos/hakios.s2mpb02_torch.c index fee85a057..2d6e55cfd 100644 --- a/drivers/camera/exynos/hakios.s2mpb02_torch.c +++ b/drivers/camera/exynos/hakios.s2mpb02_torch.c @@ -26,12 +26,19 @@ * torch=3 ... the light for iOS until the run ends: /dev/hakios-torch takes a level of 0 to 1000 (a 32-bit number, * 0 off) and a read gives what is lit; on for torch_max_s at a time at most (default 30, never more than * 120), off and staying off while the battery is above 45 C + * torch_flash_ms=N after setup, test the rear camera's flash output for N ms (1-500; Star2 defaults to 120). The + * flash current is Apollo's device-tree code 12 (1.25 A), its hardware timer code 7, and both pins are + * forced low again before the normal iOS torch service starts. + * torch_code_max=N the torch current code iOS's full level maps to (1-9, default 3: the S9's 60 mA). Star2's device + * tree gives Android's flashlight levels codes 2-9 (torch-sec1's torch_table); 9 is never exceeded. + * The chip's controller is the camera driver's too: every message goes under hakios_s2mpb02_bus_lock(). * * Sources: the phone's device tree (hsi2c@10920000, s2mpb02@59 and its flash node: torch1-gpio, flash1-gpio, the * currents and timers), Samsung's leds-s2mpb02.c and s2mpb02-private.h of the S9's kernel, Linux's pinctrl-exynos.c * (GPL: read for these facts, not copied). */ #include +#include #include #include #include @@ -54,7 +61,10 @@ #define FLED_CUR1 0x14 #define FLED_TIME1 0x15 #define FLED_CTRL2_ANDROID 0x38 /* what Android leaves in FLED_CTRL2 on the S9 (the header) */ -#define TORCH_CODE_MAX 3 /* 60 mA: Android's flashlight, never more */ +#define TORCH_CODE_MAX 3 /* 60 mA: Android's flashlight on the S9 */ +#define TORCH_CODE_CAP 9 /* Star2's DT torch_table tops out at 9: torch_code_max never above it */ +#define FLASH_CODE 12 /* Apollo star2: brightness 0xc, 1.25 A */ +#define FLASH_TIME_CODE 7 /* Apollo star2: timeout 0x7 */ #define HOT_C 45 #define GPIO_PERIC0_PA EXYNOS_GPIO_PERIC0_PA @@ -80,6 +90,7 @@ static vm_offset_t peric0, peric1; static uint32_t lit; /* the level lit now, 0 to 1000 */ static uint64_t lit_since; static uint32_t max_secs = 30; +static uint32_t torch_code_max = TORCH_CODE_MAX; /* torch_code_max: the brightest torch code iOS's 1000 maps to (1-9) */ static bool broken; /* a write did not read back, or the bus failed: no more writing */ static lck_mtx_t *torch_lock; @@ -95,22 +106,59 @@ wr(vm_offset_t base, uint32_t off, uint32_t v) *(volatile uint32_t *)(base + off) = v; } +/* + * The S2MPB02's controller (hsi2c@10920000) is the camera driver's too (hakios.exynos_camera.c: the cameras' LDOs): + * one transfer at a time, whichever driver makes it. Taken by both around every message to the chip. + */ +static LCK_GRP_DECLARE(s2mpb02_bus_grp, "hakios-s2mpb02-bus"); +static LCK_MTX_DECLARE(s2mpb02_bus_mtx, &s2mpb02_bus_grp); + +void hakios_s2mpb02_bus_lock(void); +void hakios_s2mpb02_bus_unlock(void); + +void +hakios_s2mpb02_bus_lock(void) +{ + lck_mtx_lock(&s2mpb02_bus_mtx); +} + +void +hakios_s2mpb02_bus_unlock(void) +{ + lck_mtx_unlock(&s2mpb02_bus_mtx); +} + static uint32_t -get(uint8_t reg, uint8_t *value) +get_locked(uint8_t reg, uint8_t *value) { return hakios_hsi2c_read_reg(&bus, CHIP, reg, value, 1); } +static uint32_t +get(uint8_t reg, uint8_t *value) +{ + uint32_t error; + + hakios_s2mpb02_bus_lock(); + error = get_locked(reg, value); + hakios_s2mpb02_bus_unlock(); + return error; +} + /* the flash block's registers only, every write read back */ static bool put(uint8_t reg, uint8_t value) { uint8_t buf[2] = { reg, value }, back = 0; + bool failed; if (broken || reg < FLED_CTRL1 || reg > FLED_TIME1) { return false; } - if (hakios_hsi2c_message(&bus, CHIP, false, buf, 2, true) != 0 || get(reg, &back) != 0 || back != value) { + hakios_s2mpb02_bus_lock(); + failed = hakios_hsi2c_message(&bus, CHIP, false, buf, 2, true) != 0 || get_locked(reg, &back) != 0 || back != value; + hakios_s2mpb02_bus_unlock(); + if (failed) { say("s2mpb02: register %#x: wrote %#x, read back %#x (bus %#x): no more writing for this run\n", reg, value, back, bus.last_error); broken = true; @@ -148,7 +196,7 @@ dark(void) static bool light(uint32_t permille) { - uint32_t code = (TORCH_CODE_MAX * permille + 999) / 1000; + uint32_t code = (torch_code_max * permille + 999) / 1000; if (permille == 0) { dark(); @@ -157,8 +205,8 @@ light(uint32_t permille) if (code < 1) { code = 1; } - if (code > TORCH_CODE_MAX) { - code = TORCH_CODE_MAX; + if (code > torch_code_max) { + code = torch_code_max; } if (!put_bits(FLED_CUR1, 0x0f, (uint8_t)code)) { dark(); @@ -173,6 +221,23 @@ light(uint32_t permille) return true; } +static bool +flash_test(uint32_t ms) +{ + if (ms == 0) return true; + if (ms > 500) ms = 500; + if (!put_bits(FLED_CUR1, 0xf0, FLASH_CODE << 4) || !put_bits(FLED_TIME1, 0xf0, FLASH_TIME_CODE << 4)) { + dark(); + return false; + } + pin(peric0 + GPG2, TORCH_PIN, false); + pin(peric1 + GPG3, FLASH_PIN, true); + say("s2mpb02: camera flash test: %u ms at Apollo code %u (1.25 A)\n", ms, FLASH_CODE); + IOSleep(ms); + dark(); + return true; +} + /* * On the way back to the loader (loader_return(), a panic's way out too: one processor left, no lock taken): the light * off, if it is lit. dark() is the two pins only, no I2C; the chip keeps its registers, the pins are what light it. @@ -251,7 +316,7 @@ static const struct cdevsw torch_cdevsw = { static void torch_thread(void *arg, __unused wait_result_t result) { - uint32_t steps = (uint32_t)(uintptr_t)arg, secs = 2; + uint32_t steps = (uint32_t)(uintptr_t)arg, secs = 2, flash_ms = 0; uint8_t id = 0, regs[4] = { 0 }; say("s2mpb02: 1: map the bus (USI10 of PERIC1) and the two pins' GPIO blocks\n"); @@ -263,8 +328,10 @@ torch_thread(void *arg, __unused wait_result_t result) say("s2mpb02: 1: torch1 (gpg2-4): CON %x DAT %x; flash1 (gpg3-5): CON %x DAT %x\n", (rd(peric0 + GPG2, 0) >> (4 * TORCH_PIN)) & 0xf, (rd(peric0 + GPG2, 4) >> TORCH_PIN) & 1, (rd(peric1 + GPG3, 0) >> (4 * FLASH_PIN)) & 0xf, (rd(peric1 + GPG3, 4) >> FLASH_PIN) & 1); + hakios_s2mpb02_bus_lock(); hakios_hsi2c_power(&bus); hakios_hsi2c_reset(&bus); + hakios_s2mpb02_bus_unlock(); if (get(REG_ID, &id) != 0) { say("s2mpb02: 1: no answer at 0x%x (%x)\n", CHIP, bus.last_error); goto out; @@ -280,6 +347,10 @@ torch_thread(void *arg, __unused wait_result_t result) say("s2mpb02: 2: the flash block could not be set up\n"); goto out; } + PE_parse_boot_argn("torch_flash_ms", &flash_ms, sizeof(flash_ms)); + if (flash_ms && (too_hot() || !flash_test(flash_ms))) { + say("s2mpb02: camera flash test refused or failed\n"); + } if (steps == 2) { PE_parse_boot_argn("torch_secs", &secs, sizeof(secs)); secs = secs > 10 ? 10 : secs; @@ -291,6 +362,8 @@ torch_thread(void *arg, __unused wait_result_t result) goto out; } PE_parse_boot_argn("torch_max_s", &max_secs, sizeof(max_secs)); + PE_parse_boot_argn("torch_code_max", &torch_code_max, sizeof(torch_code_max)); + torch_code_max = torch_code_max == 0 ? TORCH_CODE_MAX : torch_code_max > TORCH_CODE_CAP ? TORCH_CODE_CAP : torch_code_max; max_secs = max_secs == 0 ? 30 : max_secs > 120 ? 120 : max_secs; { int major = cdevsw_add(-1, &torch_cdevsw); @@ -327,6 +400,7 @@ hakios_s2mpb02_torch_init(void) { uint32_t steps = 0, camera = 0; thread_t thread; + kern_return_t result; if (!PE_parse_boot_argn("torch", &steps, sizeof(steps)) || steps == 0) { return; @@ -335,11 +409,13 @@ hakios_s2mpb02_torch_init(void) return; /* not the Exynos 9810's (the POCO's torch=N is its own driver's) */ } if (PE_parse_boot_argn("cam_s9", &camera, sizeof(camera)) && camera != 0) { - say("s2mpb02: not with cam_s9=%u: the camera's driver (hakios.exynos_camera.c) has this bus in this run\n", camera); - return; + say("s2mpb02: cam_s9=%u shares this controller; camera discovery was started first\n", camera); } torch_lock = lck_mtx_alloc_init(lck_grp_alloc_init("s2mpb02", LCK_GRP_ATTR_NULL), LCK_ATTR_NULL); - if (kernel_thread_start(torch_thread, (void *)(uintptr_t)steps, &thread) == KERN_SUCCESS) { + result = kernel_thread_start(torch_thread, (void *)(uintptr_t)steps, &thread); + if (result == KERN_SUCCESS) { thread_deallocate(thread); + } else { + say("s2mpb02: torch worker could not start (%#x)\n", result); } } diff --git a/drivers/core/hakios.stubs.c b/drivers/core/hakios.stubs.c index 7cb5bce34..16069871b 100644 --- a/drivers/core/hakios.stubs.c +++ b/drivers/core/hakios.stubs.c @@ -149,6 +149,11 @@ const char *hakios_exynos_iris_shot(__unused uint32_t n) { return "this kernel h #ifndef HAKIOS_S2MPB02_TORCH void hakios_s2mpb02_torch_init(void) {} void hakios_s2mpb02_torch_quiesce(void) {} +/* the S2MPB02's bus lock (hakios.exynos_camera.c takes it): no flashlight, nobody else on the bus */ +void hakios_s2mpb02_bus_lock(void); +void hakios_s2mpb02_bus_lock(void) {} +void hakios_s2mpb02_bus_unlock(void); +void hakios_s2mpb02_bus_unlock(void) {} #endif #ifndef HAKIOS_BCM4361_BT void hakios_bcm4361_bt_init(void) {} diff --git a/drivers/input/hakios.input.c b/drivers/input/hakios.input.c index 4be00f211..32cee423f 100644 --- a/drivers/input/hakios.input.c +++ b/drivers/input/hakios.input.c @@ -311,13 +311,14 @@ hakios_input_init(void) hakios_exynos_keys_init(); hakios_s6sy761_init(); hakios_s6e3ha8_init(); - hakios_s2mpb02_torch_init(); hakios_exynos_abox_init(); hakios_exynos_mfc_init(); hakios_battery_init(); hakios_scfb_battery_init(); hakios_max77705_init(); hakios_exynos_camera_init(); + /* The camera inspects the shared S2MPB02 first; start its flashlight service afterwards. */ + hakios_s2mpb02_torch_init(); hakios_exynos_iris_init(); hakios_exynos_tee_init(); hakios_ufs_init(); diff --git a/tools/extract-2l3-setfile.py b/tools/extract-2l3-setfile.py new file mode 100644 index 000000000..5f109aa94 --- /dev/null +++ b/tools/extract-2l3-setfile.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Make drivers/camera/exynos/hakios.s5k2l3_setfiles.h from Apollo's S5K2L3 setfiles A and B. + + C=APOLLO/drivers/media/platform/exynos/fimc-is2/sensor/module_framework/cis + tools/extract-2l3-setfile.py $C/fimc-is-cis-2l3-setA.h $C/fimc-is-cis-2l3-setB.h \ + > drivers/camera/exynos/hakios.s5k2l3_setfiles.h + +The Galaxy S9+'s primary rear camera (Star2, S5K2L3 at 0x10 on the camera's HSI2C0) is set up by three of a +setfile's tables, in the order sensor_2l3_cis_set_global_setting_internal() and sensor_2l3_cis_mode_change() write +them: Reset_TnP (the sensor's firmware patch, mostly burst writes to the indirect data port 0x6F12), Global, and one +mode's table. The mode taken is SENSOR_2L3_2016X1512_30FPS_MODE2 (index 14 of sensor_2l3_setfiles_A and _B): 2016 x +1512 RAW10, four lanes at 1352 Mb/s, no phase-detection virtual channel (PD_NONE), so the link carries VC0 alone. + +Which set a sensor takes is decided by its revision (register 0x0002), as sensor_2l3_cis_select_setfile() decides +it: 0xA2-0xA4 (EVT0.2) setfile A, 0xA5 (EVT0.3) setfile B, any other none. + +The tables are the sensor's own register values: data, read here and written out as data. Each entry of Apollo's is +(address, value, size) in u32s; I2C_MODE_BURST_ADDR/_DATA and I2C_MODE_DELAY are its markers. Here an entry is one of + S2L3_W16 address value a 16-bit write + S2L3_W8 address value an 8-bit write + S2L3_BURST port value one word of a burst to `port' (consecutive words of one burst are written in one + message, or several: the port increments its own pointer, so a burst split is the same) + S2L3_DELAY 0 microseconds a pause +""" +import re +import sys + +TABLES = [ + ("reset_tnp", "sensor_2l3_setfile_%s_Reset_TnP"), + ("global", "sensor_2l3_setfile_%s_Global"), + ("mode_2016x1512_30", "sensor_2l3_setfile_%s_2016x1512_30fps_mode2"), +] +PLL = "sensor_2l3_pllinfo_%s_2016x1512_30fps_mode2" +OPS = ("S2L3_W16", "S2L3_W8", "S2L3_BURST", "S2L3_DELAY") + + +def strip_comments(text): + text = re.sub(r"/\*.*?\*/", " ", text, flags=re.S) + return re.sub(r"//[^\n]*", " ", text) + + +def table(text, name): + m = re.search(r"const\s+u32\s+%s\s*\[\s*\]\s*=\s*\{(.*?)\};" % re.escape(name), text, re.S) + if not m: + sys.exit("extract-2l3-setfile: %s not found" % name) + words = [w.strip() for w in m.group(1).split(",") if w.strip()] + if len(words) % 3: + sys.exit("extract-2l3-setfile: %s has %d words, not a multiple of 3" % (name, len(words))) + out, burst_port = [], None + for i in range(0, len(words), 3): + a, v, s = words[i:i + 3] + if a == "I2C_MODE_BURST_ADDR": + burst_port = int(v, 0) + if int(s, 0) != 2: + sys.exit("extract-2l3-setfile: %s: a burst of %s-byte words" % (name, s)) + continue + if a == "I2C_MODE_BURST_DATA": + if burst_port is None: + sys.exit("extract-2l3-setfile: %s: burst data without a burst address" % name) + out.append(("S2L3_BURST", burst_port, int(v, 0))) + continue + burst_port = None + if a == "I2C_MODE_DELAY": + us = int(v, 0) + if us > 0xFFFF: + sys.exit("extract-2l3-setfile: %s: a delay of %d us" % (name, us)) + out.append(("S2L3_DELAY", 0, us)) + continue + addr, val, size = int(a, 0), int(v, 0), int(s, 0) + if size not in (1, 2) or addr > 0xFFFF or val > (0xFF if size == 1 else 0xFFFF): + sys.exit("extract-2l3-setfile: %s: entry %s %s %s" % (name, a, v, s)) + out.append(("S2L3_W8" if size == 1 else "S2L3_W16", addr, val)) + return out + + +def pll(text, name): + m = re.search(r"%s\s*=\s*\{(.*?)\};" % re.escape(name), text, re.S) + if not m: + sys.exit("extract-2l3-setfile: %s not found" % name) + words = [w.strip() for w in m.group(1).split(",") if w.strip()] + # ext_clk, mipi_datarate, pclk, frame_length_lines, line_length_pck + return int(words[1], 0), int(words[2], 0), int(words[3], 0), int(words[4], 0) + + +def main(): + if len(sys.argv) != 3: + sys.exit(__doc__) + sets = [("A", strip_comments(open(sys.argv[1], encoding="latin-1").read())), + ("B", strip_comments(open(sys.argv[2], encoding="latin-1").read()))] + print("/*") + print(" * Made by tools/extract-2l3-setfile.py from Apollo's fimc-is-cis-2l3-setA.h and -setB.h: the S5K2L3's own") + print(" * register values, as data. Do not edit; run the tool again.") + print(" */") + print("#ifndef HAKIOS_S5K2L3_SETFILES_H") + print("#define HAKIOS_S5K2L3_SETFILES_H") + print("") + print("enum { S2L3_W16, S2L3_W8, S2L3_BURST, S2L3_DELAY };") + print("struct s2l3_reg { uint16_t addr, value; uint8_t op; };") + timing = None + for letter, text in sets: + name = PLL % letter + t = pll(text, name) + if timing is not None and t != timing: + sys.exit("extract-2l3-setfile: %s differs from setfile A's timing" % name) + timing = t + for short, tname in TABLES: + tname = tname % letter + entries = table(text, tname) + counts = {k: sum(1 for e in entries if e[0] == k) for k in OPS} + print("") + print("/* %s: %d entries (%d 16-bit, %d 8-bit, %d burst words, %d pauses) */" % ( + tname, len(entries), counts["S2L3_W16"], counts["S2L3_W8"], counts["S2L3_BURST"], counts["S2L3_DELAY"])) + print("static const struct s2l3_reg s2l3_%s_%s[] = {" % (letter.lower(), short)) + line = [] + for op, a, v in entries: + line.append("{ %#06x, %#06x, %s }," % (a, v, op)) + if len(line) == 4: + print("\t" + " ".join(line)) + line = [] + if line: + print("\t" + " ".join(line)) + print("};") + rate, pclk, fll, llp = timing + print("") + print("/* the mode's timing, the same in both sets (sensor_2l3_pllinfo_[AB]_2016x1512_30fps_mode2) */") + print("#define S2L3_MODE_MIPI_MBPS %u" % (rate // 1000000)) + print("#define S2L3_MODE_PCLK %u" % pclk) + print("#define S2L3_MODE_LINES %#x" % fll) + print("#define S2L3_MODE_LINE_PCK %#x" % llp) + print("") + print("#endif /* HAKIOS_S5K2L3_SETFILES_H */") + + +main() diff --git a/tools/extract-3m3-setfile.py b/tools/extract-3m3-setfile.py new file mode 100755 index 000000000..6291ac446 --- /dev/null +++ b/tools/extract-3m3-setfile.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Make drivers/camera/exynos/hakios.s5k3m3_setfiles.h from Apollo's S5K3M3 setfile A. + + C=APOLLO/drivers/media/platform/exynos/fimc-is2/sensor/module_framework/cis + tools/extract-3m3-setfile.py $C/fimc-is-cis-3m3-setA.h > drivers/camera/exynos/hakios.s5k3m3_setfiles.h + +The Galaxy S9+'s telephoto camera (Star2, "rear2", S5K3M3 at 0x2D on the camera's HSI2C2, I2C_CAM2 0x10880000) is +set up by two of a setfile's tables, in the order sensor_3m3_cis_set_global_setting() and sensor_3m3_cis_mode_change() +write them: Global (a soft reset, the sensor's firmware patch as burst writes to the indirect data port 0x6F12, and +its analogue settings), then one mode's table. The mode taken is SENSOR_3M3_2016X1512_30FPS (index 4 of +sensor_3m3_setfiles_A; config_module_3m3's mode 4): 2016 x 1512 RAW10 (2x2 binned), four lanes at 1196 Mb/s, +CSI_MODE_VC_ONLY, PD_NONE (the phase-detection tail, 128 x 736, comes on VC1 and is not taken here). + +Which set: Apollo's star2 device tree gives the 3M3 `setfile = "setA"` (fimc-is-3m3@2D), and sensor_3m3_cis_probe() +takes setfile A for "setA" (and for "default"); fimc-is-cis-3m3-setB.h is wrapped in #if 0. Unlike the 2L3's, the +3M3's driver does not choose by revision: setfile A is the only one. + +The tables are the sensor's own register values: data, read here and written out as data. Each entry of Apollo's is +(address, value, size) in u32s; I2C_MODE_BURST_ADDR/_DATA and I2C_MODE_DELAY are its markers. An entry here has the +S5K2L3 header's layout (struct s2l3_reg and its S2L3_W16/W8/BURST/DELAY, hakios.s5k2l3_setfiles.h, which must be +included first), so that the one table writer of hakios.exynos_camera.c (rear_table) writes both sensors' tables. +""" +import re +import sys + +TABLES = [ + ("global", "sensor_3m3_setfile_A_Global"), + ("mode_2016x1512_30", "sensor_3m3_setfile_A_2016x1512_30fps"), +] +PLL = "sensor_3m3_pllinfo_A_2016x1512_30fps" +OPS = ("S2L3_W16", "S2L3_W8", "S2L3_BURST", "S2L3_DELAY") + + +def strip_comments(text): + text = re.sub(r"/\*.*?\*/", " ", text, flags=re.S) + return re.sub(r"//[^\n]*", " ", text) + + +def table(text, name): + m = re.search(r"const\s+u32\s+%s\s*\[\s*\]\s*=\s*\{(.*?)\};" % re.escape(name), text, re.S) + if not m: + sys.exit("extract-3m3-setfile: %s not found" % name) + words = [w.strip() for w in m.group(1).split(",") if w.strip()] + if len(words) % 3: + sys.exit("extract-3m3-setfile: %s has %d words, not a multiple of 3" % (name, len(words))) + out, burst_port = [], None + for i in range(0, len(words), 3): + a, v, s = words[i:i + 3] + if a == "I2C_MODE_BURST_ADDR": + burst_port = int(v, 0) + if int(s, 0) != 2: + sys.exit("extract-3m3-setfile: %s: a burst of %s-byte words" % (name, s)) + continue + if a == "I2C_MODE_BURST_DATA": + if burst_port is None: + sys.exit("extract-3m3-setfile: %s: burst data without a burst address" % name) + out.append(("S2L3_BURST", burst_port, int(v, 0))) + continue + burst_port = None + if a == "I2C_MODE_DELAY": + us = int(v, 0) + if us > 0xFFFF: + sys.exit("extract-3m3-setfile: %s: a delay of %d us" % (name, us)) + out.append(("S2L3_DELAY", 0, us)) + continue + addr, val, size = int(a, 0), int(v, 0), int(s, 0) + if size not in (1, 2) or addr > 0xFFFF or val > (0xFF if size == 1 else 0xFFFF): + sys.exit("extract-3m3-setfile: %s: entry %s %s %s" % (name, a, v, s)) + out.append(("S2L3_W8" if size == 1 else "S2L3_W16", addr, val)) + return out + + +def pll(text, name): + m = re.search(r"%s\s*=\s*\{(.*?)\};" % re.escape(name), text, re.S) + if not m: + sys.exit("extract-3m3-setfile: %s not found" % name) + words = [w.strip() for w in m.group(1).split(",") if w.strip()] + # ext_clk (EXT_CLK_Mhz * 1000 * 1000), mipi_datarate, pclk, frame_length_lines, line_length_pck + return int(words[1], 0), int(words[2], 0), int(words[3], 0), int(words[4], 0) + + +def main(): + if len(sys.argv) != 2: + sys.exit(__doc__) + text = strip_comments(open(sys.argv[1], encoding="latin-1").read()) + print("/*") + print(" * Made by tools/extract-3m3-setfile.py from Apollo's fimc-is-cis-3m3-setA.h: the S5K3M3's own register") + print(" * values, as data. Do not edit; run the tool again. Uses hakios.s5k2l3_setfiles.h's struct s2l3_reg.") + print(" */") + print("#ifndef HAKIOS_S5K3M3_SETFILES_H") + print("#define HAKIOS_S5K3M3_SETFILES_H") + print("") + print("#ifndef HAKIOS_S5K2L3_SETFILES_H") + print("#error \"include hakios.s5k2l3_setfiles.h first (struct s2l3_reg)\"") + print("#endif") + for short, tname in TABLES: + entries = table(text, tname) + counts = {k: sum(1 for e in entries if e[0] == k) for k in OPS} + print("") + print("/* %s: %d entries (%d 16-bit, %d 8-bit, %d burst words, %d pauses) */" % ( + tname, len(entries), counts["S2L3_W16"], counts["S2L3_W8"], counts["S2L3_BURST"], counts["S2L3_DELAY"])) + print("static const struct s2l3_reg s3m3_%s[] = {" % short) + line = [] + for op, a, v in entries: + line.append("{ %#06x, %#06x, %s }," % (a, v, op)) + if len(line) == 4: + print("\t" + " ".join(line)) + line = [] + if line: + print("\t" + " ".join(line)) + print("};") + rate, pclk, fll, llp = pll(text, PLL) + print("") + print("/* the mode's timing (%s) */" % PLL) + print("#define S3M3_MODE_MIPI_MBPS %u" % (rate // 1000000)) + print("#define S3M3_MODE_PCLK %u" % pclk) + print("#define S3M3_MODE_LINES %#x" % fll) + print("#define S3M3_MODE_LINE_PCK %#x" % llp) + print("#define S3M3_MODE_LINE_NS %u" % ((llp * 1000000000 + pclk // 2) // pclk)) + print("") + print("#endif /* HAKIOS_S5K3M3_SETFILES_H */") + + +main() diff --git a/tools/hakios-config.py b/tools/hakios-config.py old mode 100755 new mode 100644 index f299e5ff7..2f102c948 --- a/tools/hakios-config.py +++ b/tools/hakios-config.py @@ -67,6 +67,11 @@ class ConfigError(Exception): ("WIFI", "bool", "wifi", "Wi-Fi: the radio, its driver, a connection (also fw-wifi/mac and fw-wifi/network)", "fw-wifi", None), ("HAPTIC", "int", "haptic", "the vibration motor (5 is what the README's phone line uses)", "fw-haptic", None), ("CAM", "int", "cam", "the cameras (5 is what the README uses)", "fw-camera", None), + ("CAM_S9_REAR_PROBE", "int", "cam_s9_rear_probe", "Star2's primary rear camera (S5K2L3): 2 powers it up at boot as its stream would, reads its ID and revision, powers it down (telephoto untouched)", None, None), + ("CAM_S9_REAR", "bool", "cam_s9_rear", "Star2's primary rear camera (S5K2L3 on CSIS0) as the iOS back camera, 2016x1512 at 30; the front camera stays usable if it fails", None, "CAM_S9=7"), + ("CAM_S9_TELE_PROBE", "int", "cam_s9_tele_probe", "Star2's telephoto camera (S5K3M3): 1 powers it up at boot as its stream would, reads its ID and revision, powers it down; one that does not answer is not offered", None, None), + ("CAM_S9_TELE", "bool", "cam_s9_tele", "Star2's telephoto camera (S5K3M3 on CSIS2) as the iOS back telephoto, 2016x1512 at 30; hidden again if it fails", None, "CAM_S9=7"), + ("TORCH_FLASH_MS", "int", "torch_flash_ms", "test the Galaxy S9 rear camera flash at boot for this many milliseconds (1 to 500)", None, None), ("MODEM", "bool", "modem", "the modem beside the system: its firmware, file systems, IPA, SIM, NAS", "fw-modem", "UFS=2"), ("MODEM_AGAIN", "bool", "modem_again", "start the modem although a kernel before this one left it shut down", None, "MODEM"), ("MODEM_BUFFER", "hex", "modem_buffer", "the modem's RMTFS buffer, an address below 4 GB (0xa8000000 on the reference phone)", None, "MODEM"), From 2a481a4499e95ace7952fa2aac735f98c36e5d17 Mon Sep 17 00:00:00 2001 From: Anan Jaser Date: Thu, 12 Nov 2026 16:43:37 +0400 Subject: [PATCH 3/6] Camera: a NO_FOCUS write leaves the lens where it is (it clamped to 1023, the macro end) (cherry picked from commit 03c2ed23632613587e3dea94275318cd92baba9b; applied to hakios.exynos_camera_star2.c, the copy of the file this was written on, without its drivers/map and hakios.exynos_hsi2c.c hunks: the next commit says why) --- drivers/camera/exynos/hakios.exynos_camera_star2.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/camera/exynos/hakios.exynos_camera_star2.c b/drivers/camera/exynos/hakios.exynos_camera_star2.c index 8c9b79a96..7e773d183 100644 --- a/drivers/camera/exynos/hakios.exynos_camera_star2.c +++ b/drivers/camera/exynos/hakios.exynos_camera_star2.c @@ -3892,7 +3892,10 @@ s9dev_write(__unused dev_t dev, struct uio *uio, __unused int ioflag) break; case HAKIOS_CAMERA_FOCUS: /* the lens to c.a (0-1023): written by the stream's thread at the next frame (af_move) */ - af_pending = c.a > 1023 ? 1023 : c.a; + /* (HAKIOS_CAMERA_NO_FOCUS is no position: it would clamp to 1023, the macro end) */ + if (c.a != HAKIOS_CAMERA_NO_FOCUS) { + af_pending = c.a > 1023 ? 1023 : c.a; + } break; case HAKIOS_CAMERA_KEEP: break; From 9477c93c85ffcf005838659401159daf04218411 Mon Sep 17 00:00:00 2001 From: Anan Jaser Date: Thu, 12 Nov 2026 16:50:40 +0400 Subject: [PATCH 4/6] Camera: the AK737x's first position held 10 ms before active mode (vendor_first_delay, star2lte/starlte DT) (cherry picked from commit 6014ef873ab04685c3294fa0b7fd581587dd6c46; applied to hakios.exynos_camera_star2.c, the copy of the file this was written on, without its drivers/map and hakios.exynos_hsi2c.c hunks: the next commit says why) --- drivers/camera/exynos/hakios.exynos_camera_star2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/camera/exynos/hakios.exynos_camera_star2.c b/drivers/camera/exynos/hakios.exynos_camera_star2.c index 7e773d183..cadebb4f4 100644 --- a/drivers/camera/exynos/hakios.exynos_camera_star2.c +++ b/drivers/camera/exynos/hakios.exynos_camera_star2.c @@ -1890,7 +1890,7 @@ af_up(void) say("exynos_camera: af: %s: the first position not taken: no focus\n", cur->name); return; } - delay_for_interval(5, NSEC_PER_MSEC); + delay_for_interval(10, NSEC_PER_MSEC); /* vendor_first_delay 10000 us (star2lte/starlte DT, AK737x) */ if (!af_write(AK737X_MODE, AK737X_ACTIVE)) { say("exynos_camera: af: %s: active mode not taken: no focus\n", cur->name); return; From da11550042339a8e1e722832f233d6c3b1c673e3 Mon Sep 17 00:00:00 2001 From: Claudius Date: Thu, 12 Nov 2026 17:23:11 +0000 Subject: [PATCH 5/6] Camera: the S9+'s driver is built for star2lte only, the S9's for everything else; its two table headers are made at build time Anan Jaser's three camera commits (before this one) are his Galaxy S9+ driver, applied to a copy of the file they were written on (hakios.exynos_camera_star2.c). Main's own driver went another way on the same day and was run on a Galaxy S9. Making one of the two means running the result on both phones, and neither can be run now, so for the time being each phone keeps the code seen working on it (docs/exynos9810-cameras.md: what the two share, where they differ, what was not compared). - One of them in a kernel, never both: tools/hakios-config.py writes HAKIOS_DEVICE_IS_ into dev/hakios.drivers.h; the S9's three files compile to nothing for star2lte, the S9+'s file to nothing for everything else. Same configuration symbol (EXYNOS_CAMERA), same three entry points, so no device configuration changes. The ios-27 tree has no such header and takes the S9's files only: its kernel is unchanged. - Not taken from 74ad0778: its drivers/map lines (written here) and its hakios.exynos_hsi2c.c hunk (main already has the same change, made independently: a dedicated camera HSI2C has no USI selector). - Not committed: hakios.s5k2l3_setfiles.h and hakios.s5k3m3_setfiles.h. They hold the sensors' RAM patches, which is firmware, and firmware is not kept in this tree. tools/mk-star2-setfiles.sh makes them when a star2lte kernel is built: three headers of Samsung's GPL-2.0 kernel fetched from github.com/ananjaser1211/Apollo at a pinned commit, checked by SHA-256, run through his two extract tools (the result is byte for byte the files of his commit); xnu-build.sh lays them beside the driver in the kernel tree. NOTICE and THIRD_PARTY_LICENSES.md say so, and the latter now names the S9+ panel table. - tools/hakios-config.py and tools/extract-2l3-setfile.py are executable in git again (the patch had recorded 644). Built for every configuration; not run on a phone. --- NOTICE | 4 ++ THIRD_PARTY_LICENSES.md | 2 + docs/exynos9810-cameras.md | 55 +++++++++++++++++++ drivers/camera/conf | 3 + drivers/camera/exynos/hakios.ak737x.c | 12 ++++ drivers/camera/exynos/hakios.exynos_camera.c | 11 ++++ .../camera/exynos/hakios.exynos_camera_rear.c | 11 ++++ .../exynos/hakios.exynos_camera_star2.c | 11 ++++ drivers/map | 1 + tools/extract-2l3-setfile.py | 0 tools/hakios-config.py | 5 +- tools/mk-star2-setfiles.sh | 49 +++++++++++++++++ xnu-build.sh | 10 ++++ 13 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 docs/exynos9810-cameras.md mode change 100644 => 100755 tools/extract-2l3-setfile.py mode change 100644 => 100755 tools/hakios-config.py create mode 100755 tools/mk-star2-setfiles.sh diff --git a/NOTICE b/NOTICE index 38eae76e6..b173dbf67 100644 --- a/NOTICE +++ b/NOTICE @@ -118,3 +118,7 @@ Fetched at build time, never committed: change or, for new files, as their headers say. drivers/display/panel/hakios.s6e3ha8_star2.h is data (the Galaxy S9+ panel's dimming tables) made by tools/extract-star2-panel.py from the tables of Samsung's GPL-2.0 kernel as carried in his Apollo tree; no code of that kernel is in it. + drivers/camera/exynos/hakios.exynos_camera_star2.c (his Galaxy S9+ camera driver) includes two headers that are not in this + repository: tools/mk-star2-setfiles.sh makes them at build time, with his tools/extract-2l3-setfile.py and + tools/extract-3m3-setfile.py, from three headers of Samsung's GPL-2.0 kernel (the S5K2L3's and S5K3M3's register tables and + RAM patches) fetched from github.com/ananjaser1211/Apollo at a pinned commit. diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index 9bb3a1f4b..b02376257 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -86,6 +86,8 @@ fetch and build it yourself, on your machine. `libvulkan_freedreno.dylib` (Turni |---|---|---|---|---| | ShengUfs | `drivers/storage/ufs/hakios.ufshc.c/.h` | [UniXNU/ShengUfs](https://github.com/UniXNU/ShengUfs) at `f2d6a4a72f5e7be7d2c9b78eba16cf373868fa30`, Copyright (c) 2026, blu-sharky | BSD-2-Clause (files marked BSD-2-Clause-Patent) | compared with this driver item by item (`hakios.ufshc.h`); the behaviour adopted was written again here, no line of it is in this tree. Its notice, below, goes with it all the same | | Samsung's Exynos 9810 kernel (the Galaxy S9's) | the S9's UFS facts in `hakios.ufshc.c` | Samsung, Linux | GPL-2.0 | facts only (addresses, register offsets, descriptor layout, quirks), each named with its file there; no code | +| Samsung's S6E3HA8 panel tables for the Galaxy S9+ | `drivers/display/panel/hakios.s6e3ha8_star2.h`: the S9+ panel's dimming tables as data, made by `tools/extract-star2-panel.py` | Samsung, as carried in [ananjaser1211/Apollo](https://github.com/ananjaser1211/Apollo) | GPL-2.0 | data only (numbers of the panel), as the S9's tables in `hakios.s6e3ha8_gamma.h`; no code | +| Samsung's Exynos 9810 camera setfiles (`fimc-is-cis-2l3-setA.h`, `-setB.h`, `fimc-is-cis-3m3-setA.h`) | the Galaxy S9+'s rear sensors' register tables and RAM patches, as two headers made at build time for a `star2lte` kernel (`tools/mk-star2-setfiles.sh`; never committed) | Samsung, as carried in [ananjaser1211/Apollo](https://github.com/ananjaser1211/Apollo) at `30656faf0758` | GPL-2.0 | fetched at build time, data only; not in this repository | | Samsung's bcmdhd (the S9's Broadcom driver) | the BCM4361's chip and PCI IDs, RAM base and revisions in `wifi/lx/brcmfmac/patches/bcm4361.patch`; the firmware's file names in `devices/starlte/WIFI.md`; in the fork's `bsd/sys/hakios.bcm4361_cis.h` (both kernel lines) the OTP addresses of the module's identity and the table from its two identity bytes to the NVRAM file's name; in the fork's `bsd/sys/hakios.macaddr.h` the text form of the phone's address file | Samsung / Broadcom, the S9's kernel at `e5906667068e` (`drivers/net/wireless/broadcom/bcmdhd_101_12`) | GPL-2.0 | facts only, named in the patch's head and in each header's; no code | | galaxy-s9plus-mainline-linux | the same, cross-checked | [SheroAbi/galaxy-s9plus-mainline-linux](https://github.com/SheroAbi/galaxy-s9plus-mainline-linux) at `bcee6014fc2e44a4c4f1c34bca0a880d4b543ec3` (`src/ufs/ufs-exynos9810.c`, `src/mainline-dts/exynos9810-star2lte.dts`) | GPL-2.0 | facts only (the protector's descriptor type and PRDT entry size, 4 KB entries, OCS 7 for device errors, LUN 0's layout), named in `hakios.ufshc.c`; no code | diff --git a/docs/exynos9810-cameras.md b/docs/exynos9810-cameras.md new file mode 100644 index 000000000..3faf0e180 --- /dev/null +++ b/docs/exynos9810-cameras.md @@ -0,0 +1,55 @@ +# The Exynos 9810's cameras: two drivers for now + +The Galaxy S9 (`starlte`) and the Galaxy S9+ (`star2lte`) have the same camera hardware family, and this tree has two drivers +for it, written at the same time by different people on different phones. Both are built by the one configuration symbol +`CONFIG_HAKIOS_EXYNOS_CAMERA` and both offer the kernel the same three entry points (`hakios_exynos_camera_init`, +`_quiesce`, `_shot`) and `/dev/hakios-camera`. **A kernel has exactly one of them**: `tools/hakios-config.py` writes +`HAKIOS_DEVICE_IS_` into the generated `dev/hakios.drivers.h`, and each driver's files compile to nothing in the other +phone's kernel. They cannot both claim the hardware. + +| | the S9's (every configuration but `star2lte`; the ios-27 tree) | the S9+'s (`star2lte` only) | +|---|---|---| +| files | `hakios.exynos_camera.c`, `hakios.exynos_camera_rear.c`, `hakios.ak737x.c` | `hakios.exynos_camera_star2.c`, and two headers made at build time (below) | +| run on | a Galaxy S9, 12 November 2026 (front, then rear) | a Galaxy S9+, by Anan Jaser (front, rear, telephoto) | +| structure | one camera at a time chosen at boot: the front, or with `cam_rear=1` the rear | a table of three paths (front, rear, telephoto); the reader of `/dev/hakios-camera` picks one (`HAKIOS_CAMERA_SENSOR`) and the stream changes over | +| front, S5K3H1 on CSIS1 | `cam_s9=N` | `cam_s9=N`, the same steps (this is the file both grew from) | +| rear, S5K2L3 on CSIS0 | `cam_rear=1`: its own register sequence; 2016 x 1512 RAW10 at 30, four lanes at 1352 Mb/s, settle 66 | `cam_s9_rear=1` (and `cam_s9_rear_probe=N`): Samsung's setfile tables (`hakios.s5k2l3_setfiles.h`); the same mode, lanes, rate and settle | +| telephoto, S5K3M3 on CSIS2 | none (the S9 has no such camera) | `cam_s9_tele=1` (and `cam_s9_tele_probe=N`), settle 59 | +| the lens, AK737x at 0x0C | `cam_af=N` in `hakios.ak737x.c`: the module's EEPROM, a held position, autofocus by contrast | written by user space as a position; the telephoto's own AK737x at 0x0F | +| aperture, stabiliser | `cam_rear_ap=N`, `cam_rear_ois=N` | not driven | + +## Where the two agree (checked when they were put side by side) + +- The lens: address 0x0C, mode register 0x02 (0 active), first position 120, held 10 ms before active mode. +- The rear camera's link: CSIS0, four lanes, 1352 Mb/s, settle 66, a line of 815 ns, and its WDMA's input mux, `SYSREG_ISPPRE` + `SC_CON2` (0x40C), set to 1. +- The dedicated camera HSI2C blocks have no USI selector: both made the same change to `hakios.exynos_hsi2c.c`, and the tree has + it once. + +## Where they differ + +- The front camera's WDMA input mux (`SC_CON4`, 0x414): the S9's driver does not write it (it works with what the boot leaves); + the S9+'s writes 1 when it is not already 1. `SC_CON6` (0x41C) is written only by the S9+'s, for the telephoto. +- The rear sensor's start-up: a hand-written sequence on one side, Samsung's setfile tables on the other. **These have not been + compared register by register.** +- The S2MPB02's bus: the S9+'s driver takes a lock that the flashlight driver now offers (`hakios_s2mpb02_bus_lock`); the S9's + rear driver does not take it. +- Boot-arguments have different names for the same things (`cam_rear` and `cam_s9_rear`). +- The sensors' RAM patches: the S9's rear driver brings the S5K2L3 up without the sensor's patch ("TnP", written through the + indirect data port 0x6F12); the S9+'s driver writes it, and the S5K3M3's, as Samsung's driver does. Which the picture needs + is open, and is to be settled when the two are made one. + +## The S9+ driver's two made headers + +`hakios.s5k2l3_setfiles.h` and `hakios.s5k3m3_setfiles.h` hold Samsung's register tables for the two rear sensors, the RAM +patches among them. A sensor's patch is firmware, and firmware is not committed to this tree, so the headers are not in it: +`tools/mk-star2-setfiles.sh` fetches three headers of Samsung's GPL-2.0 kernel from the Apollo kernel's public repository at a +pinned commit, checks their SHA-256 and runs `tools/extract-2l3-setfile.py` and `tools/extract-3m3-setfile.py` on them; +`xnu-build.sh` does that for a `star2lte` kernel and lays the result beside the driver in the kernel tree. Without a network +the script says which three files to put where. + +## Why not one driver yet + +Making one driver of the two means choosing one structure and carrying the other's features into it, across some five +thousand lines, and then running it on both phones. On the day the two met, neither phone could be run. Until both can, each +phone keeps the code that was seen working on it. The unification is a job of its own; this page is where it starts from. diff --git a/drivers/camera/conf b/drivers/camera/conf index b3b962d82..34f181d99 100644 --- a/drivers/camera/conf +++ b/drivers/camera/conf @@ -21,6 +21,7 @@ driver EXYNOS_CAMERA file bsd/dev/hakios.exynos_camera.c file bsd/dev/hakios.ak737x.c file bsd/dev/hakios.exynos_camera_rear.c + file bsd/dev/hakios.exynos_camera_star2.c requires EXYNOS_HSI2C default n help the Galaxy S9's front camera, stages 1 to 3 (cam_s9=N): the S5K3H1's supplies on the S2MPB02, its reset and @@ -36,6 +37,8 @@ driver EXYNOS_CAMERA help supplies on the S2MPB03 and the S2MPB02, 2016 x 1512 RAW10 at 30 frames a second through the same steps; help its lens (the same cam_af), its stabiliser (cam_rear_ois=N) and its two-stop iris (cam_rear_ap=N); help hakios.exynos_camera_rear.c + help For the Galaxy S9+ (star2lte) the same symbol builds Anan Jaser's driver instead, hakios.exynos_camera_star2.c: + help its front, rear (cam_s9_rear) and telephoto (cam_s9_tele) cameras; the files above compile to nothing there driver EXYNOS_IRIS file bsd/dev/hakios.exynos_iris.c diff --git a/drivers/camera/exynos/hakios.ak737x.c b/drivers/camera/exynos/hakios.ak737x.c index 58022e63f..a93bbc13f 100644 --- a/drivers/camera/exynos/hakios.ak737x.c +++ b/drivers/camera/exynos/hakios.ak737x.c @@ -105,6 +105,16 @@ #include #include #include +#include /* (dev/hakios.drivers.h on the 16.5 line: which phone this kernel is for) */ + +/* + * Two camera drivers share the configuration symbol EXYNOS_CAMERA and the kernel's three entry points: this one (with + * hakios.ak737x.c and hakios.exynos_camera_rear.c), run on the Galaxy S9, and hakios.exynos_camera_star2.c, Anan Jaser's, run + * on the Galaxy S9+. A kernel has one of them: the S9+'s configuration (HAKIOS_DEVICE_IS_STAR2LTE, written into + * dev/hakios.drivers.h by tools/hakios-config.py) compiles this file to nothing, every other one compiles that file to + * nothing. Bringing the two into one driver waits until both phones can be run (docs/exynos9810-cameras.md). + */ +#ifndef HAKIOS_DEVICE_IS_STAR2LTE /* what hakios.exynos_camera.c calls (it declares them too) */ uint32_t hakios_ak737x_setup(uint32_t ipclk_mhz, int (*say)(const char *, ...)); @@ -887,3 +897,5 @@ hakios_ak737x_ask(uint32_t a, uint32_t b, uint32_t c, uint32_t d) __atomic_store_n(&ask_position, a > 1023 ? 1023 : a, __ATOMIC_RELEASE); } } + +#endif /* HAKIOS_DEVICE_IS_STAR2LTE */ diff --git a/drivers/camera/exynos/hakios.exynos_camera.c b/drivers/camera/exynos/hakios.exynos_camera.c index 4708d57aa..cef915c5c 100644 --- a/drivers/camera/exynos/hakios.exynos_camera.c +++ b/drivers/camera/exynos/hakios.exynos_camera.c @@ -255,6 +255,15 @@ #include #include +/* + * Two camera drivers share the configuration symbol EXYNOS_CAMERA and the kernel's three entry points: this one (with + * hakios.ak737x.c and hakios.exynos_camera_rear.c), run on the Galaxy S9, and hakios.exynos_camera_star2.c, Anan Jaser's, run + * on the Galaxy S9+. A kernel has one of them: the S9+'s configuration (HAKIOS_DEVICE_IS_STAR2LTE, written into + * dev/hakios.drivers.h by tools/hakios-config.py) compiles this file to nothing, every other one compiles that file to + * nothing. Bringing the two into one driver waits until both phones can be run (docs/exynos9810-cameras.md). + */ +#ifndef HAKIOS_DEVICE_IS_STAR2LTE + static bool leaving; static int say(const char *format, ...) __printflike(1, 2); @@ -3663,3 +3672,5 @@ hakios_exynos_camera_init(void) } } } + +#endif /* HAKIOS_DEVICE_IS_STAR2LTE */ diff --git a/drivers/camera/exynos/hakios.exynos_camera_rear.c b/drivers/camera/exynos/hakios.exynos_camera_rear.c index a7024cf58..c59c13c6d 100644 --- a/drivers/camera/exynos/hakios.exynos_camera_rear.c +++ b/drivers/camera/exynos/hakios.exynos_camera_rear.c @@ -118,6 +118,15 @@ #include #include +/* + * Two camera drivers share the configuration symbol EXYNOS_CAMERA and the kernel's three entry points: this one (with + * hakios.ak737x.c and hakios.exynos_camera_rear.c), run on the Galaxy S9, and hakios.exynos_camera_star2.c, Anan Jaser's, run + * on the Galaxy S9+. A kernel has one of them: the S9+'s configuration (HAKIOS_DEVICE_IS_STAR2LTE, written into + * dev/hakios.drivers.h by tools/hakios-config.py) compiles this file to nothing, every other one compiles that file to + * nothing. Bringing the two into one driver waits until both phones can be run (docs/exynos9810-cameras.md). + */ +#ifndef HAKIOS_DEVICE_IS_STAR2LTE + /* what hakios.exynos_camera.c calls (it declares them too) */ uint32_t hakios_s9rear_setup(uint32_t ipclk_mhz, int (*say)(const char *, ...)); bool hakios_s9rear_map(vm_offset_t peric0, vm_offset_t peric1, vm_offset_t cmu_top); @@ -1382,3 +1391,5 @@ hakios_s9rear_poll(uint64_t frame) } } } + +#endif /* HAKIOS_DEVICE_IS_STAR2LTE */ diff --git a/drivers/camera/exynos/hakios.exynos_camera_star2.c b/drivers/camera/exynos/hakios.exynos_camera_star2.c index cadebb4f4..ec7792986 100644 --- a/drivers/camera/exynos/hakios.exynos_camera_star2.c +++ b/drivers/camera/exynos/hakios.exynos_camera_star2.c @@ -288,6 +288,15 @@ #include "hakios.s5k2l3_setfiles.h" #include "hakios.s5k3m3_setfiles.h" +/* + * Two camera drivers share the configuration symbol EXYNOS_CAMERA and the kernel's three entry points: hakios.exynos_camera.c (with + * hakios.ak737x.c and hakios.exynos_camera_rear.c), run on the Galaxy S9, and this one, Anan Jaser's, run on the + * Galaxy S9+. A kernel has one of them: the S9+'s configuration (HAKIOS_DEVICE_IS_STAR2LTE, written into + * dev/hakios.drivers.h by tools/hakios-config.py) compiles those files to nothing, every other one compiles this file to + * nothing. Bringing the two into one driver waits until both phones can be run (docs/exynos9810-cameras.md). + */ +#ifdef HAKIOS_DEVICE_IS_STAR2LTE + /* the S2MPB02's I2C controller is the flashlight's too (hakios.s2mpb02_torch.c): one transfer at a time */ void hakios_s2mpb02_bus_lock(void); void hakios_s2mpb02_bus_unlock(void); @@ -4451,3 +4460,5 @@ hakios_exynos_camera_init(void) thread_deallocate(thread); } } + +#endif /* HAKIOS_DEVICE_IS_STAR2LTE */ diff --git a/drivers/map b/drivers/map index e9e932e35..5c0d8689f 100644 --- a/drivers/map +++ b/drivers/map @@ -48,6 +48,7 @@ camera/exynos bsd/dev/hakios.s2mpb02_torch.c camera/exynos bsd/dev/hakios.exynos_camera.c camera/exynos bsd/dev/hakios.ak737x.c camera/exynos bsd/dev/hakios.exynos_camera_rear.c +camera/exynos bsd/dev/hakios.exynos_camera_star2.c camera/exynos bsd/dev/hakios.exynos_iris.c haptic bsd/dev/hakios.haptic.c diff --git a/tools/extract-2l3-setfile.py b/tools/extract-2l3-setfile.py old mode 100644 new mode 100755 diff --git a/tools/hakios-config.py b/tools/hakios-config.py old mode 100644 new mode 100755 index 2f102c948..79a0036b6 --- a/tools/hakios-config.py +++ b/tools/hakios-config.py @@ -1052,7 +1052,10 @@ def drivers_header(built, sources, device="", soc="", builtin_args=()): return ("/* The drivers this kernel is built with: made by tools/hakios-config.py kernel from %s. Not part of the patch. */\n" "#define HAKIOS_DRIVERS_LIST \"%s\"\n#define HAKIOS_DRIVERS_COUNT %d\n#define HAKIOS_DRIVERS_CONFIG \"%s\"\n" "#define HAKIOS_DEVICE \"%s\"\n#define HAKIOS_BUILTIN_ARGS \"%s\"\n" % - (sources, " ".join(on), len(on), sources, device, " ".join(builtin_args))) + soc + (sources, " ".join(on), len(on), sources, device, " ".join(builtin_args)) + + # the device again as a name the preprocessor can test (#ifdef HAKIOS_DEVICE_IS_STAR2LTE): for the files that are + # one phone's whole driver (drivers/camera/exynos: the S9's and the S9+'s camera drivers, one of them in a kernel) + ("#define HAKIOS_DEVICE_IS_%s 1\n" % re.sub(r"[^A-Za-z0-9]", "_", device).upper() if device else "")) + soc def write_if_changed(path, text): diff --git a/tools/mk-star2-setfiles.sh b/tools/mk-star2-setfiles.sh new file mode 100755 index 000000000..7abf2e881 --- /dev/null +++ b/tools/mk-star2-setfiles.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT-0 +# tools/mk-star2-setfiles.sh [DIRECTORY]: the two generated headers the Galaxy S9+'s camera driver includes +# (drivers/camera/exynos/hakios.exynos_camera_star2.c): hakios.s5k2l3_setfiles.h and hakios.s5k3m3_setfiles.h. +# +# They are made from three headers of Samsung's GPL-2.0 kernel for the Exynos 9810 (the camera sensors' register tables, +# which also hold each sensor's RAM patch, written to it at start through its indirect data port). That is firmware in +# substance, so neither Samsung's headers nor the two made from them are in this repository: like brcmfmac +# (tools/mk-brcmfmac.sh) they are fetched when a kernel that needs them is built, from a public source at a pinned +# commit, checked by their SHA-256, and what is made stays in the build directory (xnu-deps/build/star2-camera, which git +# ignores). xnu-build.sh runs this for a star2lte kernel with the camera driver and copies the result into the kernel tree. +# +# The source: Anan Jaser's Apollo kernel, github.com/ananjaser1211/Apollo, at the commit below (GPL-2.0; +# drivers/media/platform/exynos/fimc-is2/sensor/module_framework/cis/). Without a network: put the three files, as they +# are at that commit, into xnu-deps/build/star2-camera/src/ and run this again; their hashes are checked all the same. +# +# The headers are made by tools/extract-2l3-setfile.py and tools/extract-3m3-setfile.py (Anan Jaser's), unchanged. +set -euo pipefail +ROOT=$(cd "$(dirname "$0")/.." && pwd) +OUT=${1:-$ROOT/xnu-deps/build/star2-camera} +COMMIT=30656faf075847e98f0f60a8b4282a25c7e23f80 +BASE=https://raw.githubusercontent.com/ananjaser1211/Apollo/$COMMIT/drivers/media/platform/exynos/fimc-is2/sensor/module_framework/cis +FILES="fimc-is-cis-2l3-setA.h:1ac924a6e98f8ae52f00bc1f0be05b108ebb1eaee6479efc4d63084069fd8d65 +fimc-is-cis-2l3-setB.h:cf42c93070f97e3c71db664eddbe288ec1789223b0318d1d233d43bf6052a8f2 +fimc-is-cis-3m3-setA.h:cc3de932091e06bdab0fa089985b271ecd3faee368c20d0604e1cf8d420eca3b" + +sha() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi; } +mkdir -p "$OUT/src" +for entry in $FILES; do + name=${entry%%:*}; want=${entry##*:}; file=$OUT/src/$name + if [ ! -f "$file" ] || [ "$(sha "$file")" != "$want" ]; then + echo "mk-star2-setfiles: fetching $name (Apollo $COMMIT)" + curl -sfL -o "$file.part" "$BASE/$name" && mv "$file.part" "$file" || { + rm -f "$file.part" + echo "mk-star2-setfiles: cannot fetch $BASE/$name" >&2 + echo "mk-star2-setfiles: fetch that file by hand (GPL-2.0, Samsung's, as carried in Apollo at commit $COMMIT)" >&2 + echo "mk-star2-setfiles: and put it at $file (SHA-256 $want), then build again" >&2 + exit 1 + } + fi + got=$(sha "$file") + [ "$got" = "$want" ] || { echo "mk-star2-setfiles: $file is not the file of commit $COMMIT (SHA-256 $got, want $want)" >&2; exit 1; } +done +python3 "$ROOT/tools/extract-2l3-setfile.py" "$OUT/src/fimc-is-cis-2l3-setA.h" "$OUT/src/fimc-is-cis-2l3-setB.h" > "$OUT/hakios.s5k2l3_setfiles.h.new" +python3 "$ROOT/tools/extract-3m3-setfile.py" "$OUT/src/fimc-is-cis-3m3-setA.h" > "$OUT/hakios.s5k3m3_setfiles.h.new" +for h in hakios.s5k2l3_setfiles.h hakios.s5k3m3_setfiles.h; do + if cmp -s "$OUT/$h.new" "$OUT/$h" 2>/dev/null; then rm "$OUT/$h.new"; else mv "$OUT/$h.new" "$OUT/$h"; fi +done +echo "mk-star2-setfiles: $OUT/hakios.s5k2l3_setfiles.h and hakios.s5k3m3_setfiles.h are current" diff --git a/xnu-build.sh b/xnu-build.sh index 24adbdbcf..14f088ddd 100755 --- a/xnu-build.sh +++ b/xnu-build.sh @@ -26,6 +26,16 @@ if [ -n "$LX_BUILD" ] && [ ! -f "$LX" ]; then echo "xnu-build: required Wi-Fi object was not produced: $LX" >&2 exit 1 fi +# The Galaxy S9+'s camera driver (bsd/dev/hakios.exynos_camera_star2.c) includes two headers that are made, not kept in +# the repository: the sensors' register tables with their RAM patches, from Samsung's GPL headers (tools/mk-star2-setfiles.sh +# says where they come from and why). A star2lte kernel with the camera driver gets them here, beside the driver. +if grep -q '^#define HAKIOS_DEVICE_IS_STAR2LTE' bsd/dev/hakios.drivers.h 2>/dev/null && + grep -q '^#define HAKIOS_DRIVERS_LIST .*[" ]exynos_camera[" ]' bsd/dev/hakios.drivers.h; then + "$ROOT/tools/mk-star2-setfiles.sh" || exit 1 + for h in hakios.s5k2l3_setfiles.h hakios.s5k3m3_setfiles.h; do + cmp -s "$XNUDEPS/build/star2-camera/$h" "bsd/dev/$h" || cp "$XNUDEPS/build/star2-camera/$h" "bsd/dev/$h" + done +fi STAMP=BUILD/obj/hakios-objects # (macOS: its own stat, not tools/macos-bin/stat, which is GNU stat for the scripts written for it and may be absent) mtime() { if [ "$XNU_HOST_OS" = Darwin ]; then /usr/bin/stat -f %m "$1"; else stat -c %Y "$1"; fi; } From 9c06ababad5405c1b0d872e438277dd32ad08fd0 Mon Sep 17 00:00:00 2001 From: Claudius Date: Thu, 12 Nov 2026 17:30:24 +0000 Subject: [PATCH 6/6] The catalogues made again (the S9+ camera driver's arguments: cam_s9_rear, cam_s9_tele and their probes, torch_flash_ms; drivers.md) --- configs/boot-args.lst | 16 ++++++++++ configs/common_defconfig | 62 ++++++++++++++++++++++++++++++++++----- configs/symbols.tsv | 43 +++++++++++++++++---------- docs/boot-args.md | 54 ++++++++++++++++++++++------------ docs/reference/drivers.md | 4 +-- 5 files changed, 135 insertions(+), 44 deletions(-) diff --git a/configs/boot-args.lst b/configs/boot-args.lst index a4935c49a..d5040b5a9 100644 --- a/configs/boot-args.lst +++ b/configs/boot-args.lst @@ -141,6 +141,7 @@ cam_s9 k cam_s9_addr k cam_s9_ae k cam_s9_ae_frames k +cam_s9_af k cam_s9_always k cam_s9_bayer k cam_s9_cached k @@ -152,12 +153,25 @@ cam_s9_ispdiv k cam_s9_mmu_probe k cam_s9_pack12 k cam_s9_probe k +cam_s9_rear k +cam_s9_rear_bayer k +cam_s9_rear_exp k +cam_s9_rear_ispdiv k +cam_s9_rear_probe k +cam_s9_rear_tp k +cam_s9_rear_turn k cam_s9_sat k cam_s9_selfread k cam_s9_selfread_focus k cam_s9_selfread_x k cam_s9_selfread_y k cam_s9_stride k +cam_s9_tele k +cam_s9_tele_bayer k +cam_s9_tele_exp k +cam_s9_tele_probe k +cam_s9_tele_tp k +cam_s9_tele_turn k cam_sensor k cam_settle k cam_shutter k @@ -537,6 +551,8 @@ tmu_poll k tmu_stop k tmu_ungate k torch kl +torch_code_max k +torch_flash_ms k torch_max_s k torch_secs k touch_coalesce k diff --git a/configs/common_defconfig b/configs/common_defconfig index c8ec76704..c9eed375f 100644 --- a/configs/common_defconfig +++ b/configs/common_defconfig @@ -2,7 +2,7 @@ # HakiOS common defconfig: every setting there is, with what it does. # Made by tools/hakios-config.py listing common -o ; the values are the ones that file sets, and tests/CI check that it names every symbol. # -# 716 symbols in 20 groups; 54 are set, 45 of them drivers built in. +# 732 symbols in 20 groups; 54 are set, 45 of them drivers built in. # # @@ -61,7 +61,7 @@ CONFIG_HAKIOS_TORCH=y # the Galaxy S9's flashlight: the S2MPB02's flash block on PERIC1's HSI2C, lit by a pin (torch=N there, the same steps ... [hakios.s2mpb02_torch.c; requires CONFIG_HAKIOS_EXYNOS_HSI2C] CONFIG_HAKIOS_S2MPB02_TORCH=y -# the Galaxy S9's front camera, stages 1 to 3 (cam_s9=N): the S5K3H1's supplies on the S2MPB02, its reset and MCLK, ... [hakios.exynos_camera.c, hakios.ak737x.c, hakios.exynos_camera_rear.c; requires CONFIG_HAKIOS_EXYNOS_HSI2C] +# the Galaxy S9's front camera, stages 1 to 3 (cam_s9=N): the S5K3H1's supplies on the S2MPB02, its reset and MCLK, ... [hakios.exynos_camera.c, hakios.ak737x.c, hakios.exynos_camera_rear.c, hakios.exynos_camera_star2.c; requires CONFIG_HAKIOS_EXYNOS_HSI2C] # CONFIG_HAKIOS_EXYNOS_CAMERA is not set # the Galaxy S9's iris camera (iris_s9=N): the S5K5F1 infrared sensor on CSIS3, its supplies (the S2MPS18's LDO39-41 ... [hakios.exynos_iris.c; requires CONFIG_HAKIOS_EXYNOS_HSI2C, CONFIG_HAKIOS_EXYNOS_ACPM] @@ -1410,7 +1410,7 @@ CONFIG_MODEM_AGAIN=y # no auto-exposure: the frames as cam_s9_exp and cam_s9_gain have them (default 1: on) # CONFIG_CAM_S9_AE is not set -# read by hakios.exynos_camera.c (kernel) +# read by hakios.exynos_camera.c, hakios.exynos_camera_star2.c (kernel) # CONFIG_CAM_S9_AE_FRAMES is not set # with 7: the stream from the boot to the run's end, wanted or not (the old behaviour, for debugging) @@ -1431,13 +1431,13 @@ CONFIG_MODEM_AGAIN=y # the first analog gain of step 5's frame, 0x20 being 1x (default 0x40, at most 0x200) # CONFIG_CAM_S9_GAIN is not set -# read by hakios.exynos_camera.c (kernel) +# read by hakios.exynos_camera.c, hakios.exynos_camera_star2.c (kernel) # CONFIG_CAM_S9_IPCLK is not set # CLKCMU_ISPPRE_BUS (CSIS1's and its WDMA's clock) = SHARED1_DIV4, 466 MHz, / (D + 1); default # CONFIG_CAM_S9_ISPDIV is not set -# read by hakios.exynos_camera.c (kernel) +# read by hakios.exynos_camera.c, hakios.exynos_camera_star2.c (kernel) # CONFIG_CAM_S9_MMU_PROBE is not set # the WDMA's PACK12 layout rather than RAW10 as it comes (the BSP sets PACK12 for its V4L2 SBGGR10 buffers) @@ -1492,7 +1492,7 @@ CONFIG_MODEM_AGAIN=y # CONFIG_SCCAM_W is not set # -# Haptics and torch (5) +# Haptics and torch (6) # # the vibration motor (5 is what the README's phone line uses) [needs fw-haptic/ from the phone] @@ -1504,6 +1504,9 @@ CONFIG_MODEM_AGAIN=y # a look, reading only: the bus as found, the chip's ID, the flash block's registers, the two pins # CONFIG_TORCH is not set +# the torch current code iOS's full level maps to (1-9, default 3: the S9's 60 mA) +# CONFIG_TORCH_CODE_MAX is not set + # read by hakios.s2mpb02_torch.c, hakios.torch.c (kernel) # CONFIG_TORCH_MAX_S is not set @@ -1672,9 +1675,24 @@ CONFIG_MODEM_AGAIN=y # CONFIG_UFS_WRITE_GUID is not set # -# User space (155) +# User space (170) # +# Star2's primary rear camera (S5K2L3): 2 powers it up at boot as its stream would, reads its ID and revision, powers ... +# CONFIG_CAM_S9_REAR_PROBE is not set + +# Star2's primary rear camera (S5K2L3 on CSIS0) as the iOS back camera, 2016x1512 at 30; the front camera stays usable ... [requires CONFIG_CAM_S9=7] +# CONFIG_CAM_S9_REAR is not set + +# Star2's telephoto camera (S5K3M3): 1 powers it up at boot as its stream would, reads its ID and revision, powers it ... +# CONFIG_CAM_S9_TELE_PROBE is not set + +# Star2's telephoto camera (S5K3M3 on CSIS2) as the iOS back telephoto, 2016x1512 at 30; hidden again if it fails [requires CONFIG_CAM_S9=7] +# CONFIG_CAM_S9_TELE is not set + +# test the Galaxy S9 rear camera flash at boot for this many milliseconds (1 to 500) +# CONFIG_TORCH_FLASH_MS is not set + # arguments only the kernel reads built into the kernel (HAKIOS_BUILTIN_ARGS); given on the line, an argument goes ... # CONFIG_BUILTIN_ARGS is not set @@ -1807,6 +1825,36 @@ CONFIG_MODEM_AGAIN=y # with 2: 0 still (the default), 1 video, 5 centring # CONFIG_CAM_REAR_OIS_MODE is not set +# no focus (the lens left where its spring holds it) +# CONFIG_CAM_S9_AF is not set + +# the rear frames' Bayer order (default 1, GRBG: fimc-is-cis-2l3.c's GR_BG with 0x0101 = 3) +# CONFIG_CAM_S9_REAR_BAYER is not set + +# the rear stream's first exposure in lines of 815 ns (default 30000, about 24 ms; its gain starts at 4x) +# CONFIG_CAM_S9_REAR_EXP is not set + +# the ISPPRE bus clock for the rear stream, 466 MHz / (D + 1); default 0, Android's rate +# CONFIG_CAM_S9_REAR_ISPDIV is not set + +# the rear sensor's MIPI CCS test pattern (0x0600: 1 solid colour, 2 colour bars) for its picture +# CONFIG_CAM_S9_REAR_TP is not set + +# the rear frames turned half round for user space (default 0: the sensor flips itself) +# CONFIG_CAM_S9_REAR_TURN is not set + +# the tele frames' Bayer order (default 1, GRBG: fimc-is-cis-3m3.c's GR_BG) +# CONFIG_CAM_S9_TELE_BAYER is not set + +# the tele stream's first exposure in lines of 20.3 us (default 1000; its gain starts at 4x) +# CONFIG_CAM_S9_TELE_EXP is not set + +# the tele sensor's MIPI CCS test pattern (0x0600: 1 solid colour, 2 colour bars) for its picture +# CONFIG_CAM_S9_TELE_TP is not set + +# the tele frames turned half round for user space (default 0, as Apollo streams it: no flip) +# CONFIG_CAM_S9_TELE_TURN is not set + # read by inject.c (user space) # CONFIG_CAMTL is not set diff --git a/configs/symbols.tsv b/configs/symbols.tsv index 807294180..214128d22 100644 --- a/configs/symbols.tsv +++ b/configs/symbols.tsv @@ -131,27 +131,37 @@ CAM_REAR_ADDR hex cam_rear_addr User space k hakios.exynos_camera_rear.c the sen CAM_REAR_AP arg cam_rear_ap User space k hakios.exynos_camera_rear.c the iris: 1 a look, 15 or 24 put at F1.5 or F2.4 at each power-up (0 or none: left where it rests) CAM_REAR_OIS arg cam_rear_ois User space k hakios.exynos_camera_rear.c CAM_REAR_OIS_MODE arg cam_rear_ois_mode User space k hakios.exynos_camera_rear.c with 2: 0 still (the default), 1 video, 5 centring -CAM_S9 arg cam_s9 Cameras k hakios.exynos_camera.c,hakios.exynos_iris.c,hakios.s2mpb02_torch.c the front camera's first N steps: 1 a look, 2 the sensor's ID, 3 the ISPPRE domain and CSIS1 -CAM_S9_ADDR hex cam_s9_addr Cameras k hakios.exynos_camera.c the sensor's 7-bit I2C address, if not 0x35 (the device tree's) and not found by the scan -CAM_S9_AE arg cam_s9_ae Cameras k hakios.exynos_camera.c no auto-exposure: the frames as cam_s9_exp and cam_s9_gain have them (default 1: on) -CAM_S9_AE_FRAMES arg cam_s9_ae_frames Cameras k hakios.exynos_camera.c -CAM_S9_ALWAYS arg cam_s9_always Cameras k hakios.exynos_camera.c with 7: the stream from the boot to the run's end, wanted or not (the old behaviour, for debugging) -CAM_S9_BAYER arg cam_s9_bayer Cameras k hakios.exynos_camera.c camshot's Bayer order: 0 RGGB, 1 GRBG (the default, the sensor's), 2 GBRG, 3 BGGR +CAM_S9 arg cam_s9 Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c,hakios.exynos_iris.c,hakios.s2mpb02_torch.c the front camera's first N steps: 1 a look, 2 the sensor's ID, 3 the ISPPRE domain and CSIS1 +CAM_S9_ADDR hex cam_s9_addr Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c the sensor's 7-bit I2C address, if not 0x35 (the device tree's) and not found by the scan +CAM_S9_AE arg cam_s9_ae Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c no auto-exposure: the frames as cam_s9_exp and cam_s9_gain have them (default 1: on) +CAM_S9_AE_FRAMES arg cam_s9_ae_frames Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c +CAM_S9_AF arg cam_s9_af User space k hakios.exynos_camera_star2.c no focus (the lens left where its spring holds it) +CAM_S9_ALWAYS arg cam_s9_always Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c with 7: the stream from the boot to the run's end, wanted or not (the old behaviour, for debugging) +CAM_S9_BAYER arg cam_s9_bayer Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c camshot's Bayer order: 0 RGGB, 1 GRBG (the default, the sensor's), 2 GBRG, 3 BGGR CAM_S9_CACHED arg cam_s9_cached Cameras k hakios.exynos_camera.c read() copies the frame through a second, cached mapping of the ring (see cam_cached below) -CAM_S9_DGAIN arg cam_s9_dgain Cameras k hakios.exynos_camera.c the most digital gain the auto-exposure may use, 0x100 being 1x (default 0x400, at most 0x1000) -CAM_S9_EXP arg cam_s9_exp Cameras k hakios.exynos_camera.c the first exposure of step 5's frame in lines of 26.3 us (default 1000, at most 1262) -CAM_S9_GAIN arg cam_s9_gain Cameras k hakios.exynos_camera.c the first analog gain of step 5's frame, 0x20 being 1x (default 0x40, at most 0x200) -CAM_S9_IPCLK arg cam_s9_ipclk Cameras k hakios.exynos_camera.c -CAM_S9_ISPDIV arg cam_s9_ispdiv Cameras k hakios.exynos_camera.c CLKCMU_ISPPRE_BUS (CSIS1's and its WDMA's clock) = SHARED1_DIV4, 466 MHz, / (D + 1); default -CAM_S9_MMU_PROBE arg cam_s9_mmu_probe Cameras k hakios.exynos_camera.c -CAM_S9_PACK12 arg cam_s9_pack12 Cameras k hakios.exynos_camera.c the WDMA's PACK12 layout rather than RAW10 as it comes (the BSP sets PACK12 for its V4L2 SBGGR10 buffers) -CAM_S9_PROBE arg cam_s9_probe Cameras k hakios.exynos_camera.c the risky reads too, for the log: CMU_ISPPRE's whole Q-channel sweep with each DBG_NFO (+0x4000) -CAM_S9_SAT arg cam_s9_sat Cameras k hakios.exynos_camera.c camshot's colour saturation in per cent (default 140; 100: none; at most 300) +CAM_S9_DGAIN arg cam_s9_dgain Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c the most digital gain the auto-exposure may use, 0x100 being 1x (default 0x400, at most 0x1000) +CAM_S9_EXP arg cam_s9_exp Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c the first exposure of step 5's frame in lines of 26.3 us (default 1000, at most 1262) +CAM_S9_GAIN arg cam_s9_gain Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c the first analog gain of step 5's frame, 0x20 being 1x (default 0x40, at most 0x200) +CAM_S9_IPCLK arg cam_s9_ipclk Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c +CAM_S9_ISPDIV arg cam_s9_ispdiv Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c CLKCMU_ISPPRE_BUS (CSIS1's and its WDMA's clock) = SHARED1_DIV4, 466 MHz, / (D + 1); default +CAM_S9_MMU_PROBE arg cam_s9_mmu_probe Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c +CAM_S9_PACK12 arg cam_s9_pack12 Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c the WDMA's PACK12 layout rather than RAW10 as it comes (the BSP sets PACK12 for its V4L2 SBGGR10 buffers) +CAM_S9_PROBE arg cam_s9_probe Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c the risky reads too, for the log: CMU_ISPPRE's whole Q-channel sweep with each DBG_NFO (+0x4000) +CAM_S9_REAR_BAYER arg cam_s9_rear_bayer User space k hakios.exynos_camera_star2.c the rear frames' Bayer order (default 1, GRBG: fimc-is-cis-2l3.c's GR_BG with 0x0101 = 3) +CAM_S9_REAR_EXP arg cam_s9_rear_exp User space k hakios.exynos_camera_star2.c the rear stream's first exposure in lines of 815 ns (default 30000, about 24 ms; its gain starts at 4x) +CAM_S9_REAR_ISPDIV arg cam_s9_rear_ispdiv User space k hakios.exynos_camera_star2.c the ISPPRE bus clock for the rear stream, 466 MHz / (D + 1); default 0, Android's rate +CAM_S9_REAR_TP arg cam_s9_rear_tp User space k hakios.exynos_camera_star2.c the rear sensor's MIPI CCS test pattern (0x0600: 1 solid colour, 2 colour bars) for its picture +CAM_S9_REAR_TURN arg cam_s9_rear_turn User space k hakios.exynos_camera_star2.c the rear frames turned half round for user space (default 0: the sensor flips itself) +CAM_S9_SAT arg cam_s9_sat Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c camshot's colour saturation in per cent (default 140; 100: none; at most 300) CAM_S9_SELFREAD arg cam_s9_selfread Cameras k hakios.exynos_camera.c CAM_S9_SELFREAD_FOCUS arg cam_s9_selfread_focus Cameras k hakios.exynos_camera.c the reader also writes HAKIOS_CAMERA_FOCUS, as a capture device would (cam_af=4): after its CAM_S9_SELFREAD_X arg cam_s9_selfread_x Cameras k hakios.exynos_camera.c CAM_S9_SELFREAD_Y arg cam_s9_selfread_y Cameras k hakios.exynos_camera.c -CAM_S9_STRIDE arg cam_s9_stride Cameras k hakios.exynos_camera.c camshot's bytes a line of the frame (default 3264: the WDMA's RAW10 is a pixel in 16 bits) +CAM_S9_STRIDE arg cam_s9_stride Cameras k hakios.exynos_camera.c,hakios.exynos_camera_star2.c camshot's bytes a line of the frame (default 3264: the WDMA's RAW10 is a pixel in 16 bits) +CAM_S9_TELE_BAYER arg cam_s9_tele_bayer User space k hakios.exynos_camera_star2.c the tele frames' Bayer order (default 1, GRBG: fimc-is-cis-3m3.c's GR_BG) +CAM_S9_TELE_EXP arg cam_s9_tele_exp User space k hakios.exynos_camera_star2.c the tele stream's first exposure in lines of 20.3 us (default 1000; its gain starts at 4x) +CAM_S9_TELE_TP arg cam_s9_tele_tp User space k hakios.exynos_camera_star2.c the tele sensor's MIPI CCS test pattern (0x0600: 1 solid colour, 2 colour bars) for its picture +CAM_S9_TELE_TURN arg cam_s9_tele_turn User space k hakios.exynos_camera_star2.c the tele frames turned half round for user space (default 0, as Apollo streams it: no flip) CAM_SENSOR arg cam_sensor Cameras k hakios.camera.c CAM_SETTLE arg cam_settle Cameras k hakios.camera.c CAM_SHUTTER arg cam_shutter Cameras k hakios.camera.c @@ -510,6 +520,7 @@ TMU_POLL arg tmu_poll Chip, thermal and PMIC k hakios.exynos_tmu.c how often to TMU_STOP arg tmu_stop Chip, thermal and PMIC k hakios.exynos_tmu.c step 3's limit for the SoC's zones (40 to 115; 110) TMU_UNGATE arg tmu_ungate Chip, thermal and PMIC k hakios.exynos_tmu.c open the TMU's PCLK gate if the boot left it forced shut TORCH arg torch Haptics and torch kl hakios.exynos_iris.c,hakios.s2mpb02_torch.c,hakios.sccam_torch.c,hakios.torch.c,loader.c a look, reading only: the bus as found, the chip's ID, the flash block's registers, the two pins +TORCH_CODE_MAX arg torch_code_max Haptics and torch k hakios.s2mpb02_torch.c the torch current code iOS's full level maps to (1-9, default 3: the S9's 60 mA) TORCH_MAX_S arg torch_max_s Haptics and torch k hakios.s2mpb02_torch.c,hakios.torch.c TORCH_SECS arg torch_secs Haptics and torch k hakios.s2mpb02_torch.c,hakios.torch.c TOUCH_COALESCE arg touch_coalesce Touch and buttons k hakios.input.c a reader that falls behind gets the newest pending MOVE of a finger, never a lost DOWN/UP. Off by default diff --git a/docs/boot-args.md b/docs/boot-args.md index e51e31d8e..1f6bbaa8a 100644 --- a/docs/boot-args.md +++ b/docs/boot-args.md @@ -2,7 +2,7 @@ Made by `tools/boot-args.py --markdown`; CI fails if it is out of date with the source (`tools/boot-args.py --check`). -Every argument that the kernel, the loader or user space of this port reads, and which file reads it. **659 arguments**: 568 read by the kernel, 23 by the loader, 85 by user space (one is read in more than one place). +Every argument that the kernel, the loader or user space of this port reads, and which file reads it. **675 arguments**: 584 read by the kernel, 23 by the loader, 85 by user space (one is read in more than one place). This is a catalogue of what exists and who reads it, not a manual. What an argument means is in the head of the file that reads it. The stable switches have a symbol in the configuration (`tools/hakios-config.py help SYMBOL`, `docs/configs.md`); the rest are the knobs of bring-up (a driver's steps, pauses, traces, test modes) and can change with the driver. Any of them can be given in a configuration with `CONFIG_BOOTARGS="name=value ..."`, which checks that each name is in this list. What the arguments of the two Metal paths do: [Metal on the Adreno](reference/metal.md#boot-arguments). @@ -334,27 +334,27 @@ Layers: **K** the kernel, **L** the loader, **U** user space (through `sysctl ke | `cam_keep_mb` | K | `hakios.camera.c` | `CONFIG_CAM_KEEP_MB` | | | `cam_mode` | K | `hakios.camera.c` | `CONFIG_CAM_MODE` | | | `cam_pattern` | K | `hakios.camera.c` | `CONFIG_CAM_PATTERN` | | -| `cam_s9` | K | `hakios.exynos_camera.c`, `hakios.exynos_iris.c`, `hakios.s2mpb02_torch.c` | `CONFIG_CAM_S9` | | -| `cam_s9_addr` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_ADDR` | | -| `cam_s9_ae` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_AE` | | -| `cam_s9_ae_frames` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_AE_FRAMES` | | -| `cam_s9_always` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_ALWAYS` | | -| `cam_s9_bayer` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_BAYER` | | +| `cam_s9` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c`, `hakios.exynos_iris.c`, `hakios.s2mpb02_torch.c` | `CONFIG_CAM_S9` | | +| `cam_s9_addr` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_ADDR` | | +| `cam_s9_ae` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_AE` | | +| `cam_s9_ae_frames` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_AE_FRAMES` | | +| `cam_s9_always` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_ALWAYS` | | +| `cam_s9_bayer` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_BAYER` | | | `cam_s9_cached` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_CACHED` | | -| `cam_s9_dgain` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_DGAIN` | | -| `cam_s9_exp` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_EXP` | | -| `cam_s9_gain` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_GAIN` | | -| `cam_s9_ipclk` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_IPCLK` | | -| `cam_s9_ispdiv` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_ISPDIV` | | -| `cam_s9_mmu_probe` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_MMU_PROBE` | | -| `cam_s9_pack12` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_PACK12` | | -| `cam_s9_probe` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_PROBE` | | -| `cam_s9_sat` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_SAT` | | +| `cam_s9_dgain` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_DGAIN` | | +| `cam_s9_exp` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_EXP` | | +| `cam_s9_gain` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_GAIN` | | +| `cam_s9_ipclk` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_IPCLK` | | +| `cam_s9_ispdiv` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_ISPDIV` | | +| `cam_s9_mmu_probe` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_MMU_PROBE` | | +| `cam_s9_pack12` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_PACK12` | | +| `cam_s9_probe` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_PROBE` | | +| `cam_s9_sat` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_SAT` | | | `cam_s9_selfread` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_SELFREAD` | | | `cam_s9_selfread_focus` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_SELFREAD_FOCUS` | | | `cam_s9_selfread_x` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_SELFREAD_X` | | | `cam_s9_selfread_y` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_SELFREAD_Y` | | -| `cam_s9_stride` | K | `hakios.exynos_camera.c` | `CONFIG_CAM_S9_STRIDE` | | +| `cam_s9_stride` | K | `hakios.exynos_camera.c`, `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_STRIDE` | | | `cam_sensor` | K | `hakios.camera.c` | `CONFIG_CAM_SENSOR` | | | `cam_settle` | K | `hakios.camera.c` | `CONFIG_CAM_SETTLE` | | | `cam_shutter` | K | `hakios.camera.c` | `CONFIG_CAM_SHUTTER` | | @@ -495,13 +495,15 @@ Layers: **K** the kernel, **L** the loader, **U** user space (through `sysctl ke | `usb_gadget_soc` | K | `hakios.dwc3.c` | `CONFIG_USB_GADGET_SOC` | | | `usb_gadget_susphy` | K | `hakios.dwc3.c` | `CONFIG_USB_GADGET_SUSPHY` | | -## Haptics and torch (5) +## Haptics and torch (7) | Argument | Layer | Read by | Configuration | Set by | |---|---|---|---|---| | `haptic` | K | `hakios.haptic.c` | `CONFIG_HAPTIC` | | | `haptic_effect` | K | `hakios.haptic.c` | `CONFIG_HAPTIC_EFFECT` | | | `torch` | KL | `hakios.exynos_iris.c`, `hakios.s2mpb02_torch.c`, `hakios.sccam_torch.c`, `hakios.torch.c`, `loader.c` | `CONFIG_TORCH` | | +| `torch_code_max` | K | `hakios.s2mpb02_torch.c` | `CONFIG_TORCH_CODE_MAX` | | +| `torch_flash_ms` | K | `hakios.s2mpb02_torch.c` | `CONFIG_TORCH_FLASH_MS` | | | `torch_max_s` | K | `hakios.s2mpb02_torch.c`, `hakios.torch.c` | `CONFIG_TORCH_MAX_S` | | | `torch_secs` | K | `hakios.s2mpb02_torch.c`, `hakios.torch.c` | `CONFIG_TORCH_SECS` | | @@ -591,7 +593,7 @@ Layers: **K** the kernel, **L** the loader, **U** user space (through `sysctl ke | `ufs_probe` | L | `loader.c` | `CONFIG_UFS_PROBE` | | | `ufs_write_guid` | L | `loader.c` | `CONFIG_UFS_WRITE_GUID` | | -## User space (156) +## User space (170) | Argument | Layer | Read by | Configuration | Set by | |---|---|---|---|---| @@ -638,6 +640,20 @@ Layers: **K** the kernel, **L** the loader, **U** user space (through `sysctl ke | `cam_rear_ap` | K | `hakios.exynos_camera_rear.c` | `CONFIG_CAM_REAR_AP` | | | `cam_rear_ois` | K | `hakios.exynos_camera_rear.c` | `CONFIG_CAM_REAR_OIS` | | | `cam_rear_ois_mode` | K | `hakios.exynos_camera_rear.c` | `CONFIG_CAM_REAR_OIS_MODE` | | +| `cam_s9_af` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_AF` | | +| `cam_s9_rear` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_REAR` | | +| `cam_s9_rear_bayer` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_REAR_BAYER` | | +| `cam_s9_rear_exp` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_REAR_EXP` | | +| `cam_s9_rear_ispdiv` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_REAR_ISPDIV` | | +| `cam_s9_rear_probe` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_REAR_PROBE` | | +| `cam_s9_rear_tp` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_REAR_TP` | | +| `cam_s9_rear_turn` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_REAR_TURN` | | +| `cam_s9_tele` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_TELE` | | +| `cam_s9_tele_bayer` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_TELE_BAYER` | | +| `cam_s9_tele_exp` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_TELE_EXP` | | +| `cam_s9_tele_probe` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_TELE_PROBE` | | +| `cam_s9_tele_tp` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_TELE_TP` | | +| `cam_s9_tele_turn` | K | `hakios.exynos_camera_star2.c` | `CONFIG_CAM_S9_TELE_TURN` | | | `camtl` | U | `inject.c` | `CONFIG_CAMTL` | | | `cc_fade` | U | `inject_sb.c` | `CONFIG_CC_FADE` | | | `cohtest` | K | `hakios.exynos_m3.c` | `CONFIG_COHTEST` | | diff --git a/docs/reference/drivers.md b/docs/reference/drivers.md index a739030bd..98a04a1c2 100644 --- a/docs/reference/drivers.md +++ b/docs/reference/drivers.md @@ -62,8 +62,8 @@ Every driver the kernel can be built with. `CONFIG_HAKIOS_=y` in a defconf |---|---|---|---|---|---|---|---|---|---|---| | **`HAKIOS_CAMERA`**
the cameras (cam=N): the sensors on the CCI bus and the ISP's interface. The sensors, their tables (fw-camera/) and the pins are the POCO X5 Pro's | `hakios.camera.c` | | `cam_after=`, `cam_fake=`, `cam_fake_fps=`, `cam_frames=`, `cam_gain=`, `cam_keep_mb=`, `cam_mode=`, `cam_pattern=` and 9 more | | | | | built | | | | | **`HAKIOS_TORCH`**
the flashlight: the flash LED module of the PM8350C (torch=1); it stays off while the battery is too warm | `hakios.torch.c` | | `torch_max_s=`, `torch_secs=` | | | | | built | | | | -| **`HAKIOS_S2MPB02_TORCH`**
the Galaxy S9's flashlight: the S2MPB02's flash block on PERIC1's HSI2C, lit by a pin (torch=N there, the same steps as the POCO's: 1 a look, 2 once, 3 for iOS through /dev/hakios-torch) | `hakios.s2mpb02_torch.c` | `HAKIOS_EXYNOS_HSI2C` | `cam_s9=`, `torch_max_s=`, `torch_secs=` | | built | | | built | built | built | | -| **`HAKIOS_EXYNOS_CAMERA`**
the Galaxy S9's front camera, stages 1 to 3 (cam_s9=N): the S5K3H1's supplies on the S2MPB02, its reset and MCLK, its model ID over I2C; the ISPPRE domain, CSIS1, its D-PHY and VC0's WDMA; one frame of the CSIS's test pattern, then one of the sensor's, 1632 x 1224 RAW10, into memory (1 a look, 2 the ID, 3 the domain, 4 the pattern, 5 the sensor's frame, auto-exposed, 6 that and then the USB console's camshot: a frame demosaiced and sent as a picture, tools/usb-shot.py --cam; 7 the sensor streaming into a ring of four frames while something reads /dev/hakios-camera (or a camshot is asked for); devices/starlte/CAMERA.md has the plan). Its memory is 32 MB of physical memory at 0xD8000000, below 4 GB and outside XNU's RAM, mapped through IOKit (hakios.exynos_camera.c). Its lens, only with cam_af=N: the AK737x motor driver on hsi2c@10900000 and the module's EEPROM (1 a look, 2 the lens held at a position, 3 autofocus by contrast, 4 that and iOS's HAKIOS_CAMERA_FOCUS; hakios.ak737x.c). The rear camera instead, only with cam_rear=1: the S5K2L3 on CSIS0, its supplies on the S2MPB03 and the S2MPB02, 2016 x 1512 RAW10 at 30 frames a second through the same steps; its lens (the same cam_af), its stabiliser (cam_rear_ois=N) and its two-stop iris (cam_rear_ap=N); hakios.exynos_camera_rear.c | `hakios.exynos_camera.c`
`hakios.ak737x.c`
`hakios.exynos_camera_rear.c` | `HAKIOS_EXYNOS_HSI2C` | `cam_af=`, `cam_af_coarse=`, `cam_af_drop=`, `cam_af_fine=`, `cam_af_hi=`, `cam_af_lo=`, `cam_af_pos=`, `cam_af_settle=` and 30 more | | built | | | | built | built | | +| **`HAKIOS_S2MPB02_TORCH`**
the Galaxy S9's flashlight: the S2MPB02's flash block on PERIC1's HSI2C, lit by a pin (torch=N there, the same steps as the POCO's: 1 a look, 2 once, 3 for iOS through /dev/hakios-torch) | `hakios.s2mpb02_torch.c` | `HAKIOS_EXYNOS_HSI2C` | `cam_s9=`, `torch_code_max=`, `torch_flash_ms=`, `torch_max_s=`, `torch_secs=` | | built | | | built | built | built | | +| **`HAKIOS_EXYNOS_CAMERA`**
the Galaxy S9's front camera, stages 1 to 3 (cam_s9=N): the S5K3H1's supplies on the S2MPB02, its reset and MCLK, its model ID over I2C; the ISPPRE domain, CSIS1, its D-PHY and VC0's WDMA; one frame of the CSIS's test pattern, then one of the sensor's, 1632 x 1224 RAW10, into memory (1 a look, 2 the ID, 3 the domain, 4 the pattern, 5 the sensor's frame, auto-exposed, 6 that and then the USB console's camshot: a frame demosaiced and sent as a picture, tools/usb-shot.py --cam; 7 the sensor streaming into a ring of four frames while something reads /dev/hakios-camera (or a camshot is asked for); devices/starlte/CAMERA.md has the plan). Its memory is 32 MB of physical memory at 0xD8000000, below 4 GB and outside XNU's RAM, mapped through IOKit (hakios.exynos_camera.c). Its lens, only with cam_af=N: the AK737x motor driver on hsi2c@10900000 and the module's EEPROM (1 a look, 2 the lens held at a position, 3 autofocus by contrast, 4 that and iOS's HAKIOS_CAMERA_FOCUS; hakios.ak737x.c). The rear camera instead, only with cam_rear=1: the S5K2L3 on CSIS0, its supplies on the S2MPB03 and the S2MPB02, 2016 x 1512 RAW10 at 30 frames a second through the same steps; its lens (the same cam_af), its stabiliser (cam_rear_ois=N) and its two-stop iris (cam_rear_ap=N); hakios.exynos_camera_rear.c For the Galaxy S9+ (star2lte) the same symbol builds Anan Jaser's driver instead, hakios.exynos_camera_star2.c: its front, rear (cam_s9_rear) and telephoto (cam_s9_tele) cameras; the files above compile to nothing there | `hakios.exynos_camera.c`
`hakios.ak737x.c`
`hakios.exynos_camera_rear.c`
`hakios.exynos_camera_star2.c` | `HAKIOS_EXYNOS_HSI2C` | `cam_af=`, `cam_af_coarse=`, `cam_af_drop=`, `cam_af_fine=`, `cam_af_hi=`, `cam_af_lo=`, `cam_af_pos=`, `cam_af_settle=` and 44 more | | built | | | | built | built | | | **`HAKIOS_EXYNOS_IRIS`**
the Galaxy S9's iris camera (iris_s9=N): the S5K5F1 infrared sensor on CSIS3, its supplies (the S2MPS18's LDO39-41 through ACPM), its reset, iris_en and MCLK2; the IR LED's registers on the S2MPB02 set to the kernel's own values only; one 2400 x 2400 RAW8 frame into memory, bounded in time (1 a look, 2 the ID, 3 the LED's registers, 4 a frame, 5 that and then the USB console's irshot). Not with cam_s9= or torch= in the same run. devices/starlte/CAMERA.md, "The iris camera" | `hakios.exynos_iris.c` | `HAKIOS_EXYNOS_HSI2C`, `HAKIOS_EXYNOS_ACPM` | `cam_s9=`, `fp_s9=`, `iris_s9=`, `iris_s9_exp=`, `iris_s9_gain=`, `iris_s9_ispdiv=`, `iris_s9_ms=`, `iris_s9_smc=` | | | | | | | built | | ## core