diff --git a/MAINTAINERS b/MAINTAINERS index c2e1de0244904..4a88bb609e9c4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2802,6 +2802,8 @@ F: tools/asahi/apple-pwm-pm-tests.py F: drivers/reset/reset-apple-cio.c F: drivers/rtc/rtc-macsmc.c F: drivers/soc/apple/* +F: tools/asahi/isp-preview-flow-tests.py +F: tools/asahi/isp-preview-tests.py F: tools/asahi/m3-uapi-* F: drivers/soc/apple/clpc.kunitconfig F: tools/asahi/pmp-bootargs-tests.rs diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index bbbb58d5fb954..c2fb237789403 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -912,7 +912,9 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) return err; - err = isp_cmd_ch_mbnr_enable(isp, ch, 0, ISP_MBNR_MODE_ENABLE, 1); + err = isp_cmd_ch_mbnr_enable(isp, ch, 0, + isp->preview_26 ? ISP_MBNR_MODE_DISABLE : + ISP_MBNR_MODE_ENABLE, 1); if (err) return err; @@ -946,7 +948,11 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) return err; } - err = isp_ch_configure_temporal_filter(isp, ch); + /* Preview has no consumer for temporal history on this firmware. */ + if (isp->preview_26) + err = isp_cmd_apple_ch_temporal_filter_disable(isp, ch); + else + err = isp_ch_configure_temporal_filter(isp, ch); if (err) return err; diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index a1a79ad97b4a7..f298b5d24db72 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -534,6 +534,11 @@ static int apple_isp_init_presets(struct apple_isp *isp) return 0; } +/* Preview only; processing and image quality need separate qualification. */ +static bool preview_26 = true; +module_param(preview_26, bool, 0400); +MODULE_PARM_DESC(preview_26, "Enable J613 preview on ISP firmware 26.6.2 (default: true; false disables)"); + static const char * isp_fw2str(enum isp_firmware_version version) { switch (version) { @@ -545,6 +550,8 @@ static const char * isp_fw2str(enum isp_firmware_version version) return "13.5"; case ISP_FIRMWARE_V_14_7: return "14.7"; + case ISP_FIRMWARE_V_26_6_2: + return "26.6.2"; default: return "unknown"; } @@ -574,6 +581,8 @@ static enum isp_firmware_version isp_read_fw_version(struct device *dev, /* not provided, see isp_check_firmware_version() */ break; case 3: + if (ver[0] == 26 && ver[1] == 6 && ver[2] == 2) + return ISP_FIRMWARE_V_26_6_2; if (ver[0] == 12 && ver[1] == 3 && ver[2] <= 1) return ISP_FIRMWARE_V_12_3; else if (ver[0] == 12 && ver[1] == 4 && ver[2] == 0) @@ -624,6 +633,18 @@ isp_check_firmware_version(struct apple_isp *isp) return compat; } +static bool isp_preview26_admitted(struct device *dev, + enum isp_firmware_version compat) +{ + if (compat != ISP_FIRMWARE_V_26_6_2 || !preview_26 || + !of_machine_is_compatible("apple,j613")) + return false; + if (!of_device_is_compatible(dev->of_node, "apple,t8122-isp")) + return false; + return isp_read_fw_version(dev, "apple,firmware-version") == + ISP_FIRMWARE_V_26_6_2; +} + /* After a system sleep, see apple_isp_resume(). */ static void apple_isp_resume_work(struct work_struct *work) { @@ -678,6 +699,17 @@ static int apple_isp_probe(struct platform_device *pdev) * with unknown firmware. */ isp->fw_compat = isp_check_firmware_version(isp); + if (isp->hw->fw_abi != ISP_FW_ABI_H17 && + (isp->fw_compat == ISP_FIRMWARE_V_26_6_2 || + isp_read_fw_version(dev, "apple,firmware-version") == + ISP_FIRMWARE_V_26_6_2)) { + if (!isp_preview26_admitted(dev, isp->fw_compat)) { + err = dev_err_probe(dev, -ENODEV, + "26.6.2 preview requires J613/T8122; preview_26=0 disables it\n"); + goto free_isp; + } + isp->preview_26 = true; + } err = of_property_read_u32(dev->of_node, "apple,platform-id", &isp->platform_id); diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index fda0e754e7110..3551018baf174 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -81,6 +81,7 @@ enum isp_firmware_version { ISP_FIRMWARE_V_12_4, ISP_FIRMWARE_V_13_5, ISP_FIRMWARE_V_14_7, + ISP_FIRMWARE_V_26_6_2, }; struct isp_surf { @@ -354,6 +355,7 @@ struct apple_isp { struct device *dev; const struct apple_isp_hw *hw; enum isp_firmware_version fw_compat; + bool preview_26; u32 platform_id; u32 temporal_filter; u32 cam_connections_scheme; diff --git a/tools/asahi/isp-preview-flow-tests.py b/tools/asahi/isp-preview-flow-tests.py new file mode 100644 index 0000000000000..98e7d228f3ff1 --- /dev/null +++ b/tools/asahi/isp-preview-flow-tests.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Observe the production capture configuration through fake command boundaries.""" +import importlib.util +from pathlib import Path +import re, subprocess, tempfile +root=Path(__file__).resolve().parents[2] +spec=importlib.util.spec_from_file_location('isp',root/'tools/asahi/isp-tests.py') +mod=importlib.util.module_from_spec(spec);spec.loader.exec_module(mod) +body=mod.function((root/'drivers/media/platform/apple/isp/isp-cam.c').read_text(),'isp_ch_configure_capture') +names=sorted(set(re.findall(r'\b(isp_(?:cmd_\w+|ch_configure_frame_rate|ch_configure_pools|ch_load_setfile|ch_configure_power_line))\(',body))) +code=''' +#include +#include +#include +typedef unsigned int u32; +struct coord {int x,y;}; +struct preset {int index;struct coord crop_offset,crop_size,output_dim;}; +struct isp_format {struct preset *preset;int strides[2];}; +struct hw {int fw_abi;bool lpdp;}; +struct apple_isp {struct hw *hw;bool preview_26;int temporal_filter;}; +static struct preset preset; +static struct isp_format fmt={.preset=&preset}; +static struct isp_format *isp_get_format(struct apple_isp *i,u32 ch){return &fmt;} +#define ISP_FW_ABI_H17 1 +#define ISP_MBNR_MODE_DISABLE 0 +#define ISP_MBNR_MODE_ENABLE 1 +#define CHECK(x) do {if(!(x)){fprintf(stderr,"failed %d\\n",__LINE__);return 1;}}while(0) +static int mbnr_mode,temporal_start,history,enable,disable,pools,fail_disable; +''' +for constant in sorted(set(re.findall(r'\bCISP_\w+',body))):code+=f'#define {constant} 0\n' +for name in names: + if name=='isp_cmd_ch_mbnr_enable': + code+='static int '+name+'(struct apple_isp *i,u32 ch,int a,int mode,int b){mbnr_mode=mode;return 0;}\n';continue + effect={'isp_cmd_apple_ch_temporal_filter_start':'temporal_start++;','isp_cmd_apple_ch_motion_history_start':'history++;','isp_cmd_apple_ch_temporal_filter_enable':'enable++;','isp_cmd_apple_ch_temporal_filter_disable':'disable++;if(fail_disable)return -EIO;','isp_ch_configure_pools':'pools++;'}.get(name,'') + code+='static int '+name+'(struct apple_isp *i,...){'+effect+'return 0;}\n' +code += 'static bool isp_is_p010(struct apple_isp *i){return false;}\n' +code += 'static int isp_ch_configure_temporal_filter(struct apple_isp *i,u32 ch){temporal_start++;history++;enable++;return 0;}\n' +code+=body+r''' +int main(void){ + struct hw hw={0};struct apple_isp isp={.hw=&hw}; + for(int profile=0;profile<3;profile++){ + temporal_start=history=enable=disable=pools=0;mbnr_mode=-1; + isp.preview_26=profile==1;hw.fw_abi=profile==2; + CHECK(!isp_ch_configure_capture(&isp,0)); + CHECK(mbnr_mode==(profile==1?0:1)); + CHECK(disable==(profile==1)); + CHECK(temporal_start==(profile!=1)); + CHECK(history==(profile!=1));CHECK(enable==(profile!=1));CHECK(pools==1); + } + isp.preview_26=true;hw.fw_abi=0;pools=0;fail_disable=1; + CHECK(isp_ch_configure_capture(&isp,0)==-EIO);CHECK(pools==0); + puts("PASS: explicit MBNR disable, no temporal/history startup, legacy/H17 unchanged, disable failure propagation");return 0; +} +''' +with tempfile.TemporaryDirectory() as d: + p=Path(d);(p/'test.c').write_text(code) + subprocess.run(['clang','-std=gnu11','-Werror','-fsanitize=address,undefined',str(p/'test.c'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/asahi/isp-preview-tests.py b/tools/asahi/isp-preview-tests.py new file mode 100644 index 0000000000000..06c9d328e60bf --- /dev/null +++ b/tools/asahi/isp-preview-tests.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Exercise production OF parsing/admission with synthetic device descriptions.""" +import importlib.util +from pathlib import Path +import subprocess, sys, tempfile +root = Path(__file__).resolve().parents[2] +spec = importlib.util.spec_from_file_location('isp', root/'tools/asahi/isp-tests.py') +mod = importlib.util.module_from_spec(spec) +spec.loader.exec_module(mod) +source = (root/'drivers/media/platform/apple/isp/isp-drv.c').read_text() +header = (root/'drivers/media/platform/apple/isp/isp-drv.h').read_text() +a=header.index('enum isp_firmware_version {'); b=header.index('};',a)+2 +preamble = r''' +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; +struct device {void *of_node;}; +static bool board, soc; +static int cells=3, bytes=12; +static u32 values[5]={26,6,2}; +#define ISP_FW_VERSION_MIN_LEN 3 +#define ISP_FW_VERSION_MAX_LEN 5 +#define dev_warn(...) ((void)0) +static void *of_find_property(void *n,const char *p,int *len) {*len=bytes; return bytes<0?NULL:values;} +static int of_property_read_variable_u32_array(void *n,const char *p,u32 *out,int min,int max) { + if(cellsmax) return -EINVAL; + memcpy(out,values,cells*sizeof(u32));return cells; +} +static bool of_machine_is_compatible(const char *s) {return board;} +static bool of_device_is_compatible(void *n,const char *s) {return soc;} +#define CHECK(x) do {if (!(x)) {fprintf(stderr,"failed line %d\n",__LINE__);return 1;}} while(0) +''' +main = r''' +int main(void) { + struct device dev={0}; + board=soc=true; + CHECK(preview_26); + CHECK(isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); + preview_26=false; + CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); + for(int flags=0;flags<8;flags++) { + preview_26=flags&1;board=flags&2;soc=flags&4; + CHECK(isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)==(flags==7)); + } + preview_26=board=soc=true; + CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_14_7)); + CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_UNKNOWN)); + for(int field=0;field<3;field++) { + values[field]++; CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2));values[field]--; + } + for(int n=-1;n<=6;n++) { + if(n==3)continue; + cells=n;bytes=n*4; + CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); + } + cells=3;bytes=13;CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); + puts("PASS: production default, opt-out/board/SoC combinations, compat mismatch, version mismatch, missing/truncated/extended cells"); + return 0; +} +''' +default=source[source.index('static bool preview_26'):source.index(';',source.index('static bool preview_26'))+1] +code=preamble+default+'\n'+header[a:b]+'\n'+mod.function(source,'isp_read_fw_version')+mod.function(source,'isp_preview26_admitted')+main +with tempfile.TemporaryDirectory() as d: + p=Path(d);(p/'test.c').write_text(code) + subprocess.run(['clang','-std=gnu11','-Werror','-Wno-unused-function','-fsanitize=address,undefined',str(p/'test.c'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index c12950590b509..4f81e8570f76d 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -154,6 +154,8 @@ def mutations(flag, names, base, expect=KILL): 'g17-t6041-initdata-tests.py': [([], PASS)], 'g17-uninstalled-cleanup-tests.py': [([], PASS)], 'g17-unpublished-tests.py': [([], PASS)], + 'isp-preview-flow-tests.py': [([], PASS)], + 'isp-preview-tests.py': [([], PASS)], 'isp-tests.py': [(['--suite', suite] + out(), PASS) for suite in ['session', 'buffers', 'receive', 'returns', 'queue']] + [(['--suite', suite, '--control', control] + out(), KILL) for suite, controls in [('session', ['send', 'overwrite', 'stop', 'reset']), ('buffers', ['rollback']), ('receive', ['drain']), ('returns', ['address', 'pool', 'duplicate', 'snapshot']), ('queue', ['queue-error'])] for control in controls], 'mt7932-association-events-tests.py': [(out(), PASS)], 'mt7932-connect-result-tests.py': [(out('--output'), PASS)],