Repository navigation
Conversation
|
starting claude sonnet 4.6 review |
調査レポート(Claude による aquaris-5 ダウンストリームとの比較)✅ 正しかった事項1. LARB ポートオフセット(
2. M4U レジスタオフセット(
3. MT6589 DTS の M4U アドレスマップ(
4. SMI bus_sel 値(
5. mt6589
6. YUV2RGB 係数行列(
7. mt6589_get_fault_larb_port の LARB ID デコード
8. DSI MEM_CONTI レジスタ(
9. DSI PHY PLL コンフィグテーブル(
❌ 発見したバグと修正
MIPI DSI は DDR(差動)シグナリングを使用するため、クロックエッジごとに 1 ビットを伝送する。したがって実際のビットレート(lane data rate)は PLL 出力クロックの 2 倍になる。
影響: |
追加調査レポート(TOP_CKDIV1 / FHCTL)❌ 発見・修正したバグ(2件)
|
| 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-5mt_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)
最終調査レポート(全ドライバ網羅的検証完了)✅ 追加で検証した正しかった事項19. mutex モジュール ID(
20. mmsys ルーティングレジスタ(
21. BLS/PWM ドライバ(
22. topckgen クロック MUX(
23. infracfg ゲートクロック(
24. pericfg ゲートクロック(
25. disp クロックゲート(
26. 全ディスプレイブロック物理アドレス(DTS)
27. 全ディスプレイ GIC SPI IRQ 番号(DTS)
28. M4U / LARB / SMI アドレス(DTS)
29. AFE レジスタ / ビットフィールド(
総括合計 3 件のバグを発見・修正・プッシュ済み:
それ以外の全レジスタ値・アドレス・IRQ番号・クロックゲートビット・ルーティング設定は全て aquaris-5 ダウンストリームと一致することを確認。 |
追加調査レポート(MT6320/MT6628/残ドライバ完全網羅)❌ 発見・修正したバグ(1件)
aquaris-5
修正内容:
行列係数テーブル( ✅ 追加で検証した正しかった事項36. FM BOP opcodes(
37. gnss-mt6628.c — GNSS ドライバ
38. btmtksdio.c — Bluetooth SDIO
39. MT6320 AUXADC(
40. MT6320 EFUSE(
41. auxadc_thermal.c — MT6589 thermal
42. JPEG デコーダ
43. NAND + ECC
44. mt6397-core MT6320 MFD
総括(全調査完了)合計 4 件のバグを発見・修正・プッシュ済み:
その他の全レジスタ値・アドレス・IRQ番号・クロックゲートビット・ルーティング設定・電圧テーブル・BOP opcode・STPタスクID・ファームウェアアドレスは全て aquaris-5 ダウンストリームと一致することを確認(確認項目 44 項目)。 |
Aquaris-5 ダウンストリームとの比較調査結果・修正内容
Fix 1: mt6320-pwrc の resource base address が間違っていたコミット:
// 修正前 (誤り)
.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: シャットダウンシーケンスが足りないコミット: ダウンストリームの
特に STRUP_CON9( 新しく
すべて Fix 3: reboot が謎のモードになる問題コミット:
原因は // 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 の // 修正前
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前にカウンタをキック
補足: RTC write protection についてダウンストリームの通常 RTC 操作(時刻設定等)では 3 コミットすべて |
|
???? |
|
6265aaf to
96759b3
Compare
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"); |
14cd0e6 to
138861c
Compare
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.
daf545c to
7346b01
Compare
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.
…STA index" This reverts commit 84088a4.
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.
This reverts commit 054153e.
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.
…g it for ever" This reverts commit abaca9e.
…ists" This reverts commit c113557.
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 && |
There was a problem hiding this comment.
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 */
}There was a problem hiding this comment.
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 */
}There was a problem hiding this comment.
| @@ -0,0 +1,447 @@ | |||
| // SPDX-License-Identifier: GPL-2.0-only | |||
| /* | |||
| * Lenovo YOGA Tablet 8 (B6000) DSI panel driver. | |||
| data->regmap = regmap; | ||
| data->irq = irq; | ||
| data->dev = dev; | ||
| /* |
Signed-off-by: Akari Tsuyukusa <akkun11.open@gmail.com>
Signed-off-by: Akari Tsuyukusa <akkun11.open@gmail.com>
| @@ -37,6 +37,57 @@ static int mtk_rtc_write_trigger(struct mt6397_rtc *rtc) | |||
| return ret; | |||
| } | |||
|
|
|||
| /* | |||
There was a problem hiding this comment.
AI Slop, untested
TODO
note:
AI models