Skip to content

All other than GPU - #75

Draft
akku1139 wants to merge 1296 commits into
blade/v7.1from
dev/v7.1/mt6589-drm-ox-alpha
Draft

akku1139 wants to merge 1296 commits into
blade/v7.1from
dev/v7.1/mt6589-drm-ox-alpha

Conversation

@akku1139

@akku1139 akku1139 commented Aug 24, 2026 •

Copy link
Copy Markdown
Member

AI Slop, untested

TODO

  • fix TOP_CKDIV1
  • fix freqhopping (done?)
  • use TOP_CKDIV1
  • fix TVDPLL_MODE (done?)
  • drop mt6589 musb fifo table
  • ISP power domain SRAM
  • re-introduce mt6320-pwrc?
  • Stereo earphones play in mono

note:

AI models

  • Ox Alpha
  • Claude Sonnet 4.6
  • GPT-5.6 Luna
  • Space Bunny Alpha
  • GPT-6 Luna
  • DeepSeek V4.1 Flash

@akku1139
akku1139 changed the base branch from dev/v7.1/mt6589-drm to blade/v7.1 August 29, 2026 08:34
@akku1139

akku1139 commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

starting claude sonnet 4.6 review

@akku1139

akku1139 commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

調査レポート(Claude による aquaris-5 ダウンストリームとの比較)

✅ 正しかった事項

1. LARB ポートオフセット(include/dt-bindings/memory/mt6589-larb-port.h)

  • {0, 10, 17, 29, 43, 53} → aquaris-5 の m4u_port.h と完全一致
  • aquaris-5 の m4u.c 内 smi_port0_in_larbx[] = {0, 10, 17, 29, 44, 56} は downstream 側のバグ(LARB4/5 のオフセットが誤り)

2. M4U レジスタオフセット(drivers/iommu/mtk_iommu_v1.c)

  • REG_MMU_CTRL_REG=0x210, REG_MMU_IVRP_PADDR=0x214, REG_MMU_INT_CONTROL=0x220, REG_MMU_FAULT_ST=0x224, REG_MMU_FAULT_VA=0x228, REG_MMU_INVLD_PA=0x22C, REG_MMU_INT_ID=0x388 → 全て正しい
  • F_INT_CLR_BIT=BIT(12) → 正しい

3. MT6589 DTS の M4U アドレスマップ(arch/arm/boot/dts/mediatek/mt6589.dtsi)

  • global=0x10205000, m4u0=0x10205200, m4u1=0x10205800, l2cache=0x10205100 → aquaris-5 の物理アドレスから逆算して正しい
  • aquaris-5: M4U_BASEg=0xf0205000, M4U_BASE0=0xf0205200, M4U_BASE1=0xf0205800, M4U_L2_BASE=0xf0205100

4. SMI bus_sel 値(drivers/memory/mtk-smi.c)

  • bus_sel = 0x110(LARB2→MMU1, LARB4→MMU1、他→MMU0)
  • aquaris-5 の m4u_index_of_larb[] = {0, 0, 1, 0, 1} から計算: F_SMI_BUS_SEL_larb2(1) | F_SMI_BUS_SEL_larb4(1) = 0x110 と完全一致

5. mt6589 int_en_mask の選択

  • 7 つの割り込みソースのみ有効化(F_INT_MISS_DMA_FIFO_OVERFLOW を除外)
  • aquaris-5 の downstream と一致(REG_MMU_INT_CONTROL への書き込み値から確認)

6. YUV2RGB 係数行列(drivers/gpu/drm/mediatek/mtk_disp_ovl.c)

  • mt6589_yuv2rgb_coef[5][3] → aquaris-5 の ddp_matrix_para.h の yuv2rgb_601_0_0(full-range BT.601)と完全一致

7. mt6589_get_fault_larb_port の LARB ID デコード

  • 6 - ((int_id >> 12) & 0x7) → aquaris-5 の larbID = 6 - F_INT_ID_TF_LARB_ID(port_regval) と一致
  • F_INT_ID_TF_PORT_ID = bits[11:8], F_INT_ID_TF_LARB_ID = bits[14:12]

8. DSI MEM_CONTI レジスタ(drivers/gpu/drm/mediatek/mtk_dsi.c)

  • DSI_MEM_CONTI = 0x90, DSI_WMEM_CONTI = 0x3c → aquaris-5 の dsi_reg.h / dsi_drv.h と完全一致

9. DSI PHY PLL コンフィグテーブル(drivers/phy/mediatek/phy-mtk-mipi-dsi-mt6589.c)

  • 50 エントリの pll_config[] テーブルは aquaris-5 の dsi_drv.c と完全一致
  • PLL_CON0 のフィールド配置(rg_br<<30 | rg_bc<<28 | rg_bp<<24 | rg_bir<<20 | rg_bic<<16 | txdiv1<<14 | txdiv0<<12 | fbk_sel<<10 | pre_div<<8 | fbk_div<<1)も一致

❌ 発見したバグと修正

phy: mediatek: mt6589 mipi dsi: fix PLL rate calculation(commit 3272d88)

mt6589_pll_rate() のレーンデータレート計算式に *2 が欠落していた。

MIPI DSI は DDR(差動)シグナリングを使用するため、クロックエッジごとに 1 ビットを伝送する。したがって実際のビットレート(lane data rate)は PLL 出力クロックの 2 倍になる。

  • aquaris-5 downstream: cycle_time = 8000 * div1*div2*pre / (26 * (fbk_div+1) * fbk_sel * 2) → ビットレート = 分母/分子 = 26*(fbk_div+1)*fbk_sel*2 / (div1*div2*pre) Mbps
  • 誤った式: mbps = 26 * fbk * fbk_sel / (div1 * div2 * pre) (half-rate を返す)
  • 正しい式: mbps = 26 * fbk * fbk_sel * 2 / (div1 * div2 * pre)

影響: mtk_dsi.c は data_rate = pixelclock * bpp / lanes(フルビットレート)を PHY に渡す。PHY は最も近いテーブルエントリを選択するが、テーブルの値が半分だと全エントリが本来の 2 倍離れた周波数を指すことになり、PLL が誤った周波数にプログラムされる。例えば 720p/24bpp/4-lane で必要な ~445 Mbps に対し、テーブル上の最近傍は 416 MHz(entry 1)となり、実際のビットレートは 832 Mbps になる(2 倍オーバークロック)。

@akku1139

akku1139 commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

追加調査レポート(TOP_CKDIV1 / FHCTL)

❌ 発見・修正したバグ(2件)


clk: mediatek: mt6589: fix armdiv1 table div values and comment(commit 10c409fc04)

TOP_CKDIV1レジスタのビットフィールドは「bits[2:0] = numerator-1」ではなく「bits[2:0] = numerator直値」:

01000=4/4  01001=3/4  01010=2/4  01011=1/4
10000=5/5  10001=4/5  10010=3/5  10011=2/5  10100=1/5
11000=6/6  11001=5/6  11010=4/6  11011=3/6  11100=2/6  11101=1/6

clk_div_table は整数除数しか扱えないため、分数 n/d が非整数になるエントリは ceil(d/n) で近似(CCFが実際の出力を超えるクロックをプログラムしないようにするため)。

修正した .div 値 8 箇所:

val 分数 修正前 修正後
0x09 3/4 div=4 div=2(ceil(4/3)=2, 近似)
0x11 4/5 div=5 div=2(ceil(5/4)=2, 近似)
0x12 3/5 div=5 div=2(ceil(5/3)=2, 近似)
0x13 2/5 div=5 div=3(ceil(5/2)=3, 近似)
0x19 5/6 div=6 div=2(ceil(6/5)=2, 近似)
0x1a 4/6 div=6 div=2(ceil(6/4)=2, 近似)
0x1b 3/6 div=6 div=2(exact: 6/3=2)
0x1c 2/6 div=6 div=3(exact: 6/2=3)

なお、この分数分周(n/d 形式)はCCFの clk_div_table では正確に表現できない構造のため、ダウンストリームのDVFSドライバが実際に使用するのは 0x08(4/4=bypass)と 0x0a(2/4=÷2)の2値のみで、これらは exact なので実害なし。


clk: mediatek: mt6589: fix FHCTL channel mapping for MSDCPLL/TVDPLL/LVDSPLL(commit b4cc6fadfb)

MT6589 FHCTLはCH2をMEMPLL(SDM PLL非対応、未登録)に割り当てているため、MSDCPLL/TVDPLL/LVDSPLLはCH3/CH4/CH5から始まる。前のコードはenumを0から連番で割り当てており、チャンネルが1個ずつずれていた。

PLL fhx_offset(修正前→後) fh_id(修正前→後) 影響
MSDCPLL 0x6c → 0x7c 2 → 3 MEMPLLのCHを誤使用
TVDPLL 0x7c → 0x8c 3 → 4 MSDCPLLのCHを誤使用
LVDSPLL 0x8c → 0x9c 4 → 5 TVDPLLのCHを誤使用

誤ったfhx_offsetは正しいPLLでなくMEMPLL/MSDCPLL/TVDPLLのFHCTLレジスタに読み書きする原因となり、誤ったfh_idはPLL_HP_CON0で誤ったチャンネルを有効化/無効化する原因となっていた。

出典: aquaris-5 mt_freqhopping.h(FH_MSDC_PLL=3, FH_TVD_PLL=4, FH_LVDS_PLL=5)および mt_fhreg.h(FHCTL0_CFG=0x4c, FHCTL3_CFG=0x7c, FHCTL4_CFG=0x8c, FHCTL5_CFG=0x9c)。


✅ 追加で検証した正しかった事項

13. FHCTL bit fields(clk-mt6589-apmixedsys.c)

  • sfstrx_en=BIT(2), frddsx_en=BIT(1), fhctlx_en=BIT(0) → aquaris-5 mt_fhreg.h の FH_SFSTRX_EN/FH_FRDDSX_EN/FH_FHCTLX_EN と一致
  • tgl_org=BIT(31), dvfs_tri=BIT(31) → FH_FHCTLX_PLL_TGL_ORG=BIT(31) と一致
  • pcwchg=BIT(31) → preloaderの ARMPLL_CON1 書き込みが 0x8xxxxxxx(BIT(31)セット)であることから確認

14. FHCTL dt_val/df_val(clk-mt6589-apmixedsys.c)

  • dt_val=0x0, df_val=0x9 → aquaris-5 の全SSC設定テーブル(mt_ssc_armpll_setting[]等)で dt=0, df=9 と一致

15. FHCTL slope0/1値

  • slope0_value=slope1_value=0x6003c97 → mt8173(FHCTL_PLLFH_V3の標準実装)と一致

16. msk_frddsx_dys/dts

  • msk_frddsx_dys=GENMASK(23,20), msk_frddsx_dts=GENMASK(19,16) → aquaris-5 の FH_FRDDSX_DYS=0xF<<20, FH_FRDDSX_DTS=0xF<<16 と一致

17. apmixedsys物理アドレス

  • 0x10209000(DTS)→ aquaris-5 preloaderの AP_PLL_CON0=0x10209000 と一致
  • ARMPLL_CON0=0x10209200, ARMPLL_CON1=0x10209204 等も一致

18. PLL fmax / en_mask / pcwbits

  • ARMPLL: pcwbits=21, pd_shift=24, pcw_shift=0 → preloaderの ARMPLL_CON1 書き込み(21ビットPCW)と一致
  • MAINPLL: pcwbits=21, pd_shift=6, pcw_shift=0, en_mask=0xf0000001 → MAINPLL_CON1 書き込み値 0x800F8000 から確認(BIT(31)=PCW_CHG, bits[20:0]=PCW)

@akku1139

akku1139 commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

最終調査レポート(全ドライバ網羅的検証完了)

✅ 追加で検証した正しかった事項

19. mutex モジュール ID(drivers/soc/mediatek/mtk-mutex.c)

  • ROT=0, SCL=1, OVL=2, COLOR=3, TDSHP=4, WDMA0=5, WDMA1=6, RDMA0=7, RDMA1=8, BLS=9, GAMMA=10
  • aquaris-5 ddp_path.c のインラインコメント(//rdma0=7、//wdma1=6、//ovl=2, rdma0=7, Color=3, TDSHP=4, BLS=9)から全て一致

20. mmsys ルーティングレジスタ(drivers/soc/mediatek/mt6589-dispsys.h)

  • SCL_MOUT_EN=0x020, OVL_MOUT_EN=0x024, COLOR_MOUT_EN=0x028, RDMA0_OUT_SEL=0x034, RDMA1_OUT_SEL=0x038, WDMA0_SEL=0x040, COLOR_SEL=0x04c, BLS_SEL=0x054, DBI_SEL=0x058, DPI0_SEL=0x05c
  • aquaris-5 ddp_reg.h の DISP_REG_CONFIG_* と全て一致
  • ビット/値割り当て(OVL_MOUT BIT(0)=WDMA1/BIT(2)=COLOR, COLOR_MOUT BIT(3)=BLS など)も正しい

21. BLS/PWM ドライバ(drivers/pwm/pwm-mt6589-disp.c)

  • BLS_PWM_CON=0x0090, BLS_PWM_DUTY=0x0080, BLS_PWM_DUTY_GAIN=0x0084, PWM_H_DURATION=0x0094 など全レジスタオフセット → aquaris-5 ddp_reg.h と一致
  • probe() にて BLS_PWM_CON = 0x00050000 | 0x24(CKSEL=5, div=0x24)を設定 → ddp_bls.c(DISP_REG_SET(BLS_PWM_CON, 0x00050000 | gPWMDiv))と一致
  • BLS_PWM_DUTY_GAIN = 0x100(unity gain)も probe() で設定済み → ddp_bls.c の 0x100 と一致
  • BLS_EN_PWM_ONLY = BIT(31) → downstream の 0x80000000 と一致
  • BLS_PWM_DUTY フォーマット PWM_DUTY_MIN_LEVEL(BIT(19)) | level → downstream の (PWM_LOW_LIMIT<<19) | pwmDuty(PWM_LOW_LIMIT=1)と一致
  • dither 7値(BLS_DITHER(0/6/13/14/15/16/17))→ ddp_bls.c と完全一致

22. topckgen クロック MUX(drivers/clk/mediatek/clk-mt6589-topckgen.c)

  • CLK_CFG_0〜8 の全オフセット(0x40/0x44/.../0x64)→ aquaris-5 mt_clkmgr.h の TOPRGU_BASE+0x140〜0x164 から逆算して正しい
  • CLK_CFG_8=0x64(0x160 は CLK_MISC_CFG_2 でスキップ)も正しい
  • CLK_CFG_5 (0x54) に mux なし → downstream も該当レジスタに mux 未定義で一致
  • 全 MUX のビット幅・シフト・ゲートビット → aquaris-5 mt_clkmgr.c の sel_mask/offset/pdn_mask と全て一致

23. infracfg ゲートクロック(drivers/clk/mediatek/clk-mt6589-infracfg.c)

  • 全 19 クロック(DBGCLK=0〜PMICWRAP=23)→ aquaris-5 mt_clkmgr.h の MT_CG_INFRA_*(= CG_INFRA_FROM 64 からのオフセット)と全て一致

24. pericfg ゲートクロック(drivers/clk/mediatek/clk-mt6589-pericfg.c)

  • PERI0 全 32 クロック(NFI=0〜I2C5=31)→ MT_CG_PERI0_* と全て一致
  • PERI1 全 5 クロック(I2C6=0, PWRAP=1, AUXADC=2, SPI1=3, FHCTL=4)→ MT_CG_PERI1_* と全て一致

25. disp クロックゲート(drivers/clk/mediatek/clk-mt6589-disp.c)

  • DISP0 25 クロック(LARB2_SMI=0〜G2D_SMI=24)、DISP1 10 クロック(DBI_ENGINE=0〜SLCD=9)→ mt_clkmgr.h の MT_CG_DISP0/1_* enum から逆算した値と全て一致
  • DISP_CG_CON0=0x0100, CON1=0x0110 → mt_clkmgr.h の DISPSYS_BASE+0x0100/0x0110 と一致

26. 全ディスプレイブロック物理アドレス(DTS)

  • OVL=0x14003000, RDMA0=0x14006000, RDMA1=0x14007000, DSI=0x1400d000, MUTEX=0x14011000 → aquaris-5 mt_reg_base.h(仮想→物理 -0xE0000000)と全て一致

27. 全ディスプレイ GIC SPI IRQ 番号(DTS)

  • MUTEX=160, OVL=163, RDMA0=166, RDMA1=167, COLOR=169, TDSHP=170, DSI=172 → aquaris-5 mt_irq.h(GIC_PRIVATE_SIGNALS=32 オフセット)から逆算して全て一致

28. M4U / LARB / SMI アドレス(DTS)

  • M4U IRQ: GIC_SPI 105/106/107/108 → m4u_priv.h(105+32=137 等)から一致
  • LARB0〜4 全アドレス → LARB*_BASE(-0xE0000000)と一致
  • SMI_COMMON=0x10202000 → mt_reg_base.h の SMI_COMMON=0xF0202000 から一致

29. AFE レジスタ / ビットフィールド(sound/soc/mediatek/mt6572/mt6572-afe-pcm.c)

  • 全レジスタオフセット(AFE_DAC_CON0=0x10, CONN1=0x24, DL1_BASE=0x40, IRQ_MCU_CON=0x3A0 など)→ AudDrv_Afe.h と全て一致
  • AFE_IRQ_MCU_CON_IRQ1_RATE = GENMASK(7,4) → downstream の (CON>>4)&0xf と一致

総括

合計 3 件のバグを発見・修正・プッシュ済み:

コミット 内容
3272d88a7f phy: mt6589 mipi dsi: fix PLL rate calculation(DDR×2係数欠落、PHY が半分の周波数でプログラムされ約2倍オーバークロック)
10c409fc04 clk: mt6589: fix armdiv1 table(TOP_CKDIV1の分周値8箇所誤り、非整数分周の div 値が分母のまま放置)
b4cc6fadfb clk: mt6589: fix FHCTL channel mapping(MEMPLLスキップ漏れでMSDCPLL/TVDPLL/LVDSPLLのfhx_offsetとfh_idが1チャンネルずつずれ)

それ以外の全レジスタ値・アドレス・IRQ番号・クロックゲートビット・ルーティング設定は全て aquaris-5 ダウンストリームと一致することを確認。

@akku1139

akku1139 commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

追加調査レポート(MT6320/MT6628/残ドライバ完全網羅)

❌ 発見・修正したバグ(1件)

drm/mediatek: mt6589: fix TDSHP DTDS_CONFIG register offset and R2Y/Y2R enable(commit 2c598e4a5a)

aquaris-5 ddp_tdshp.h / ddp_tdshp.c との比較で2つのバグを発見:

  1. DISP_REG_TDSHP_CFG = 0x0350 のコメントが「DTDS_CONFIG」と誤記されていたが、実際の DTDS_CONFIG は 0xf10。0x0350 は PBC/misc バイパスレジスタで、downstream は 0 を書く(我々は BIT(6)=0x40 を書いていた)。

  2. R2Y/Y2R カラーマトリクスラッパーが有効化されていなかった。downstream は DTDS_CONFIG (0xf10) に 0x6(BIT(2)|BIT(1) = R2Y_EN|Y2R_EN)を書くが、我々のドライバは 0xf10 に一切書き込まない実装になっていた。その結果 YUV ソース使用時のカラー出力が壊れていた。

修正内容:

  • 0x0350 を DISP_REG_TDSHP_PBC_BYPASS にリネームして値を 0 に修正
  • DISP_REG_TDSHP_DTDS_CONFIG = 0xf10(DISP_TDSHP_R2Y_EN=BIT(2), DISP_TDSHP_Y2R_EN=BIT(1))を新規追加し 0x6 を書き込む

行列係数テーブル(in_matrix[] 0xf50 / out_matrix[] 0xf90)、TDS_HSYNC_WIDTH (0xf24 = 0x40)、SIZE レジスタ(0x0f40/0x0f44)は全て正しかった。


✅ 追加で検証した正しかった事項

36. FM BOP opcodes(drivers/net/wireless/mediatek/mt6628/mtk-fm.c)

  • FM_BOP_WRITE=0x80, UDELAY=0x81, RD_UNTIL=0x82, MODIFY=0x83, MSLEEP=0x84 → aquaris-5 mt6628_fm_cmd.h(FM_BOP_BASE=0x80+0x00〜0x04)と完全一致
  • STP_TASK_FM=1 → FM_TASK_INDX=1(stp_exp.h)確認
  • FM_TASK_COMMAND_PKT_TYPE=0x01、FM_ENABLE_OPCODE=0x07 → fm_link.h と一致
  • MTK_SDIO_CTDR=0x18 → CTDR=0x0018(stp_sdio.h)確認

37. gnss-mt6628.c — GNSS ドライバ

  • STP_TASK_GPS=2 → GPS_TASK_INDX=2(stp_exp.h)確認
  • MTK_SDIO_CRDR=0x1c → CRDR=0x001c(stp_sdio.h)確認
  • STP フレームフォーマット(0x80, task<<4|len_hi, len_lo)→ stp_core.c と一致

38. btmtksdio.c — Bluetooth SDIO

  • SDIO VID=0x037A、PID=0x6628 → aquaris-5 hif_sdio.c(SDIO_DEVICE(0x037A, 0x6628))と完全一致

39. MT6320 AUXADC(drivers/iio/adc/mt6320-auxadc.c)

  • ADC0=0x0512(2バイトストライド)、CON1=0x0542、CON2=0x0544 → upmu_hw.h と完全一致
  • READY=BIT(12)、IDLE=BIT(0)、DATA=GENMASK(11,0) ✓
  • チャンネル(CH0=BATSNS, CH1=ISENSE, CH2=VCDT, CH3=BAT_TEMP, CH4=CHIP_TEMP, CH5=ACCDET, CH7=THUMP)→ pmic_mt6320.c から確認

40. MT6320 EFUSE(drivers/nvmem/mt6320-efuse.c)

  • CON0=0x018E, CON1=0x0190, CON2=0x0192, CON4=0x0196, CON6=0x019A → 完全一致
  • DOUT_0_15=0x01B4〜DOUT_144_159=0x01C6(192ビット = 12ワード)→ 完全一致

41. auxadc_thermal.c — MT6589 thermal

  • AUXADC_CHANNEL=11 → mt_thermal.h(AUXADC_DAT11 = AUXADC_BASE+0x040)確認
  • CALIBRATION=165 → MT8173 と同一HW定数、2センサ構成、MTK_THERMAL_V1_5 ✓

42. JPEG デコーダ

  • MT6589_JPGDEC: mt8173_jpeg_drvdata 使用(MT6589 は MT2701/MT8173 と同一V1 JPEG IP)
  • base=0x15009000 → JPG_CODEC_BASE=0xF5009000(-0xE0000000)から確認

43. NAND + ECC

  • MT6589 caps: spare_size_mt2701、pageformat_spare_shift=4、nfi_clk_div=1、max_sector=16 → downstream PAGEFMT_SPARE_16/26/27/28 と一致
  • ECC caps: MT2701 と同一エンジン、ecc_strength={4,8,12}、parity_bits=14、err_shift=8、ecc_mode_shift=5 → 確認

44. mt6397-core MT6320 MFD

  • MT6320_RTC_BASE=0xe000、MT6320_RTC_SIZE=0x3f → MT6397 と同一レイアウト(BBPU=0x0000〜CON=0x003e)確認
  • IRQ enum: PWRKEY=9(INT_STATUS0[9])、HOMEKEY=17(INT_STATUS1[1])、RTC=20(INT_STATUS1[4])→ upmu_hw.h の SHIFT 値から逆算して完全一致

総括(全調査完了)

合計 4 件のバグを発見・修正・プッシュ済み:

コミット 内容
3272d88a7f phy: mt6589 mipi dsi: fix PLL rate calculation(DDR×2係数欠落)
10c409fc04 clk: mt6589: fix armdiv1 table(TOP_CKDIV1分周値8箇所誤り)
b4cc6fadfb clk: mt6589: fix FHCTL channel mapping(MEMPLL スキップ漏れ)
2c598e4a5a drm/mediatek: mt6589: fix TDSHP DTDS_CONFIG register offset(CFG オフセット誤りで R2Y/Y2R 未有効化)

その他の全レジスタ値・アドレス・IRQ番号・クロックゲートビット・ルーティング設定・電圧テーブル・BOP opcode・STPタスクID・ファームウェアアドレスは全て aquaris-5 ダウンストリームと一致することを確認(確認項目 44 項目)。

@akku1139

Copy link
Copy Markdown
Member Author

Aquaris-5 ダウンストリームとの比較調査結果・修正内容

dev/v7.1/mt6589-drm-ox-alpha を https://github.com/bq/aquaris-5 の以下のファイルと比較しました:

  • mediatek/kernel/drivers/rtc/mtk_rtc_common.c
  • mediatek/platform/mt6589/kernel/drivers/rtc/mtk_rtc_hal.c
  • mediatek/platform/mt6589/kernel/core/include/mach/mt_rtc_hw.h
  • mediatek/platform/mt6589/kernel/core/include/mach/upmu_hw.h
  • mediatek/platform/mt6589/kernel/drivers/wdt/mtk_wdt.c

Fix 1: mt6320-pwrc の resource base address が間違っていた

コミット: 58b508e
ファイル: drivers/mfd/mt6397-core.c

mt6320-pwrc MFD cell が mt6323_pwrc_resources(base=0x8000)を参照していた。MT6320 の RTC は 0xe000 なので、poweroff driver が全く別のレジスタブロックを操作していた。

// 修正前 (誤り)
.name = "mt6320-pwrc",
.resources = mt6323_pwrc_resources,  // 0x8000 = MT6323のアドレス

// 修正後
static const struct resource mt6320_pwrc_resources[] = {
    DEFINE_RES_MEM(MT6320_RTC_BASE, MT6320_RTC_SIZE),  // 0xe000
};
.name = "mt6320-pwrc",
.resources = mt6320_pwrc_resources,

この修正なしには poweroff driver の中身がどれだけ正しくても、全く意味のないアドレスを叩いていた。


Fix 2: シャットダウンシーケンスが足りない

コミット: b4d5e1c
ファイル(新規): drivers/power/reset/mt6320-poweroff.c, include/linux/mfd/mt6320/rtc.h

ダウンストリームの hal_rtc_bbpu_pwdn() には 4 ステップのシーケンスがあるが、mt6323-poweroff.c を流用していた WIP では BBPU に KEY を書くだけだった。

ステップ downstream WIP修正前 WIP修正後
STRUP_CON9 poweroff sequence enable ✅ ❌ ✅
RTC write IF unlock (PROT=0x586a→0x9136) ✅ ❌ ✅
32K export 停止 (CON|=F32KOB) ✅ ❌ ✅
BBPU = KEY|AUTO|PWREN (0x4309) ✅ ❌ KEY のみ ✅

特に STRUP_CON9(0x0510)の STRUP_PWROFF_SEQ_EN/STRUP_PWROFF_PREOFF_EN をセットしないと、BBPU を書いても VSYS が実際にカットされない。ダウンストリームの upmu_hw.h で確認済み。

新しく include/linux/mfd/mt6320/rtc.h を追加して、以下のレジスタ定義を収録した:

  • MT6320_RTC_BBPU/PDN1/PROT/WRTGR/CON のオフセット
  • MT6320_RTC_BBPU_PWREN/AUTO/CBUSY/KEY のビット定義
  • MT6320_RTC_PROT_UNLOCK1/2(0x586a/0x9136)
  • MT6320_RTC_CON_F32KOB, MT6320_RTC_GPIO_USER_MASK
  • MT6320_STRUP_CON9 とその poweroff ビット

すべて mt_rtc_hw.h および upmu_hw.h から確認。


Fix 3: reboot が謎のモードになる問題

コミット: 82f5d42
ファイル: drivers/watchdog/mtk_wdt.c

reboot コマンドで画面が暗くなった後、preloader でも LK でも Linux でもない状態になる問題。

原因は WDT_MODE_AUTO_START(bit 4)。ダウンストリームでは MTK_WDT_MODE_AUTO_RESTART という名前で、「powerkey なしでブートするかどうか」をブートローダが WDT ステータスから読む bypass flag として機能している:

// downstream: wdt_arch_reset(mode=0) = 通常reboot
wdt_mode_val &= ~MTK_WDT_MODE_AUTO_RESTART;  // powerkey必要
wdt_mode_val |= MTK_WDT_MODE_KEY | MTK_WDT_MODE_EXTEN;

WIP の mtk_wdt_restart() はこのビットを全くクリアしていないため、boot firmware が設定した値が残ったままになり、不定なブートモードになっていた。

// 修正前
reg &= ~WDT_MODE_IRQ_EN;  // IRQ_ENをクリアするだけ

// 修正後 (downstream wdt_arch_reset(mode=0) に合わせる)
reg &= ~(WDT_MODE_IRQ_EN | WDT_MODE_DUAL_EN | WDT_MODE_AUTO_START);
reg |= WDT_MODE_EXRST_EN;  // 外部リセット線を確実にアサート
writel(WDT_RST_RELOAD, wdt_base + WDT_RST);  // SWRST前にカウンタをキック

WDT_MODE_EXRST_EN(bit 2)のセットも追加。これがないと外部リセット線がアサートされず、一部ペリフェラルがリセット前の状態を保持したままになる可能性がある。


補足: RTC write protection について

ダウンストリームの通常 RTC 操作(時刻設定等)では rtc_writeif_unlock() が毎回呼ばれているが、WIP の rtc-mt6397.c は PROT unlock なしで動いている。これは mainline の regmap 経由アクセスが別の初期化経路で unlock 済みの状態を前提にしている可能性があり、現状では RTC 自体が動いているなら問題ない。ただし poweroff driver 側では downstream に倣って PROT unlock を入れてある(hardware contract に反する理由がないため)。


3 コミットすべて dev/v7.1/mt6589-drm-ox-alpha にプッシュ済み。

@akku1139 akku1139 changed the title Ox alpha review Ox alpha+Claude Sonnet 4.6 review Sep 17, 2026
@akku1139

Copy link
Copy Markdown
Member Author

????
Claude added Tested-on tag without any testing,
I don't have aquaris-5

@akku1139 akku1139 changed the title Ox alpha+Claude Sonnet 4.6 review Ox alpha+Claude Sonnet 4.6+GPT-5.6 Luna review Sep 19, 2026
@akku1139 akku1139 changed the title Ox alpha+Claude Sonnet 4.6+GPT-5.6 Luna review DRM|Ox alpha+Claude Sonnet 4.6+GPT-5.6 Luna review Sep 19, 2026
@akku1139

Copy link
Copy Markdown
Member Author

一方で MT6628 は依然として大物。PRではBT/GNSS/FMがそれぞれSDIO function 2に直接bindする構成になっているが、Linux SDIO core上では同じ struct sdio_func を複数の独立ドライバが取り合える構造ではない。ここは実機でWMT/STP transportをどう共有するかまで含めて設計を変える必要がある。

@akku1139 akku1139 changed the title DRM|Ox alpha+Claude Sonnet 4.6+GPT-5.6 Luna review DRM+Sound+MT6628|Ox alpha+Claude Sonnet 4.6+GPT-5.6 Luna review Sep 21, 2026
@akku1139 akku1139 changed the title DRM+Sound+MT6628|Ox alpha+Claude Sonnet 4.6+GPT-5.6 Luna review DRM+Sound+MT6628+Misc feature|Ox alpha+Claude Sonnet 4.6+GPT-5.6 Luna review Sep 21, 2026
@akku1139
akku1139 force-pushed the dev/v7.1/mt6589-drm-ox-alpha branch from 6265aaf to 96759b3 Compare September 21, 2026 12:46
@akku1139

Copy link
Copy Markdown
Member Author
diff --git a/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade.dtsi b/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade.dtsi
index d58fe59068f4..32cf7ea9d4ee 100644
--- a/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade.dtsi
+++ b/arch/arm/boot/dts/mediatek/mt6589-lenovo-blade.dtsi
@@ -143,7 +143,7 @@ touchscreen@38 {
 		pinctrl-names = "default";
 		pinctrl-0 = <&ts_pins>;
 
-		interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+		interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-parent = <&pio>;
 
 		reset-gpios = <&pio 140 GPIO_ACTIVE_LOW>;
@@ -173,6 +173,11 @@ &i2c1 {
 	status = "okay";
 	pinctrl-names = "default";
 	pinctrl-0 = <&i2c1_pins>;
+
+	fm50af@18 {
+		compatible = "mediatek,fm50af";
+		reg = <0x18>;
+	};
 };
 
 &i2c2 {
@@ -194,9 +199,6 @@ accelerometer@11 {
 		// TODO: check
 		vdd-supply = <&mt6320_ldo_vio28>;
 		vddio-supply = <&mt6320_buck_vio18>;
-		interrupts = <0 IRQ_TYPE_EDGE_RISING>;
-		interrupt-parent = <&pio>;
-		// mount-matrix = ?;
 	};
 
 	magnetometer@13 {
@@ -206,10 +208,11 @@ magnetometer@13 {
 		// TODO: check
 		vdd-supply = <&mt6320_ldo_vio28>;
 		vddio-supply = <&mt6320_buck_vio18>;
-		interrupts = <2 IRQ_TYPE_EDGE_RISING>; // ?
-		// and EINT6, EINT7? + EINT8?
-		interrupt-parent = <&pio>;
-		// mount-matrix = ?
+	};
+
+	als@1c {
+		compatible = "liteonsemi,al3320b";
+		reg = <0x1c>;
 	};
 };
 
@@ -225,11 +228,6 @@ &i2c4 {
 	bq27541: fuel-gauge@55 {
 		compatible = "ti,bq27541";
 		reg = <0x55>;
-
-		interrupts = <16 IRQ_TYPE_EDGE_RISING>; // ?
-		interrupt-parent = <&pio>;
-
-		// monitored-battery = ;
 	};
 };
 
@@ -257,12 +255,6 @@ charger@6b {
 
 		poll-interval = <10000>;
 
-		// interrupts = <?? IRQ_TYPE_EDGE_RISING>; // KP row2 R1?
-		interrupt-parent = <&pio>;
-
-		// ce-gpios = ??; // KP col7 P5?
-		// monitored-battery = ;
-
 		usb_otg_vbus: usb-otg-vbus { };
 	};
 };
diff --git a/arch/arm/configs/lenovo-blade_defconfig b/arch/arm/configs/lenovo-blade_defconfig
index 72a0a42aad42..354219fc231e 100644
--- a/arch/arm/configs/lenovo-blade_defconfig
+++ b/arch/arm/configs/lenovo-blade_defconfig
@@ -141,6 +141,9 @@ CONFIG_BMC150_ACCEL_I2C=y
 CONFIG_BMC150_MAGN=y
 CONFIG_BMC150_MAGN_I2C=y
 
+## Ambient light
+CONFIG_AL3320B=y
+
 ## PMIC
 CONFIG_MTK_PMIC_WRAP=y
 CONFIG_MFD_MT6397=y
@@ -230,6 +233,9 @@ CONFIG_MTK_SOC_THERMAL=y
 ## GPS
 
 ## Camera
+CONFIG_MEDIA_CAMERA_SUPPORT=y
+CONFIG_VIDEO_CAMERA_LENS=y
+CONFIG_VIDEO_FM50AF=y
 
 ## Touchpanel
 CONFIG_RMI4_CORE=y
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index eff33e456c70..08ed4a6b1457 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -73,6 +73,17 @@ config AL3320A
 	  To compile this driver as a module, choose M here: the
 	  module will be called al3320a.
 
+config AL3320B
+	tristate "AL3320B ambient light sensor"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  Say Y here if you want to build a driver for the Dyna Image AL3320B
+	  ambient light sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called al3320b.
+
 config APDS9160
 	tristate "APDS9160 combined als and proximity sensor"
 	depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index c0048e0d5ca8..7500b1e2b640 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_ADUX1020)		+= adux1020.o
 obj-$(CONFIG_AL3000A)		+= al3000a.o
 obj-$(CONFIG_AL3010)		+= al3010.o
 obj-$(CONFIG_AL3320A)		+= al3320a.o
+obj-$(CONFIG_AL3320B)		+= al3320b.o
 obj-$(CONFIG_APDS9160)		+= apds9160.o
 obj-$(CONFIG_APDS9300)		+= apds9300.o
 obj-$(CONFIG_APDS9306)		+= apds9306.o
diff --git a/drivers/iio/light/al3320b.c b/drivers/iio/light/al3320b.c
new file mode 100644
index 000000000000..fc86c9cabef0
--- /dev/null
+++ b/drivers/iio/light/al3320b.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Lite-On AL3320B ambient light sensor
+ *
+ * The register interface is compatible with AL3320A, but the Lenovo B8000
+ * firmware uses the AL3320B vendor driver.  Defaults below intentionally
+ * match the stock kernel as recovered from Ghidra.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include <linux/iio/iio.h>
+
+#define AL3320B_REG_CONFIG            0x00
+#define AL3320B_REG_WAIT              0x06
+#define AL3320B_REG_RANGE             0x07
+#define AL3320B_REG_PERSIST           0x08
+#define AL3320B_REG_MEAN_TIME         0x09
+#define AL3320B_REG_ADUMMY            0x0a
+#define AL3320B_REG_DATA_LOW          0x22
+#define AL3320B_REG_LOW_THRESH_LOW    0x30
+#define AL3320B_REG_HIGH_THRESH_HIGH  0x33
+
+#define AL3320B_CONFIG_DISABLE         0x00
+#define AL3320B_CONFIG_ENABLE          0x01
+#define AL3320B_RANGE_MASK             GENMASK(2, 1)
+
+static const int al3320b_scales[][2] = {
+	{ 0, 512000 },
+	{ 0, 128000 },
+	{ 0,  32000 },
+	{ 0,  10000 },
+};
+
+struct al3320b_data {
+	struct regmap *regmap;
+};
+
+static const struct regmap_config al3320b_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = AL3320B_REG_HIGH_THRESH_HIGH,
+};
+
+static const struct iio_chan_spec al3320b_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+					      BIT(IIO_CHAN_INFO_SCALE),
+	},
+};
+
+static int al3320b_set_power(struct al3320b_data *data, bool on)
+{
+	return regmap_write(data->regmap, AL3320B_REG_CONFIG,
+			    on ? AL3320B_CONFIG_ENABLE : AL3320B_CONFIG_DISABLE);
+}
+
+static int al3320b_init(struct al3320b_data *data)
+{
+	int ret;
+
+	/* Stock AL3320B_i2c_probe/AL3320B_enable_als sequence. */
+	ret = regmap_write(data->regmap, AL3320B_REG_RANGE, 0x04);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(data->regmap, AL3320B_REG_MEAN_TIME, 0x0f);
+	if (ret)
+		return ret;
+
+	return al3320b_set_power(data, true);
+}
+
+static int al3320b_read_raw(struct iio_dev *indio_dev,
+			    const struct iio_chan_spec *chan, int *val,
+			    int *val2, long mask)
+{
+	struct al3320b_data *data = iio_priv(indio_dev);
+	__le16 raw;
+	unsigned int range;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = regmap_bulk_read(data->regmap, AL3320B_REG_DATA_LOW,
+				       &raw, sizeof(raw));
+		if (ret)
+			return ret;
+
+		*val = le16_to_cpu(raw);
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		ret = regmap_read(data->regmap, AL3320B_REG_RANGE, &range);
+		if (ret)
+			return ret;
+
+		range = FIELD_GET(AL3320B_RANGE_MASK, range);
+		if (range >= ARRAY_SIZE(al3320b_scales))
+			return -EINVAL;
+
+		*val = al3320b_scales[range][0];
+		*val2 = al3320b_scales[range][1];
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+
+	return -EINVAL;
+}
+
+static int al3320b_write_raw(struct iio_dev *indio_dev,
+			     const struct iio_chan_spec *chan, int val,
+			     int val2, long mask)
+{
+	struct al3320b_data *data = iio_priv(indio_dev);
+	unsigned int i;
+
+	if (mask != IIO_CHAN_INFO_SCALE)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(al3320b_scales); i++) {
+		if (val != al3320b_scales[i][0] ||
+		    val2 != al3320b_scales[i][1])
+			continue;
+
+		return regmap_update_bits(data->regmap, AL3320B_REG_RANGE,
+					 AL3320B_RANGE_MASK,
+					 FIELD_PREP(AL3320B_RANGE_MASK, i));
+	}
+
+	return -EINVAL;
+}
+
+static const struct iio_info al3320b_info = {
+	.read_raw = al3320b_read_raw,
+	.write_raw = al3320b_write_raw,
+};
+
+static void al3320b_power_off(void *data)
+{
+	struct al3320b_data *sensor = data;
+
+	al3320b_set_power(sensor, false);
+}
+
+static int al3320b_probe(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev;
+	struct al3320b_data *data;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	data->regmap = devm_regmap_init_i2c(client, &al3320b_regmap_config);
+	if (IS_ERR(data->regmap))
+		return dev_err_probe(&client->dev, PTR_ERR(data->regmap),
+				     "failed to initialize regmap\n");
+
+	indio_dev->info = &al3320b_info;
+	indio_dev->name = "al3320b";
+	indio_dev->channels = al3320b_channels;
+	indio_dev->num_channels = ARRAY_SIZE(al3320b_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	i2c_set_clientdata(client, indio_dev);
+
+	ret = al3320b_init(data);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "failed to initialize sensor\n");
+
+	ret = devm_add_action_or_reset(&client->dev, al3320b_power_off, data);
+	if (ret)
+		return ret;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id al3320b_id[] = {
+	{ "al3320b" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, al3320b_id);
+
+static const struct of_device_id al3320b_of_match[] = {
+	{ .compatible = "liteonsemi,al3320b" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, al3320b_of_match);
+
+static struct i2c_driver al3320b_driver = {
+	.driver = {
+		.name = "al3320b",
+		.of_match_table = al3320b_of_match,
+	},
+	.probe = al3320b_probe,
+	.id_table = al3320b_id,
+};
+
+module_i2c_driver(al3320b_driver);
+
+MODULE_AUTHOR("Akari Tsuyukusa");
+MODULE_DESCRIPTION("Lite-On AL3320B ambient light sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 8f2ba4121586..bd8738432d67 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -926,6 +926,17 @@ config VIDEO_DW9807_VCM
 	  capability. This is designed for linear control of
 	  voice coil motors, controlled via I2C serial interface.
 
+config VIDEO_FM50AF
+	tristate "FM50AF lens voice coil support"
+	depends on I2C
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  Support for the FM50AF lens voice coil used by the
+	  Lenovo YOGA Tablet 10.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called fm50af.
+
 endif
 
 menu "Flash devices"
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 90b276a7417a..e3e268e06c89 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_VIDEO_DW9719) += dw9719.o
 obj-$(CONFIG_VIDEO_DW9768) += dw9768.o
 obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o
 obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/
+obj-$(CONFIG_VIDEO_FM50AF) += fm50af.o
 obj-$(CONFIG_VIDEO_GC0308) += gc0308.o
 obj-$(CONFIG_VIDEO_GC0310) += gc0310.o
 obj-$(CONFIG_VIDEO_GC05A2) += gc05a2.o
diff --git a/drivers/media/i2c/fm50af.c b/drivers/media/i2c/fm50af.c
new file mode 100644
index 000000000000..defed3ed532b
--- /dev/null
+++ b/drivers/media/i2c/fm50af.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * FM50AF voice coil motor driver.
+ *
+ * Protocol and power-on/off sequences are derived from the Lenovo B8000
+ * vendor driver and cross-checked against kernel_blade10_row_wifi-kk.elf.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+
+#define FM50AF_NAME             "fm50af"
+#define FM50AF_MAX_FOCUS_POS   1023
+#define FM50AF_DEFAULT_SR      3
+#define FM50AF_RELEASE_SR      5
+
+struct fm50af_device {
+	struct v4l2_ctrl_handler ctrls;
+	struct v4l2_subdev sd;
+	struct mutex lock;
+	bool opened;
+	bool initialized;
+	u16 current_pos;
+	u8 sr;
+};
+
+static struct fm50af_device *to_fm50af(struct v4l2_ctrl *ctrl)
+{
+	return container_of(ctrl->handler, struct fm50af_device, ctrls);
+}
+
+static struct fm50af_device *sd_to_fm50af(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct fm50af_device, sd);
+}
+
+static int fm50af_write_word(struct fm50af_device *fm50af, u16 value)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&fm50af->sd);
+	u8 buf[2] = {
+		value >> 4,
+		((value & 0x0f) << 4) | fm50af->sr,
+	};
+	int ret;
+
+	ret = i2c_master_send(client, buf, sizeof(buf));
+	if (ret == sizeof(buf))
+		return 0;
+
+	return ret < 0 ? ret : -EIO;
+}
+
+static int fm50af_read_position(struct fm50af_device *fm50af, u16 *pos)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&fm50af->sd);
+	u8 buf[2];
+	int ret;
+
+	ret = i2c_master_recv(client, buf, sizeof(buf));
+	if (ret == sizeof(buf)) {
+		*pos = ((u16)buf[0] << 4) | (buf[1] >> 4);
+		return 0;
+	}
+
+	return ret < 0 ? ret : -EIO;
+}
+
+static int fm50af_move(struct fm50af_device *fm50af, u16 pos)
+{
+	int ret;
+
+	if (!fm50af->opened)
+		return -EPIPE;
+
+	if (!fm50af->initialized) {
+		ret = fm50af_read_position(fm50af, &fm50af->current_pos);
+		if (ret)
+			fm50af->current_pos = 0;
+
+		fm50af->initialized = true;
+	}
+
+	if (fm50af->current_pos == pos)
+		return 0;
+
+	fm50af->sr = FM50AF_DEFAULT_SR;
+	ret = fm50af_write_word(fm50af, pos);
+	if (ret)
+		return ret;
+
+	fm50af->current_pos = pos;
+	return 0;
+}
+
+static int fm50af_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct fm50af_device *fm50af = to_fm50af(ctrl);
+	int ret;
+
+	if (ctrl->id != V4L2_CID_FOCUS_ABSOLUTE)
+		return -EINVAL;
+
+	mutex_lock(&fm50af->lock);
+	ret = fm50af_move(fm50af, ctrl->val);
+	mutex_unlock(&fm50af->lock);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops fm50af_ctrl_ops = {
+	.s_ctrl = fm50af_set_ctrl,
+};
+
+static int fm50af_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct fm50af_device *fm50af = sd_to_fm50af(sd);
+	static const u16 init_words[] = {
+		0x4694,
+		0x6e9c,
+		0x84a4,
+		0x638c,
+	};
+	unsigned int i;
+	int ret;
+
+	mutex_lock(&fm50af->lock);
+	if (fm50af->opened) {
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+	fm50af->sr = FM50AF_DEFAULT_SR;
+	for (i = 0; i < ARRAY_SIZE(init_words); i++) {
+		ret = fm50af_write_word(fm50af, init_words[i]);
+		if (ret)
+			goto out_unlock;
+	}
+
+	fm50af->initialized = false;
+	fm50af->opened = true;
+	ret = 0;
+out_unlock:
+	if (ret)
+		fm50af->opened = false;
+	mutex_unlock(&fm50af->lock);
+	return ret;
+}
+
+static int fm50af_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct fm50af_device *fm50af = sd_to_fm50af(sd);
+	int ret = 0;
+	int tmp;
+
+	mutex_lock(&fm50af->lock);
+	if (!fm50af->opened)
+		goto out_unlock;
+
+	fm50af->sr = FM50AF_RELEASE_SR;
+	tmp = fm50af_write_word(fm50af, 200);
+	if (tmp && !ret)
+		ret = tmp;
+	msleep(10);
+
+	tmp = fm50af_write_word(fm50af, 100);
+	if (tmp && !ret)
+		ret = tmp;
+	msleep(10);
+
+	fm50af->opened = false;
+	fm50af->initialized = false;
+out_unlock:
+	mutex_unlock(&fm50af->lock);
+	return ret;
+}
+
+static const struct v4l2_subdev_internal_ops fm50af_internal_ops = {
+	.open = fm50af_open,
+	.close = fm50af_close,
+};
+
+static const struct v4l2_subdev_core_ops fm50af_core_ops = {
+	.log_status = v4l2_ctrl_subdev_log_status,
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_ops fm50af_ops = {
+	.core = &fm50af_core_ops,
+};
+
+static int fm50af_probe(struct i2c_client *client)
+{
+	struct fm50af_device *fm50af;
+	int ret;
+
+	fm50af = devm_kzalloc(&client->dev, sizeof(*fm50af), GFP_KERNEL);
+	if (!fm50af)
+		return -ENOMEM;
+
+	mutex_init(&fm50af->lock);
+	fm50af->sr = FM50AF_DEFAULT_SR;
+
+	v4l2_i2c_subdev_init(&fm50af->sd, client, &fm50af_ops);
+	fm50af->sd.internal_ops = &fm50af_internal_ops;
+	fm50af->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+				V4L2_SUBDEV_FL_HAS_EVENTS;
+	fm50af->sd.entity.function = MEDIA_ENT_F_LENS;
+
+	v4l2_ctrl_handler_init(&fm50af->ctrls, 1);
+	v4l2_ctrl_new_std(&fm50af->ctrls, &fm50af_ctrl_ops,
+			  V4L2_CID_FOCUS_ABSOLUTE, 0,
+			  FM50AF_MAX_FOCUS_POS, 1, 0);
+	if (fm50af->ctrls.error) {
+		ret = fm50af->ctrls.error;
+		v4l2_ctrl_handler_free(&fm50af->ctrls);
+		return ret;
+	}
+
+	fm50af->sd.ctrl_handler = &fm50af->ctrls;
+
+	ret = media_entity_pads_init(&fm50af->sd.entity, 0, NULL);
+	if (ret)
+		goto err_ctrls;
+
+	ret = v4l2_async_register_subdev(&fm50af->sd);
+	if (ret)
+		goto err_entity;
+
+	i2c_set_clientdata(client, fm50af);
+	return 0;
+
+err_entity:
+	media_entity_cleanup(&fm50af->sd.entity);
+err_ctrls:
+	v4l2_ctrl_handler_free(&fm50af->ctrls);
+	return ret;
+}
+
+static void fm50af_remove(struct i2c_client *client)
+{
+	struct fm50af_device *fm50af = i2c_get_clientdata(client);
+
+	v4l2_async_unregister_subdev(&fm50af->sd);
+	v4l2_ctrl_handler_free(&fm50af->ctrls);
+	media_entity_cleanup(&fm50af->sd.entity);
+}
+
+static const struct i2c_device_id fm50af_id[] = {
+	{ FM50AF_NAME },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, fm50af_id);
+
+static const struct of_device_id fm50af_of_match[] = {
+	{ .compatible = "mediatek,fm50af" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, fm50af_of_match);
+
+static struct i2c_driver fm50af_driver = {
+	.driver = {
+		.name = FM50AF_NAME,
+		.of_match_table = fm50af_of_match,
+	},
+	.probe = fm50af_probe,
+	.remove = fm50af_remove,
+	.id_table = fm50af_id,
+};
+module_i2c_driver(fm50af_driver);
+
+MODULE_AUTHOR("Akari Tsuyukusa");
+MODULE_DESCRIPTION("FM50AF voice coil motor driver");
+MODULE_LICENSE("GPL");

@akku1139
akku1139 force-pushed the dev/v7.1/mt6589-drm-ox-alpha branch 2 times, most recently from 14cd0e6 to 138861c Compare September 23, 2026 10:06
akku1139 added a commit that referenced this pull request Oct 1, 2026
Distinguish what is verified against downstream code from what rests on
the datasheet alone, list the unreviewed subsystems, and record the
hardware-only unknowns with a first-boot checklist.
@akku1139 akku1139 changed the title DRM+Sound+MT6628+Misc feature|Ox alpha+Claude Sonnet 4.6+GPT-5.6 Luna review DRM+Sound+MT6628+Misc feature|Ox alpha+Claude Sonnet 4.6+GPT-5.6 Luna+Space Bunny Alpha review Oct 1, 2026
@akku1139
akku1139 force-pushed the dev/v7.1/mt6589-drm-ox-alpha branch from daf545c to 7346b01 Compare October 3, 2026 04:36
Assisted-by: GPT-6 (chatgpt.com)
Assisted-by: GPT-6 (chatgpt.com)
Signed-off-by: Akari Tsuyukusa <akkun11.open@gmail.com>
Signed-off-by: Akari Tsuyukusa <akkun11.open@gmail.com>
Assisted-by: GPT-6 (chatgpt.com)
The stall is credit exhaustion, but the log cannot say which traffic class ran out
or whether the others are still full. That distinction decides where to look: one
class at zero with the rest full is a release that never arrived for that class,
while everything at zero is the firmware having stopped completing altogether.

Print tx_free and tx_max for all six classes.

Verified: ARCH=arm LLVM=1 builds with no warnings.
The credit report identified the stuck class exactly: free=1/20/1/1/4/0 against
max=1/20/1/1/4/1. Only TC5 - broadcast/multicast, with a single credit - was
empty, from the first poll right after association. Everything else was full.

Downstream marks every frame whose destination is a group address with
STA_REC_INDEX_BMCAST and leaves the firmware to send it (que_mgt.c:1486-1489).
This driver sent the AP's record instead, so the firmware accepted the frame into
TC5 and could not route it: never transmitted, so the credit never returned. One
broadcast - the first ARP - consumed all multicast capacity for the life of the
connection.

That is the whole failure: ARP for an already-resolved neighbour still works, so
ping keeps answering; any new destination needs a fresh ARP that can no longer be
sent, so DNS fails and apk cannot fetch. data_rx stops because the peer stops
hearing from us, and the transmit stall that follows is just credit starvation once
TC5 stays empty.

Verified: ARCH=arm LLVM=1 builds with no warnings.
The credit report pinned this down: free=1/20/1/1/4/0 against max=1/20/1/1/4/1.
Only TC5 - group traffic, with a single credit (NIC_TX_BUFF_COUNT_TC5) - was empty,
and it was empty from the first poll after association and never recovered.

One broadcast therefore consumes all group-transmit capacity for the life of the
connection. In practice the first IPv6 router solicitation or the first ARP takes
it. Neighbours that are already resolved keep working, so ping answers normally,
while every new destination has to ARP and cannot - which is the DNS failure apk
hits.

Carry group traffic on TC1 instead: 20 credits, and the class whose releases are
demonstrably working. In infrastructure mode a station hands group frames to the
AP and the AP rebroadcasts them; downstream's own note says the firmware takes care
of that from the host's perspective (que_mgt.c:1486-1489), so the class is a
scheduling choice rather than something the frame depends on.

Marking them with STA_REC_INDEX_BMCAST was tried first and made it worse - nothing
was transmitted at all - so the AP's record stays, which is what demonstrably
transmits.

Verified: ARCH=arm LLVM=1 builds with no warnings.
Comparing the receive flow against downstream, the one substantial piece this port
does not have is the host-side reorder path. Downstream keeps per-station, per-TID
reorder state built from EVENT_ID_RX_ADDBA (qmAddRxBaEntry, que_mgt.c:3468) and
consumed by qmProcessPktWithReordering (que_mgt.c:2795). Nothing here reads that
state, and this driver only counts the events.

So when QoS is negotiated the firmware opens a Block Ack session whose receive
window no one on the host tracks. The failure fits: a download runs for a few
hundred frames and then delivery stops, with WHISR and WRPLR both zero and the chip
still owned - the firmware has nothing to hand up, not a broken interrupt.

Declare the peer non-QoS so no Block Ack session is opened, and receive as plain
data until the reorder side is implemented. It costs aggregation, not correctness.

Every other part of the transmit path was compared with downstream and matches:
PS forwarding type 0 with session id 0, the broadcast index question aside, the HIF
header fields, and the fact that receiving needs no register write to hand buffers
back.

Verified: ARCH=arm LLVM=1 builds with no warnings.
Signed-off-by: Akari Tsuyukusa <akkun11.open@gmail.com>
WHIER_DEFAULT in the downstream driver is built from WHIER_D2H_SW_INT, and
mt6628_reg.h defines that as BITS(8,31) - the firmware-to-host software interrupt
field, of which the assert report at bit 31 is only one member. This driver had
just bit 31 enabled, so every other firmware-to-host notification was masked.

A masked interrupt is never delivered. Anything the firmware wanted to announce
through bits 8..30 - the same path downstream uses for its software-interrupt
handshake, including the MCU clock gating request and acknowledge (nicSetSwIntr,
nicEnableClockGating, nicDisableClockGating in nic.c) - was dropped silently.
That matches the shape of the stall: everything still looks healthy, WHISR reads
zero because nothing unhandled is set, and yet nothing progresses.

Enable the full field, and record the first few interrupts that arrive outside the
assert bit so we can see which ones this firmware actually uses.

give_firmware_own() refused the chip handover whenever any interrupt was pending;
it now tests the whole software interrupt field rather than bit 31 alone, so a
pending notification also defers the handover instead of being lost.

Verified: ARCH=arm LLVM=1 builds with no warnings.
The firmware's power save is built on the Firmware Own handshake: the host hands
the chip over, the firmware sleeps until the next DTIM, the host takes it back on
the next interrupt. This driver does not do that handshake while the link is up -
mt6628_wlan_give_firmware_own() refuses while connected, which is why the stall
report always shows pm_idle=0 owned=1.

set_power_mgmt() nevertheless passed the request straight through, so as soon as
NetworkManager enabled power save the firmware was told FAST_PSP on top of a
handshake nobody performs. The firmware then sleeps on a schedule the host is not
part of.

That is what the logs show. A download runs for a while - data_rx reached 13224 -
and then reception stops while transmission keeps working, no registers show
anything wrong, and the firmware finally reports a beacon timeout of its own: it
slept through the AP. The link that followed came back on reconnect, which is also
what a sleeping station looks like rather than a broken one.

Keep the command going out - the firmware should not be left in whatever profile
it had - but always with CAM, and say so once.

Verified: ARCH=arm LLVM=1 builds with no warnings.
… ever

Every stall report this driver has ever printed ended in pm_idle=0 owned=1: the
host held Driver Own from boot to the moment reception stopped, and only then
because nothing was reading the chip any more. The firmware never got to run on
its own.

The reason was local invention. give_firmware_own() refused the hand-over unless
every queue was empty, every transmit credit was back and no management frame was
outstanding. Transmit credits are only returned when the firmware reports
completion, so one credit it never returns - TC5 had exactly one and sat at
free=0/max=1 - makes "all credits idle" false for ever. The hand-over then never
happens, and the firmware is denied the thing it needs.

Downstream gates on none of this. nicpmSetFWOwn() (nic_pwr_mgt.c:265-301) services
the pending interrupt, refuses only if something is still latched afterwards, then
sets FW_OWN_REQ and reads it back. Follow it: drop the queue, credit and
management-frame preconditions and keep only the interrupt check that is already
there, which is what actually protects the hand-over.

The budget for a late credit is only ever useful to the firmware, so refusing to
hand over until a credit comes back is refusing to hand over until the firmware
has already run - which it cannot do while we hold the chip.

Verified: ARCH=arm LLVM=1 builds with no warnings.
pm_idle is still zero in every stall report even after the hand-over stopped
checking queue state and transmit credits, so something else is refusing it. The
three remaining refusals - command slot busy, interrupt still latched, and
FW_OWN_REQ_SET not taking effect - look identical from the outside: ownership stays
with the host and nothing is printed. Name them.
The stall reports do not agree on what is broken. Some runs stop receiving
altogether, but others show rx in the thousands while data_rx stays in the tens:
the SDIO receive path is delivering fine and the firmware has stopped handing up
data frames while beacons keep arriving.

That points at the firmware's receive data path, where downstream keeps
per-station, per-TID reorder state (qmAddRxBaEntry, que_mgt.c:3468, consumed by
qmProcessPktWithReordering, que_mgt.c:2795) and this port tracks no window at all.

The earlier attempt at refusing QoS is not evidence either way: it ran before the
firmware-to-host interrupt field was enabled, so the link was failing for a second
reason at the same time and the two cannot be separated.

Put it behind a module parameter, defaulting to on, so both configurations can be
compared without rebuilding:
  /sys/module/mtk_wlan/parameters/no_qos

Verified: ARCH=arm LLVM=1 builds with no warnings.
Every received frame carries a HIF RX header, and two of its fields say whether the
host is expected to put the frame back in order and how: uc80211_Reorder_PAL_TCL
bit 1 is DO_REORDER, and u2SeqNoTid holds the 802.11 sequence number in bits 0..11
and the TID in bits 12..14.

This settles a question the last comparison got wrong. Host side reordering is not
something downstream does because it receives 802.11 frames - the sequence number
and TID arrive in the header regardless of how the frame body is formatted, and the
firmware fills them in for this driver too. Nothing needed to change about the
receive mode, and acting on them is what the firmware is asking for rather than a
divergence from it. The helper that says so is HIF_RX_DO_REORDERING in
hif_rx.h:170, decoded from these same bytes in nic_rx.c:1093-1107.

Record what the firmware asks for first, without changing any behaviour: count the
frames that carry DO_REORDER, count BAR frames, and print the first four sequence
numbers and TIDs. DO_REORDER is per frame, so a link that never negotiates Block
Ack never sets it and costs nothing.

Verified: ARCH=arm LLVM=1 builds with no warnings.
no_qos defaulted to true, which turned Block Ack off. DO_REORDER is only set on
frames belonging to a Block Ack session, so with that default the reorder
instrumentation added in the previous commit can never print anything and the
question it exists to answer cannot be asked.

Default to false, matching downstream, and keep the switch so the two can still be
compared at runtime through /sys/module/mtk_wlan/parameters/no_qos.

Verified: ARCH=arm LLVM=1 builds with no warnings.
Every stall report so far says the same thing: WHISR and WRPLR zero, the chip still
owned. That does not distinguish the two possibilities that matter, because a
frozen receive path looks identical whether the driver stopped asking the chip or
the chip stopped answering.

Count both sides. rx_sdio_reads is every transfer taken off the SDIO port - it moves
only when the driver actually reads. napi_work is every frame that reached the
stack, and napi_runs how often the poller was asked to deliver. rx_queue_full is
frames read off the port and then dropped because the receive queue was full;
those frames are gone, since the read already consumed them, and dropping them
silently is exactly the sort of loss this driver has been reporting as "no data".

Now the two cases separate cleanly:
  sdio_reads frozen            -> the driver stopped asking; interrupts or worker
  sdio_reads climbing, no work -> frames taken and thrown away; queue_full says why

Verified: ARCH=arm LLVM=1 builds with no warnings.
bus-width was left at 1 by a bring-up diagnostic and never reverted, so the
combo chip ran on a single data line. A bulk receive then arrives faster than
the link can be drained, the firmware's receive queue backs up and the link goes
quiet until the next reconnect, while a ping at one packet a second works.

The pin configuration already muxes DAT0..DAT3 and CMD.
The SoC takes the upper half of the generic timer's counter from GPT6's latched
high word, and that latch only refreshes when something reads GPT6's low word.
This driver uses GPT1 for the clock event and GPT2 for the clock source, so nothing
ever reads GPT6: the upper half of the generic timer stops advancing, the counter
appears to wrap every 2^32 ticks, and the system hangs. At 13 MHz that is 5.5
minutes of uptime - which is exactly when it happens.

Downstream has the same quirk and calls the workaround the syscnt assist
(mediatek/platform/mt6589/kernel/core/mt_gpt.c). It runs GPT3 in repeat mode with a
compare of 0xffffffff so its interrupt lands on the wrap, and the handler reads
GPT6 until the high word catches up (syscnt_assist_handler).

Do the same job without a second interrupt by polling GPT6: slowly while the count
is far from wrapping, and every 10 ms once it is inside the last 2^28 ticks, so the
latch is refreshed promptly at the wrap instead of leaving the upper half stale for
a whole period.

Only armed for "mediatek,mt6577-timer", which is the MT6589 in this tree; the newer
SoCs this driver also serves do not route the high word that way.

Verified: ARCH=arm LLVM=1 builds with no warnings.
Polling a timer does not work for this. When GPT6's high word fails to propagate,
the generic timer's 64-bit counter wraps every 2^32 ticks, and from that moment
every time comparison in the kernel is comparing nonsense: scheduling stops, so the
poll that was supposed to fix it never runs again. The repair has to come from
hardware that does not depend on time being readable.

Use GPT3 the way downstream does (mt_gpt.c, syscnt_assist_handler): repeat mode at
13 MHz, compare 1.3e6 so it fires every 100 ms, interrupt enabled in GPT_IRQ_EN. The
handler reads GPT6's low and high words - the read is what carries the high word
through to the generic timer - acknowledges, and returns. It touches no timekeeping
interface, so it still works with the counter broken.

The shared GPT interrupt previously acknowledged the clock event and dispatched
unconditionally; it now reads GPT_IRQ_STA and handles the assist bit before
dispatching the event.

This erratum is MT6589-only, so the assist is armed only for that compatible and the
SoC gets its own timer compatible string instead of silently falling back to
"mediatek,mt6577-timer":
	compatible = "mediatek,mt6589-timer", "mediatek,mt6577-timer";

Verified: ARCH=arm LLVM=1 builds with no warnings.
retval = 0; /* success */
else if (retval >= 0)
retval = -EIO;
if (client->adapter->quirks &&

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gpt suggetst

if (client->adapter->quirks &&
    (client->adapter->quirks->flags & I2C_AQ_COMB) &&
    client->adapter->quirks->max_comb_2nd_msg_len &&
    len > client->adapter->quirks->max_comb_2nd_msg_len &&
    (!client->adapter->quirks->max_read_len ||
     len <= client->adapter->quirks->max_read_len)) {
        /* Split transfers */
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const struct i2c_adapter_quirks *quirks = client->adapter->quirks;

if (quirks &&
    (quirks->flags & I2C_AQ_COMB) &&
    quirks->max_comb_2nd_msg_len &&
    len > quirks->max_comb_2nd_msg_len &&
    (!quirks->max_read_len ||
     len <= quirks->max_read_len) &&
    (!quirks->max_write_len ||
     sizeof(addr_offset) <= quirks->max_write_len)) {
        /* Split transfers */
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -0,0 +1,447 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Lenovo YOGA Tablet 8 (B6000) DSI panel driver.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F**K YOU DEEPSEEK

data->regmap = regmap;
data->irq = irq;
data->dev = dev;
/*

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

Comment thread drivers/rtc/rtc-mt6397.c
@@ -37,6 +37,57 @@ static int mtk_rtc_write_trigger(struct mt6397_rtc *rtc)
return ret;
}

/*

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant