* [PATCH v1 0/6] power: Use named initializers for platform_device_id arrays
From: Uwe Kleine-König (The Capable Hub) @ 2026-05-29 10:18 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Kuan-Wei Chiu, Benson Leung, Guenter Roeck, Thomas Weißschuh,
Krzysztof Kozlowski, Matthias Brugger, AngeloGioacchino Del Regno,
linux-pm, linux-kernel, chrome-platform, linux-arm-kernel,
linux-mediatek, Hans de Goede, Marek Szyprowski,
Sebastian Krzyszkowiak, Purism Kernel Team, Yixun Lan,
Andreas Kemnade, Matti Vaittinen, Sven Peter, Janne Grunau,
Neal Gompa, Amit Sunil Dhamne, Samuel Kayode, linux-riscv,
spacemit, asahi, imx, Chen-Yu Tsai
Hello,
this series targets to use named initializers for platform_device_id
arrays. In general these are better readable for humans and more robust
to changes in the respective struct definition.
This robustness is needed as I want to do
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -610,4 +610,7 @@ struct dmi_system_id {
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
- kernel_ulong_t driver_data;
+ union {
+ kernel_ulong_t driver_data;
+ const void *driver_data_ptr;
+ };
};
which allows dropping several casts and eases porting CHERI to mainline
linux. A possible follow-up change is the following example:
diff --git a/drivers/gpio/gpio-pxa.c b/drivers/gpio/gpio-pxa.c
index 5d61053e0596..03bc8e859d73 100644
--- a/drivers/gpio/gpio-pxa.c
+++ b/drivers/gpio/gpio-pxa.c
@@ -534,7 +534,7 @@ static struct irq_chip pxa_muxed_gpio_chip = {
static int pxa_gpio_nums(struct platform_device *pdev)
{
const struct platform_device_id *id = platform_get_device_id(pdev);
- struct pxa_gpio_id *pxa_id = (struct pxa_gpio_id *)id->driver_data;
+ struct pxa_gpio_id *pxa_id = id->driver_data_ptr;
int count = 0;
switch (pxa_id->type) {
@@ -708,14 +708,14 @@ static int pxa_gpio_probe(struct platform_device *pdev)
}
static const struct platform_device_id gpio_id_table[] = {
- { .name = "pxa25x-gpio", .driver_data = (unsigned long)&pxa25x_id },
- { .name = "pxa26x-gpio", .driver_data = (unsigned long)&pxa26x_id },
- { .name = "pxa27x-gpio", .driver_data = (unsigned long)&pxa27x_id },
- { .name = "pxa3xx-gpio", .driver_data = (unsigned long)&pxa3xx_id },
- { .name = "pxa93x-gpio", .driver_data = (unsigned long)&pxa93x_id },
- { .name = "mmp-gpio", .driver_data = (unsigned long)&mmp_id },
- { .name = "mmp2-gpio", .driver_data = (unsigned long)&mmp2_id },
- { .name = "pxa1928-gpio", .driver_data = (unsigned long)&pxa1928_id },
+ { .name = "pxa25x-gpio", .driver_data_ptr = &pxa25x_id },
+ { .name = "pxa26x-gpio", .driver_data_ptr = &pxa26x_id },
+ { .name = "pxa27x-gpio", .driver_data_ptr = &pxa27x_id },
+ { .name = "pxa3xx-gpio", .driver_data_ptr = &pxa3xx_id },
+ { .name = "pxa93x-gpio", .driver_data_ptr = &pxa93x_id },
+ { .name = "mmp-gpio", .driver_data_ptr = &mmp_id },
+ { .name = "mmp2-gpio", .driver_data_ptr = &mmp2_id },
+ { .name = "pxa1928-gpio", .driver_data_ptr = &pxa1928_id },
{ }
};
increasing readability due to less casting. Also this results in the
compiler warning:
drivers/gpio/gpio-pxa.c: In function ‘pxa_gpio_nums’:
drivers/gpio/gpio-pxa.c:537:38: error: initialization discards ‘const’ qualifier from pointer target type [-Werror=discarded-qualifiers]
537 | struct pxa_gpio_id *pxa_id = id->driver_data_ptr;
| ^~
which is a good thing as adding the needed const to fix this warning
improves type safety. (Sorry, no driver below drivers/power benefits
here, so the example is from a different subsystem.)
Patch #5 is just an unrelated change where I spotted the patch
opportunity while preparing this series. It's included here just for my
convenience and could be applied independently.
If you consider the last patch mostly churn, just drop it.
There are no dependencies between the patches, the only file that is
touched in more than one patch is drivers/power/supply/mt6360_charger.c
and even if these (i.e. patches #1 and #5) are reordered, git should be
able to cope with that without conflicts.
Note that this series is only build tested, and there is the possibility
that I failed to spot driver data usage that render patches #2 and/or #3
wrong. So a deeper look on at least those two won't hurt.
Best regards
Uwe
Uwe Kleine-König (The Capable Hub) (6):
power: Drop unused assignment of platform_device_id driver data
power: supply: max14577: Drop driver data in of and platform device id
arrays
power: supply: max17042: Drop driver data in of and platform device id
arrays
power: Use named initializers for platform_device_id arrays
power: supply: mt6360_charger: Use of match table unconditionally
power: Unify code style for platform_device_id arrays
drivers/power/reset/qemu-virt-ctrl.c | 2 +-
drivers/power/reset/spacemit-p1-reboot.c | 4 ++--
drivers/power/reset/tps65086-restart.c | 2 +-
drivers/power/supply/axp288_charger.c | 2 +-
drivers/power/supply/axp288_fuel_gauge.c | 2 +-
drivers/power/supply/bd71828-power.c | 8 ++++----
drivers/power/supply/charger-manager.c | 4 ++--
drivers/power/supply/cros_charge-control.c | 4 ++--
drivers/power/supply/cros_peripheral_charger.c | 4 ++--
drivers/power/supply/cros_usbpd-charger.c | 4 ++--
drivers/power/supply/macsmc-power.c | 2 +-
drivers/power/supply/max14577_charger.c | 12 +++++-------
drivers/power/supply/max17042_battery.c | 14 +++++++-------
drivers/power/supply/max77693_charger.c | 2 +-
drivers/power/supply/max77759_charger.c | 2 +-
drivers/power/supply/max8997_charger.c | 2 +-
drivers/power/supply/max8998_charger.c | 2 +-
drivers/power/supply/mt6360_charger.c | 6 +++---
drivers/power/supply/pf1550-charger.c | 2 +-
drivers/power/supply/rt5033_charger.c | 2 +-
20 files changed, 40 insertions(+), 42 deletions(-)
base-commit: f7af91adc230aa99e23330ecf85bc9badd9780ad
--
2.47.3
^ permalink raw reply
* [PATCH 2/2] soc: mediatek: mtk-infracfg: Export symbols for DDK modules
From: Justin Yeh @ 2026-05-29 10:16 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno
Cc: Project_Global_Chrome_Upstream_Group, linux-kernel,
linux-arm-kernel, linux-mediatek, Justin Yeh
In-Reply-To: <20260529101613.55697-1-justin.yeh@mediatek.com>
Export mtk_infracfg functions to allow other DDK modules (like
mtk-scpsys) to use bus protection APIs.
Changes:
- Add EXPORT_SYMBOL_GPL for set/clear bus_protection and init functions
- Remove static and __init qualifiers from mtk_infracfg_init
- Add mtk_infracfg_init() declaration to header
- Remove postcore_initcall, let dependent modules call init explicitly
- Add #include <linux/module.h> for export macros
- Add MODULE_LICENSE("GPL") metadata
This allows mtk-infracfg to be built as a DDK module (.ko) and its
functions to be used by other modules like mtk-scpsys for power domain
management.
Signed-off-by: Justin Yeh <justin.yeh@mediatek.com>
---
drivers/soc/mediatek/mtk-infracfg.c | 9 +++++++--
include/linux/soc/mediatek/infracfg.h | 1 +
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/soc/mediatek/mtk-infracfg.c b/drivers/soc/mediatek/mtk-infracfg.c
index 2acf19676af2..8a88805468cc 100644
--- a/drivers/soc/mediatek/mtk-infracfg.c
+++ b/drivers/soc/mediatek/mtk-infracfg.c
@@ -7,6 +7,7 @@
#include <linux/jiffies.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
+#include <linux/module.h>
#include <linux/soc/mediatek/infracfg.h>
#include <asm/processor.h>
@@ -43,6 +44,7 @@ int mtk_infracfg_set_bus_protection(struct regmap *infracfg, u32 mask,
return ret;
}
+EXPORT_SYMBOL_GPL(mtk_infracfg_set_bus_protection);
/**
* mtk_infracfg_clear_bus_protection - disable bus protection
@@ -73,8 +75,9 @@ int mtk_infracfg_clear_bus_protection(struct regmap *infracfg, u32 mask,
return ret;
}
+EXPORT_SYMBOL_GPL(mtk_infracfg_clear_bus_protection);
-static int __init mtk_infracfg_init(void)
+int mtk_infracfg_init(void)
{
struct regmap *infracfg;
@@ -90,4 +93,6 @@ static int __init mtk_infracfg_init(void)
MT8192_INFRA_CTRL_DISABLE_MFG2ACP);
return 0;
}
-postcore_initcall(mtk_infracfg_init);
+EXPORT_SYMBOL_GPL(mtk_infracfg_init);
+
+MODULE_LICENSE("GPL");
diff --git a/include/linux/soc/mediatek/infracfg.h b/include/linux/soc/mediatek/infracfg.h
index 9956e18c5ffa..847431a7b967 100644
--- a/include/linux/soc/mediatek/infracfg.h
+++ b/include/linux/soc/mediatek/infracfg.h
@@ -454,4 +454,5 @@ int mtk_infracfg_set_bus_protection(struct regmap *infracfg, u32 mask,
bool reg_update);
int mtk_infracfg_clear_bus_protection(struct regmap *infracfg, u32 mask,
bool reg_update);
+int mtk_infracfg_init(void);
#endif /* __SOC_MEDIATEK_INFRACFG_H */
--
2.45.2
^ permalink raw reply related
* [PATCH 1/2] soc: mediatek: Allow MTK_INFRACFG to be built as module
From: Justin Yeh @ 2026-05-29 10:16 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno
Cc: Project_Global_Chrome_Upstream_Group, linux-kernel,
linux-arm-kernel, linux-mediatek, Justin Yeh
In-Reply-To: <20260529101613.55697-1-justin.yeh@mediatek.com>
Change MTK_INFRACFG from bool to tristate to support building it as
a loadable kernel module (.ko) instead of being built into the kernel.
This is required for DDK (Driver Development Kit) module build, where
mtk-infracfg needs to be loaded dynamically along with other SoC
infrastructure modules.
Signed-off-by: Justin Yeh <justin.yeh@mediatek.com>
---
drivers/soc/mediatek/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/soc/mediatek/Kconfig b/drivers/soc/mediatek/Kconfig
index d7293977f06e..d28b0817ce51 100644
--- a/drivers/soc/mediatek/Kconfig
+++ b/drivers/soc/mediatek/Kconfig
@@ -38,7 +38,7 @@ config MTK_DVFSRC
best achievable performance-per-watt.
config MTK_INFRACFG
- bool "MediaTek INFRACFG Support"
+ tristate "MediaTek INFRACFG Support"
select REGMAP
help
Say yes here to add support for the MediaTek INFRACFG controller. The
--
2.45.2
^ permalink raw reply related
* [PATCH 0/2] soc: mediatek: Make mtk-infracfg modular
From: Justin Yeh @ 2026-05-29 10:16 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno
Cc: Project_Global_Chrome_Upstream_Group, linux-kernel,
linux-arm-kernel, linux-mediatek, Justin Yeh
This series converts mtk-infracfg to be buildable as a loadable module,
allowing for more flexible kernel configurations and reduced memory
footprint on systems that don't require this functionality built-in.
Justin Yeh (2):
soc: mediatek: Allow MTK_INFRACFG to be built as module
soc: mediatek: mtk-infracfg: Export symbols for module support
drivers/soc/mediatek/Kconfig | 2 +-
drivers/soc/mediatek/mtk-infracfg.c | 9 +++++++--
include/linux/soc/mediatek/infracfg.h | 1 +
3 files changed, 9 insertions(+), 3 deletions(-)
--
2.45.2
^ permalink raw reply
* [PATCH v1 1/1] arm64: dts: mediatek: mt8186: change CCI OPP scaling mapping
From: Mark Tseng @ 2026-05-29 10:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Matthias Brugger,
AngeloGioacchino Del Regno, devicetree, linux-kernel
Cc: linux-arm-kernel, linux-mediatek,
Project_Global_Chrome_Upstream_Group, chun-jen.tseng
In-Reply-To: <20260529100514.52082-1-chun-jen.tseng@mediatek.com>
The original CCI OPP table minimum frequency 500Mhz is too low to cause
system stall, So it need update to new version, 1.4G ~ 0.8G.
Signed-off-by: Mark Tseng <chun-jen.tseng@mediatek.com>
---
arch/arm64/boot/dts/mediatek/mt8186.dtsi | 90 ++++++++++++------------
1 file changed, 45 insertions(+), 45 deletions(-)
diff --git a/arch/arm64/boot/dts/mediatek/mt8186.dtsi b/arch/arm64/boot/dts/mediatek/mt8186.dtsi
index b91f88ffae0e..fded6345d422 100644
--- a/arch/arm64/boot/dts/mediatek/mt8186.dtsi
+++ b/arch/arm64/boot/dts/mediatek/mt8186.dtsi
@@ -48,79 +48,79 @@ cci_opp: opp-table-cci {
compatible = "operating-points-v2";
opp-shared;
- cci_opp_0: opp-500000000 {
- opp-hz = /bits/ 64 <500000000>;
- opp-microvolt = <600000>;
+ cci_opp_0: opp-800000000 {
+ opp-hz = /bits/ 64 <800000000>;
+ opp-microvolt = <800000>;
};
- cci_opp_1: opp-560000000 {
- opp-hz = /bits/ 64 <560000000>;
- opp-microvolt = <675000>;
+ cci_opp_1: opp-840000000 {
+ opp-hz = /bits/ 64 <840000000>;
+ opp-microvolt = <806250>;
};
- cci_opp_2: opp-612000000 {
- opp-hz = /bits/ 64 <612000000>;
- opp-microvolt = <693750>;
+ cci_opp_2: opp-880000000 {
+ opp-hz = /bits/ 64 <880000000>;
+ opp-microvolt = <812500>;
};
- cci_opp_3: opp-682000000 {
- opp-hz = /bits/ 64 <682000000>;
- opp-microvolt = <718750>;
+ cci_opp_3: opp-920000000 {
+ opp-hz = /bits/ 64 <920000000>;
+ opp-microvolt = <825000>;
};
- cci_opp_4: opp-752000000 {
- opp-hz = /bits/ 64 <752000000>;
- opp-microvolt = <743750>;
+ cci_opp_4: opp-960000000 {
+ opp-hz = /bits/ 64 <960000000>;
+ opp-microvolt = <831250>;
};
- cci_opp_5: opp-822000000 {
- opp-hz = /bits/ 64 <822000000>;
- opp-microvolt = <768750>;
+ cci_opp_5: opp-1000000000 {
+ opp-hz = /bits/ 64 <1000000000>;
+ opp-microvolt = <837500>;
};
- cci_opp_6: opp-875000000 {
- opp-hz = /bits/ 64 <875000000>;
- opp-microvolt = <781250>;
+ cci_opp_6: opp-1040000000 {
+ opp-hz = /bits/ 64 <1040000000>;
+ opp-microvolt = <850000>;
};
- cci_opp_7: opp-927000000 {
- opp-hz = /bits/ 64 <927000000>;
- opp-microvolt = <800000>;
+ cci_opp_7: opp-1080000000 {
+ opp-hz = /bits/ 64 <1080000000>;
+ opp-microvolt = <856250>;
};
- cci_opp_8: opp-980000000 {
- opp-hz = /bits/ 64 <980000000>;
- opp-microvolt = <818750>;
+ cci_opp_8: opp-1120000000 {
+ opp-hz = /bits/ 64 <1120000000>;
+ opp-microvolt = <862500>;
};
- cci_opp_9: opp-1050000000 {
- opp-hz = /bits/ 64 <1050000000>;
- opp-microvolt = <843750>;
+ cci_opp_9: opp-1160000000 {
+ opp-hz = /bits/ 64 <1160000000>;
+ opp-microvolt = <887500>;
};
- cci_opp_10: opp-1120000000 {
- opp-hz = /bits/ 64 <1120000000>;
- opp-microvolt = <862500>;
+ cci_opp_10: opp-1200000000 {
+ opp-hz = /bits/ 64 <1200000000>;
+ opp-microvolt = <912500>;
};
- cci_opp_11: opp-1155000000 {
- opp-hz = /bits/ 64 <1155000000>;
- opp-microvolt = <887500>;
+ cci_opp_11: opp-1240000000 {
+ opp-hz = /bits/ 64 <1240000000>;
+ opp-microvolt = <937500>;
};
- cci_opp_12: opp-1190000000 {
- opp-hz = /bits/ 64 <1190000000>;
- opp-microvolt = <906250>;
+ cci_opp_12: opp-1280000000 {
+ opp-hz = /bits/ 64 <1280000000>;
+ opp-microvolt = <962500>;
};
- cci_opp_13: opp-1260000000 {
- opp-hz = /bits/ 64 <1260000000>;
- opp-microvolt = <950000>;
+ cci_opp_13: opp-1320000000 {
+ opp-hz = /bits/ 64 <1320000000>;
+ opp-microvolt = <987500>;
};
- cci_opp_14: opp-1330000000 {
- opp-hz = /bits/ 64 <1330000000>;
- opp-microvolt = <993750>;
+ cci_opp_14: opp-1360000000 {
+ opp-hz = /bits/ 64 <1360000000>;
+ opp-microvolt = <1012500>;
};
cci_opp_15: opp-1400000000 {
--
2.45.2
^ permalink raw reply related
* [PATCH v1 0/1] arm64: dts: mediatek: mt8186: change CCI OPP scaling mapping
From: Mark Tseng @ 2026-05-29 10:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Matthias Brugger,
AngeloGioacchino Del Regno, devicetree, linux-kernel
Cc: linux-arm-kernel, linux-mediatek,
Project_Global_Chrome_Upstream_Group, chun-jen.tseng
update new cci-opp table
Mark Tseng (1):
arm64: dts: mediatek: mt8186: change CCI OPP scaling mapping
arch/arm64/boot/dts/mediatek/mt8186.dtsi | 90 ++++++++++++------------
1 file changed, 45 insertions(+), 45 deletions(-)
--
2.45.2
^ permalink raw reply
* [PATCH 1/2] pinctrl: mediatek: Restore PINCTRL_MT8189 to tristate
From: Justin Yeh @ 2026-05-29 10:02 UTC (permalink / raw)
To: Sean Wang, Linus Walleij, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: Project_Global_Chrome_Upstream_Group, linux-mediatek, linux-gpio,
linux-kernel, linux-arm-kernel, Justin Yeh
In-Reply-To: <20260529100308.51271-1-justin.yeh@mediatek.com>
Under the GKI + vendor_dlkm model, vendor-specific pinctrl cannot be
built into the GKI vmlinux. Upstream's recent switch of PINCTRL_MT8189
to bool prevents building as a loadable module, which breaks DDK module
usage. Restore tristate so MT8189 pinctrl can be packaged as a kernel
module in vendor_dlkm.
Signed-off-by: Justin Yeh <justin.yeh@mediatek.com>
---
drivers/pinctrl/mediatek/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/pinctrl/mediatek/Kconfig b/drivers/pinctrl/mediatek/Kconfig
index 4819617d9368..a75434e7e989 100644
--- a/drivers/pinctrl/mediatek/Kconfig
+++ b/drivers/pinctrl/mediatek/Kconfig
@@ -270,7 +270,7 @@ config PINCTRL_MT8188
map specific eint which doesn't have real gpio pin.
config PINCTRL_MT8189
- bool "MediaTek MT8189 pin control"
+ tristate "MediaTek MT8189 pin control"
depends on OF
depends on ARM64 || COMPILE_TEST
default ARM64 && ARCH_MEDIATEK
--
2.45.2
^ permalink raw reply related
* [PATCH 2/2] pinctrl: mediatek: mt8189: Add MODULE_LICENSE declaration
From: Justin Yeh @ 2026-05-29 10:02 UTC (permalink / raw)
To: Sean Wang, Linus Walleij, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: Project_Global_Chrome_Upstream_Group, linux-mediatek, linux-gpio,
linux-kernel, linux-arm-kernel, Justin Yeh
In-Reply-To: <20260529100308.51271-1-justin.yeh@mediatek.com>
Add missing MODULE_LICENSE("GPL v2") macro to fix modpost error during
kernel module build. The license identifier matches the SPDX header
(GPL-2.0) at the top of the file.
This fixes the following build error:
ERROR: modpost: missing MODULE_LICENSE() in pinctrl-mt8189.o
Signed-off-by: Justin Yeh <justin.yeh@mediatek.com>
---
drivers/pinctrl/mediatek/pinctrl-mt8189.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/pinctrl/mediatek/pinctrl-mt8189.c b/drivers/pinctrl/mediatek/pinctrl-mt8189.c
index cd4cdff309a1..a9c128c514a4 100644
--- a/drivers/pinctrl/mediatek/pinctrl-mt8189.c
+++ b/drivers/pinctrl/mediatek/pinctrl-mt8189.c
@@ -1696,3 +1696,4 @@ static int __init mt8189_pinctrl_init(void)
arch_initcall(mt8189_pinctrl_init);
MODULE_DESCRIPTION("MediaTek MT8189 Pinctrl Driver");
+MODULE_LICENSE("GPL v2");
--
2.45.2
^ permalink raw reply related
* [PATCH 0/2] pinctrl: mediatek: Enable MT8189 as loadable module
From: Justin Yeh @ 2026-05-29 10:02 UTC (permalink / raw)
To: Sean Wang, Linus Walleij, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: Project_Global_Chrome_Upstream_Group, linux-mediatek, linux-gpio,
linux-kernel, linux-arm-kernel, Justin Yeh
This series enables PINCTRL_MT8189 to be built as a loadable kernel
module, which is required for GKI (Generic Kernel Image) + vendor_dlkm
deployments where vendor-specific drivers must be kept separate from
the GKI vmlinux.
Patch 1 restores the tristate option that was recently changed to bool,
preventing module builds. Patch 2 adds the missing MODULE_LICENSE macro
that's required when building as a module.
Together these changes allow MT8189 pinctrl to be properly packaged as
a vendor kernel module while maintaining the existing built-in option.
Justin Yeh (2):
pinctrl: mediatek: Restore PINCTRL_MT8189 to tristate
pinctrl: mediatek: mt8189: Add MODULE_LICENSE declaration
drivers/pinctrl/mediatek/Kconfig | 2 +-
drivers/pinctrl/mediatek/pinctrl-mt8189.c | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
--
2.45.2
^ permalink raw reply
* Re: [PATCH v1 2/3] regulator: Use named initializers for platform_device_id arrays
From: Matti Vaittinen @ 2026-05-29 8:02 UTC (permalink / raw)
To: Uwe Kleine-König (The Capable Hub)
Cc: Liam Girdwood, Mark Brown, Karel Balej, Marek Vasut, Chanwoo Choi,
Krzysztof Kozlowski, Matthias Brugger, AngeloGioacchino Del Regno,
Samuel Kayode, André Draszik, Aaro Koskinen, Andreas Kemnade,
Kevin Hilman, Roger Quadros, Tony Lindgren, linux-kernel,
linux-renesas-soc, linux-arm-kernel, linux-mediatek, imx,
linux-arm-msm, linux-samsung-soc, linux-omap
In-Reply-To: <d02f55dfd5bdd743ae5cd76f2a5af0d346226a68.1779878004.git.u.kleine-koenig@baylibre.com>
ke 27.5.2026 klo 13.48 Uwe Kleine-König (The Capable Hub)
(u.kleine-koenig@baylibre.com) kirjoitti:
>
> Named initializers are better readable and more robust to changes of the
> struct definition. This robustness is relevant for a planned change to
> struct platform_device_id replacing .driver_data by an anonymous unit.
>
> While touching these arrays unify spacing and usage of commas.
>
> Signed-off-by: Uwe Kleine-König (The Capable Hub) <u.kleine-koenig@baylibre.com>
Reviewed-by: Matti Vaittinen <mazziesaccount@gmail.com>
--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland
~~ When things go utterly wrong vim users can always type :help! ~~
Discuss - Estimate - Plan - Report and finally accomplish this:
void do_work(int time) __attribute__ ((const));
^ permalink raw reply
* Re: [net v2] net: pcs: pcs-mtk-lynxi: fix bpi-r3 serdes configuration
From: patchwork-bot+netdevbpf @ 2026-05-29 1:20 UTC (permalink / raw)
To: Frank Wunderlich
Cc: andrew, angelogioacchino.delregno, vladimir.oltean,
linux-mediatek, linux, daniel, edumazet, lynxis, linux-arm-kernel,
netdev, matthias.bgg, kuba, pabeni, davem, linux-kernel,
hkallweit1
In-Reply-To: <20260526153239.30194-1-linux@fw-web.de>
Hello:
This patch was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Tue, 26 May 2026 17:32:38 +0200 you wrote:
> From: Frank Wunderlich <frank-w@public-files.de>
>
> Commit 8871389da151 introduces common pcs dts properties which writes
> rx=normal,tx=normal polarity to register SGMSYS_QPHY_WRAP_CTRL of switch.
> This is initialized with tx-bit set and so change inverts polarity
> compared to before.
>
> [...]
Here is the summary with links:
- [net,v2] net: pcs: pcs-mtk-lynxi: fix bpi-r3 serdes configuration
https://git.kernel.org/netdev/net/c/422b5233b607
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [PATCH] Input: mtk-pmic-keys: Count available keys during probe instead of pre-counting
From: Rosen Penev @ 2026-05-28 23:56 UTC (permalink / raw)
To: linux-input
Cc: Dmitry Torokhov, Matthias Brugger, AngeloGioacchino Del Regno,
open list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support
Replace the separate of_get_available_child_count() pre-count and
validation step with a single pass through for_each_child_of_node_scoped().
Skip unavailable child nodes and bail out if more than
MTK_PMIC_MAX_KEY_COUNT available keys are found. Set nkeys after the
loop so suspend/resume iterate only over initialized entries.
Also use a key variable in the loop for clarity.
Use of_device_get_match_data() to fetch the PMIC key register data directly
instead of open-coding an of_match_device() lookup.
This also lets the driver drop the of_device.h include.
Assisted-by: OpenCode:BigPickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/input/keyboard/mtk-pmic-keys.c | 53 ++++++++++++--------------
1 file changed, 24 insertions(+), 29 deletions(-)
diff --git a/drivers/input/keyboard/mtk-pmic-keys.c b/drivers/input/keyboard/mtk-pmic-keys.c
index c78d9f6d97c4..e34856693ee2 100644
--- a/drivers/input/keyboard/mtk-pmic-keys.c
+++ b/drivers/input/keyboard/mtk-pmic-keys.c
@@ -16,7 +16,6 @@
#include <linux/mfd/mt6397/core.h>
#include <linux/mfd/mt6397/registers.h>
#include <linux/module.h>
-#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
@@ -147,6 +146,7 @@ struct mtk_pmic_keys {
struct input_dev *input_dev;
struct device *dev;
struct regmap *regmap;
+ unsigned int nkeys;
struct mtk_pmic_keys_info keys[MTK_PMIC_MAX_KEY_COUNT];
};
@@ -267,7 +267,7 @@ static int mtk_pmic_keys_suspend(struct device *dev)
struct mtk_pmic_keys *keys = dev_get_drvdata(dev);
int index;
- for (index = 0; index < MTK_PMIC_MAX_KEY_COUNT; index++) {
+ for (index = 0; index < keys->nkeys; index++) {
if (keys->keys[index].wakeup) {
enable_irq_wake(keys->keys[index].irq);
if (keys->keys[index].irq_r > 0)
@@ -283,7 +283,7 @@ static int mtk_pmic_keys_resume(struct device *dev)
struct mtk_pmic_keys *keys = dev_get_drvdata(dev);
int index;
- for (index = 0; index < MTK_PMIC_MAX_KEY_COUNT; index++) {
+ for (index = 0; index < keys->nkeys; index++) {
if (keys->keys[index].wakeup) {
disable_irq_wake(keys->keys[index].irq);
if (keys->keys[index].irq_r > 0)
@@ -325,24 +325,23 @@ MODULE_DEVICE_TABLE(of, of_mtk_pmic_keys_match_tbl);
static int mtk_pmic_keys_probe(struct platform_device *pdev)
{
int error, index = 0;
- unsigned int keycount;
struct mt6397_chip *pmic_chip = dev_get_drvdata(pdev->dev.parent);
struct device_node *node = pdev->dev.of_node;
static const char *const irqnames[] = { "powerkey", "homekey" };
static const char *const irqnames_r[] = { "powerkey_r", "homekey_r" };
struct mtk_pmic_keys *keys;
const struct mtk_pmic_regs *mtk_pmic_regs;
+ struct mtk_pmic_keys_info *key;
struct input_dev *input_dev;
- const struct of_device_id *of_id =
- of_match_device(of_mtk_pmic_keys_match_tbl, &pdev->dev);
keys = devm_kzalloc(&pdev->dev, sizeof(*keys), GFP_KERNEL);
if (!keys)
return -ENOMEM;
-
keys->dev = &pdev->dev;
keys->regmap = pmic_chip->regmap;
- mtk_pmic_regs = of_id->data;
+ mtk_pmic_regs = of_device_get_match_data(&pdev->dev);
+ if (!mtk_pmic_regs)
+ return -EINVAL;
keys->input_dev = input_dev = devm_input_allocate_device(keys->dev);
if (!input_dev) {
@@ -356,31 +355,26 @@ static int mtk_pmic_keys_probe(struct platform_device *pdev)
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0001;
- keycount = of_get_available_child_count(node);
- if (keycount > MTK_PMIC_MAX_KEY_COUNT ||
- keycount > ARRAY_SIZE(irqnames)) {
- dev_err(keys->dev, "too many keys defined (%d)\n", keycount);
- return -EINVAL;
- }
+ for_each_available_child_of_node_scoped(node, child) {
+ if (index >= MTK_PMIC_MAX_KEY_COUNT) {
+ dev_err(&pdev->dev, "too many keys defined\n");
+ return -EINVAL;
+ }
- for_each_child_of_node_scoped(node, child) {
- keys->keys[index].regs = &mtk_pmic_regs->keys_regs[index];
+ key = &keys->keys[index];
+ key->regs = &mtk_pmic_regs->keys_regs[index];
- keys->keys[index].irq =
- platform_get_irq_byname(pdev, irqnames[index]);
- if (keys->keys[index].irq < 0)
- return keys->keys[index].irq;
+ key->irq = platform_get_irq_byname(pdev, irqnames[index]);
+ if (key->irq < 0)
+ return key->irq;
if (mtk_pmic_regs->key_release_irq) {
- keys->keys[index].irq_r = platform_get_irq_byname(pdev,
- irqnames_r[index]);
-
- if (keys->keys[index].irq_r < 0)
- return keys->keys[index].irq_r;
+ key->irq_r = platform_get_irq_byname(pdev, irqnames_r[index]);
+ if (key->irq_r < 0)
+ return key->irq_r;
}
- error = of_property_read_u32(child,
- "linux,keycodes", &keys->keys[index].keycode);
+ error = of_property_read_u32(child, "linux,keycodes", &key->keycode);
if (error) {
dev_err(keys->dev,
"failed to read key:%d linux,keycode property: %d\n",
@@ -389,14 +383,15 @@ static int mtk_pmic_keys_probe(struct platform_device *pdev)
}
if (of_property_read_bool(child, "wakeup-source"))
- keys->keys[index].wakeup = true;
+ key->wakeup = true;
- error = mtk_pmic_key_setup(keys, &keys->keys[index]);
+ error = mtk_pmic_key_setup(keys, key);
if (error)
return error;
index++;
}
+ keys->nkeys = index;
error = input_register_device(input_dev);
if (error) {
--
2.54.0
^ permalink raw reply related
* [PATCH] pmdomain: mediatek: mfg: move __packed after struct name to fix kernel-doc
From: Rosen Penev @ 2026-05-28 20:24 UTC (permalink / raw)
To: linux-pm
Cc: Ulf Hansson, Matthias Brugger, AngeloGioacchino Del Regno,
open list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support
The kernel-doc parser cannot parse 'struct __packed mtk_mfg_opp_entry {'.
Move __packed to the closing brace, which is the more common kernel style.
Assisted-by: Opencode:Big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c
index 3ce6fb74dd53..53bdab66cf15 100644
--- a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c
+++ b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c
@@ -236,14 +236,14 @@ struct __packed mtk_mfg_ipi_sleep_msg {
*
* This struct is part of the ABI with the EB firmware. Do not change it.
*/
-struct __packed mtk_mfg_opp_entry {
+struct mtk_mfg_opp_entry {
__le32 freq_khz;
__le32 voltage_core;
__le32 voltage_sram;
__le32 posdiv;
__le32 voltage_margin;
__le32 power_mw;
-};
+} __packed;
struct mtk_mfg_mbox {
struct mbox_client cl;
--
2.54.0
^ permalink raw reply related
* [PATCH v3 4/4] ASoC: airoha: Add AFE and machine driver for Airoha AN7581
From: Christian Marangi @ 2026-05-28 17:48 UTC (permalink / raw)
To: Christian Marangi, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
Matthias Brugger, AngeloGioacchino Del Regno, Philipp Zabel,
Cyril Chao, Kuninori Morimoto, Chen-Yu Tsai, linux-sound,
devicetree, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260528174840.28644-1-ansuelsmth@gmail.com>
Add support for the Sound system present on Airoha AN7581 SoC. This is
based on the mediatek AFE drivers.
Also add the machine driver to create an actual sound card for the AFE.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
MAINTAINERS | 8 +
sound/soc/mediatek/Kconfig | 27 +-
sound/soc/mediatek/Makefile | 1 +
sound/soc/mediatek/an7581/Makefile | 9 +
sound/soc/mediatek/an7581/an7581-afe-common.h | 48 ++
sound/soc/mediatek/an7581/an7581-afe-pcm.c | 529 ++++++++++++++++++
sound/soc/mediatek/an7581/an7581-dai-etdm.c | 453 +++++++++++++++
sound/soc/mediatek/an7581/an7581-reg.h | 114 ++++
sound/soc/mediatek/an7581/an7581-wm8960.c | 156 ++++++
9 files changed, 1344 insertions(+), 1 deletion(-)
create mode 100644 sound/soc/mediatek/an7581/Makefile
create mode 100644 sound/soc/mediatek/an7581/an7581-afe-common.h
create mode 100644 sound/soc/mediatek/an7581/an7581-afe-pcm.c
create mode 100644 sound/soc/mediatek/an7581/an7581-dai-etdm.c
create mode 100644 sound/soc/mediatek/an7581/an7581-reg.h
create mode 100644 sound/soc/mediatek/an7581/an7581-wm8960.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 21c0ef0b9ce5..fe29a940209a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -762,6 +762,14 @@ F: Documentation/devicetree/bindings/phy/airoha,en7581-pcie-phy.yaml
F: drivers/phy/phy-airoha-pcie-regs.h
F: drivers/phy/phy-airoha-pcie.c
+AIROHA SOUND DRIVER
+M: Christian Marangi <ansuelsmth@gmail.com>
+L: linux-sound@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml
+F: Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml
+F: sound/soc/mediatek/an7581/*
+
AIROHA SPI SNFI DRIVER
M: Lorenzo Bianconi <lorenzo@kernel.org>
M: Ray Liu <ray.liu@airoha.com>
diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig
index 3a1e1fa3fe5c..c138f00f2efb 100644
--- a/sound/soc/mediatek/Kconfig
+++ b/sound/soc/mediatek/Kconfig
@@ -1,10 +1,35 @@
# SPDX-License-Identifier: GPL-2.0-only
-menu "Mediatek"
config SND_SOC_MEDIATEK
tristate
select REGMAP_MMIO
+menu "Airoha"
+
+config SND_SOC_AN7581
+ tristate "ASoC support for Airoha AN7581 chip"
+ depends on ARCH_AIROHA || COMPILE_TEST
+ select SND_SOC_MEDIATEK
+ help
+ This adds ASoC platform driver support for Airoha AN7581 chip
+ that can be used with other codecs.
+ Select Y if you have such device.
+ If unsure select "N".
+
+config SND_SOC_AN7581_WM8960
+ tristate "ASoc Audio driver for Airoha AN7581 with WM8960 codec"
+ depends on SND_SOC_AN7581 && I2C
+ select SND_SOC_WM8960
+ help
+ This adds support for ASoC machine driver for Airoha AN7581
+ boards with the WM8960 codecs.
+ Select Y if you have such device.
+ If unsure select "N".
+
+endmenu
+
+menu "Mediatek"
+
config SND_SOC_MT2701
tristate "ASoC support for Mediatek MT2701 chip"
depends on ARCH_MEDIATEK
diff --git a/sound/soc/mediatek/Makefile b/sound/soc/mediatek/Makefile
index 7cd67bce92e9..692d7a472ecc 100644
--- a/sound/soc/mediatek/Makefile
+++ b/sound/soc/mediatek/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SND_SOC_MEDIATEK) += common/
+obj-$(CONFIG_SND_SOC_AN7581) += an7581/
obj-$(CONFIG_SND_SOC_MT2701) += mt2701/
obj-$(CONFIG_SND_SOC_MT6797) += mt6797/
obj-$(CONFIG_SND_SOC_MT7986) += mt7986/
diff --git a/sound/soc/mediatek/an7581/Makefile b/sound/soc/mediatek/an7581/Makefile
new file mode 100644
index 000000000000..f48cd0269bea
--- /dev/null
+++ b/sound/soc/mediatek/an7581/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# platform driver
+snd-soc-an7581-afe-y := \
+ an7581-afe-pcm.o \
+ an7581-dai-etdm.o
+
+obj-$(CONFIG_SND_SOC_AN7581) += snd-soc-an7581-afe.o
+obj-$(CONFIG_SND_SOC_AN7581_WM8960) += an7581-wm8960.o
diff --git a/sound/soc/mediatek/an7581/an7581-afe-common.h b/sound/soc/mediatek/an7581/an7581-afe-common.h
new file mode 100644
index 000000000000..7bd67ff4010b
--- /dev/null
+++ b/sound/soc/mediatek/an7581/an7581-afe-common.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * an7581-afe-common.h -- Airoha AN7581 audio driver definitions
+ */
+
+#ifndef _AN7581_AFE_COMMON_H_
+#define _AN7581_AFE_COMMON_H_
+
+#include <linux/atomic.h>
+#include <sound/soc.h>
+#include <linux/list.h>
+#include <linux/regmap.h>
+#include "../../mediatek/common/mtk-base-afe.h"
+
+enum {
+ AN7581_MEMIF_DL1,
+ AN7581_MEMIF_UL1,
+ AN7581_MEMIF_NUM,
+ AN7581_DAI_ETDM = AN7581_MEMIF_NUM,
+ AN7581_DAI_NUM,
+};
+
+enum {
+ AN7581_IRQ_0,
+ AN7581_IRQ_1,
+ AN7581_IRQ_NUM,
+};
+
+struct an7581_memif_irq_desc {
+ u8 irq;
+ u32 status_bit;
+ u32 clear_reg;
+};
+
+struct an7581_afe_private {
+ atomic_t users;
+
+ /* dai */
+ void *dai_priv[AN7581_DAI_NUM];
+};
+
+unsigned int an7581_afe_rate_transform(struct device *dev,
+ unsigned int rate);
+
+/* dai register */
+int an7581_dai_etdm_register(struct mtk_base_afe *afe);
+
+#endif
diff --git a/sound/soc/mediatek/an7581/an7581-afe-pcm.c b/sound/soc/mediatek/an7581/an7581-afe-pcm.c
new file mode 100644
index 000000000000..d6c3fc09951c
--- /dev/null
+++ b/sound/soc/mediatek/an7581/an7581-afe-pcm.c
@@ -0,0 +1,529 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Airoha ALSA SoC AFE platform driver for AN7581
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#include "an7581-afe-common.h"
+#include "an7581-reg.h"
+#include "../common/mtk-afe-platform-driver.h"
+#include "../common/mtk-afe-fe-dai.h"
+
+enum {
+ ARH_AFE_RATE_8K = 0,
+ ARH_AFE_RATE_12K = 1,
+ ARH_AFE_RATE_16K = 2,
+ ARH_AFE_RATE_24K = 3,
+ ARH_AFE_RATE_32K = 4,
+ ARH_AFE_RATE_48K = 5,
+ ARH_AFE_RATE_96K = 6,
+ ARH_AFE_RATE_192K = 7,
+ ARH_AFE_RATE_384K = 8,
+ ARH_AFE_RATE_7K = 16,
+ ARH_AFE_RATE_11K = 17,
+ ARH_AFE_RATE_14K = 18,
+ ARH_AFE_RATE_22K = 19,
+ ARH_AFE_RATE_29K = 20,
+ ARH_AFE_RATE_44K = 21,
+ ARH_AFE_RATE_88K = 22,
+ ARH_AFE_RATE_176K = 23,
+ ARH_AFE_RATE_352K = 24,
+};
+
+unsigned int an7581_afe_rate_transform(struct device *dev, unsigned int rate)
+{
+ switch (rate) {
+ case 7350:
+ return ARH_AFE_RATE_7K;
+ case 8000:
+ return ARH_AFE_RATE_8K;
+ case 11025:
+ return ARH_AFE_RATE_11K;
+ case 12000:
+ return ARH_AFE_RATE_12K;
+ case 14700:
+ return ARH_AFE_RATE_14K;
+ case 16000:
+ return ARH_AFE_RATE_16K;
+ case 22050:
+ return ARH_AFE_RATE_22K;
+ case 24000:
+ return ARH_AFE_RATE_24K;
+ case 29400:
+ return ARH_AFE_RATE_29K;
+ case 32000:
+ return ARH_AFE_RATE_32K;
+ case 44100:
+ return ARH_AFE_RATE_44K;
+ case 48000:
+ return ARH_AFE_RATE_48K;
+ case 88200:
+ return ARH_AFE_RATE_88K;
+ case 96000:
+ return ARH_AFE_RATE_96K;
+ case 176400:
+ return ARH_AFE_RATE_176K;
+ case 192000:
+ return ARH_AFE_RATE_192K;
+ case 352800:
+ return ARH_AFE_RATE_352K;
+ case 384000:
+ return ARH_AFE_RATE_384K;
+ default:
+ dev_warn(dev, "%s(), rate %u invalid, using %d!!!\n",
+ __func__, rate, ARH_AFE_RATE_48K);
+ return ARH_AFE_RATE_48K;
+ }
+}
+
+static const struct an7581_memif_irq_desc an7581_memif_irq_descs[AN7581_MEMIF_NUM] = {
+ [AN7581_MEMIF_DL1] = {
+ .irq = AN7581_IRQ_0,
+ .status_bit = AFE_IRQ_STS_PLAY,
+ .clear_reg = AFE_IRQ_CON0,
+ },
+ [AN7581_MEMIF_UL1] = {
+ .irq = AN7581_IRQ_1,
+ .status_bit = AFE_IRQ_STS_RECORD,
+ .clear_reg = AFE_IRQ1_CON0,
+ },
+};
+
+static const struct snd_pcm_hardware an7581_afe_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .period_bytes_min = 512,
+ .period_bytes_max = 128 * 1024,
+ .periods_min = 2,
+ .periods_max = 256,
+ .buffer_bytes_max = 256 * 1024,
+ .fifo_size = 0,
+};
+
+static int an7581_memif_fs(struct snd_pcm_substream *substream,
+ unsigned int rate)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME);
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component);
+
+ return an7581_afe_rate_transform(afe->dev, rate);
+}
+
+static int an7581_irq_fs(struct snd_pcm_substream *substream,
+ unsigned int rate)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME);
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component);
+
+ return an7581_afe_rate_transform(afe->dev, rate);
+}
+
+static int an7581_afe_fe_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ ret = mtk_afe_fe_startup(substream, dai);
+ if (ret < 0)
+ return ret;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ ret = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ 0x2000, UINT_MAX);
+ if (ret < 0)
+ dev_err(afe->dev, "hw_constraint_minmax failed\n");
+ }
+
+ return ret;
+}
+
+const struct snd_soc_dai_ops an7581_afe_fe_ops = {
+ .startup = an7581_afe_fe_startup,
+ .shutdown = mtk_afe_fe_shutdown,
+ .hw_params = mtk_afe_fe_hw_params,
+ .hw_free = mtk_afe_fe_hw_free,
+ .prepare = mtk_afe_fe_prepare,
+ .trigger = mtk_afe_fe_trigger,
+};
+
+#define ARH_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver an7581_memif_dai_driver[] = {
+ /* FE DAIs: memory intefaces to CPU */
+ {
+ .name = "DL1",
+ .id = AN7581_MEMIF_DL1,
+ .playback = {
+ .stream_name = "DL1",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = ARH_PCM_FORMATS,
+ },
+ .ops = &an7581_afe_fe_ops,
+ },
+ {
+ .name = "UL1",
+ .id = AN7581_MEMIF_UL1,
+ .capture = {
+ .stream_name = "UL1",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = ARH_PCM_FORMATS,
+ },
+ .ops = &an7581_afe_fe_ops,
+ },
+};
+
+static const struct snd_soc_dapm_widget an7581_memif_widgets[] = {
+ /* DL */
+ SND_SOC_DAPM_MIXER("I032", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("I033", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ /* UL */
+ SND_SOC_DAPM_MIXER("O018", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("O019", SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route an7581_memif_routes[] = {
+ {"I032", NULL, "DL1"},
+ {"I033", NULL, "DL1"},
+ {"UL1", NULL, "O018"},
+ {"UL1", NULL, "O019"},
+ {"O018", NULL, "I150"},
+ {"O019", NULL, "I151"},
+};
+
+static const struct snd_soc_component_driver an7581_afe_pcm_dai_component = {
+ .name = "an7581-afe-pcm-dai",
+};
+
+static const struct mtk_base_memif_data memif_data[AN7581_MEMIF_NUM] = {
+ [AN7581_MEMIF_DL1] = {
+ .name = "DL1",
+ .id = AN7581_MEMIF_DL1,
+ .reg_ofs_base = AFE_DL1_BASE,
+ .reg_ofs_cur = AFE_DL1_CUR,
+ .reg_ofs_end = AFE_DL1_END,
+ .fs_reg = -1,
+ .fs_shift = -1,
+ .fs_maskbit = -1,
+ .mono_reg = -1,
+ .mono_shift = -1,
+ .hd_reg = AFE_DL1_CON0,
+ .hd_shift = AFE_HD_SHIFT,
+ .hd_align_reg = -1,
+ .hd_align_mshift = -1,
+ .enable_reg = AFE_DAC_CON0,
+ .enable_shift = AFE_DL1_ENABLE_SHIFT,
+ .msb_reg = -1,
+ .msb_shift = -1,
+ .agent_disable_reg = -1,
+ .agent_disable_shift = -1,
+ },
+ [AN7581_MEMIF_UL1] = {
+ .name = "UL1",
+ .id = AN7581_MEMIF_UL1,
+ .reg_ofs_base = AFE_UL1_BASE,
+ .reg_ofs_cur = AFE_UL1_CUR,
+ .reg_ofs_end = AFE_UL1_END,
+ .fs_reg = -1,
+ .fs_shift = -1,
+ .fs_maskbit = -1,
+ .mono_reg = -1,
+ .mono_shift = -1,
+ .hd_reg = AFE_UL1_CON0,
+ .hd_shift = AFE_HD_SHIFT,
+ .hd_align_reg = AFE_UL1_CON0,
+ .hd_align_mshift = AFE_HD_ALIGN_SHIFT,
+ .enable_reg = AFE_DAC_CON0,
+ .enable_shift = AFE_UL1_ENABLE_SHIFT,
+ .msb_reg = -1,
+ .msb_shift = -1,
+ .agent_disable_reg = -1,
+ .agent_disable_shift = -1,
+ },
+};
+
+static struct mtk_base_irq_data irq_data[AN7581_IRQ_NUM] = {
+ [AN7581_IRQ_0] = {
+ .id = AN7581_IRQ_0,
+ .irq_cnt_reg = AFE_IRQ_CNT,
+ .irq_cnt_shift = AFE_IRQ_CNT_SHIFT,
+ .irq_cnt_maskbit = AFE_IRQ_CNT_MASK,
+ .irq_en_reg = AFE_IRQ_CON0,
+ .irq_en_shift = AFE_IRQ_ON_SHIFT,
+ .irq_fs_reg = -1,
+ .irq_fs_shift = -1,
+ .irq_fs_maskbit = -1,
+ .irq_clr_reg = -1,
+ .irq_clr_shift = -1,
+ },
+ [AN7581_IRQ_1] = {
+ .id = AN7581_IRQ_1,
+ .irq_cnt_reg = AFE_IRQ1_CNT,
+ .irq_cnt_shift = AFE_IRQ_CNT_SHIFT,
+ .irq_cnt_maskbit = AFE_IRQ_CNT_MASK,
+ .irq_en_reg = AFE_IRQ1_CON0,
+ .irq_en_shift = AFE_IRQ_ON_SHIFT,
+ .irq_fs_reg = -1,
+ .irq_fs_shift = -1,
+ .irq_fs_maskbit = -1,
+ .irq_clr_reg = -1,
+ .irq_clr_shift = -1,
+ },
+};
+
+static const struct regmap_config an7581_afe_regmap_config = {
+ .name = "afe",
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = AFE_MAX_REGISTER,
+};
+
+static const struct regmap_config an7581_afe_irq1_regmap_config = {
+ .name = "afe_irq1",
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = AFE_IRQ1_MAX_REGISTER,
+};
+
+static irqreturn_t an7581_afe_irq_handler(int irq_id, void *dev)
+{
+ const struct an7581_memif_irq_desc *irq_desc;
+ struct mtk_base_afe *afe = dev;
+ struct regmap *irq_regmap;
+ u32 clear_reg, status;
+ int substream, i;
+
+ regmap_read(afe->regmap, AFE_IRQ_STS, &status);
+ if (status & AFE_IRQ_STS_PLAY)
+ substream = AN7581_MEMIF_DL1;
+ else
+ substream = AN7581_MEMIF_UL1;
+
+ irq_desc = &an7581_memif_irq_descs[substream];
+ irq_regmap = irq_data[irq_desc->irq].regmap;
+ clear_reg = irq_desc->clear_reg;
+
+ for (i = 0; i < AN7581_MEMIF_NUM; i++) {
+ struct mtk_base_afe_memif *memif = &afe->memif[i];
+
+ if (!memif->substream)
+ continue;
+
+ if (memif->irq_usage < 0)
+ continue;
+
+ irq_desc = &an7581_memif_irq_descs[i];
+ if (status & irq_desc->status_bit)
+ snd_pcm_period_elapsed(memif->substream);
+ }
+
+ regmap_set_bits(irq_regmap, clear_reg,
+ BIT(AFE_IRQ_CLR_SHIFT));
+ regmap_clear_bits(irq_regmap, clear_reg,
+ BIT(AFE_IRQ_CLR_SHIFT));
+
+ regmap_set_bits(irq_regmap, clear_reg,
+ BIT(AFE_IRQ_MISS_FLG_CLR_SHIFT));
+ regmap_clear_bits(irq_regmap, clear_reg,
+ BIT(AFE_IRQ_MISS_FLG_CLR_SHIFT));
+
+ return IRQ_HANDLED;
+}
+
+static int an7581_dai_memif_register(struct mtk_base_afe *afe)
+{
+ struct mtk_base_afe_dai *dai;
+
+ dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL);
+ if (!dai)
+ return -ENOMEM;
+
+ list_add(&dai->list, &afe->sub_dais);
+
+ dai->dai_drivers = an7581_memif_dai_driver;
+ dai->num_dai_drivers = ARRAY_SIZE(an7581_memif_dai_driver);
+
+ dai->dapm_widgets = an7581_memif_widgets;
+ dai->num_dapm_widgets = ARRAY_SIZE(an7581_memif_widgets);
+ dai->dapm_routes = an7581_memif_routes;
+ dai->num_dapm_routes = ARRAY_SIZE(an7581_memif_routes);
+
+ return 0;
+}
+
+typedef int (*dai_register_cb)(struct mtk_base_afe *);
+static const dai_register_cb dai_register_cbs[] = {
+ an7581_dai_etdm_register,
+ an7581_dai_memif_register,
+};
+
+static int an7581_afe_pcm_dev_probe(struct platform_device *pdev)
+{
+ struct an7581_afe_private *afe_priv;
+ struct reset_control *reset;
+ struct mtk_base_afe *afe;
+ struct device *dev;
+ int i, irq_id, ret;
+ void *base;
+
+ afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL);
+ if (!afe)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, afe);
+
+ afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv),
+ GFP_KERNEL);
+ if (!afe->platform_priv)
+ return -ENOMEM;
+
+ afe_priv = afe->platform_priv;
+ atomic_set(&afe_priv->users, 0);
+ afe->dev = &pdev->dev;
+ dev = afe->dev;
+
+ reset = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(reset))
+ return PTR_ERR(reset);
+
+ /* Global reset I2S */
+ reset_control_assert(reset);
+ usleep_range(10, 20);
+ reset_control_deassert(reset);
+
+ afe->base_addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(afe->base_addr))
+ return PTR_ERR(afe->base_addr);
+
+ base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr,
+ &an7581_afe_regmap_config);
+ if (IS_ERR(afe->regmap))
+ return PTR_ERR(afe->regmap);
+
+ irq_data[AN7581_IRQ_0].regmap = afe->regmap;
+ irq_data[AN7581_IRQ_1].regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &an7581_afe_irq1_regmap_config);
+ if (IS_ERR(irq_data[AN7581_IRQ_1].regmap))
+ return PTR_ERR(irq_data[AN7581_IRQ_1].regmap);
+
+ mutex_init(&afe->irq_alloc_lock);
+
+ /* irq initialize */
+ afe->irqs_size = AN7581_IRQ_NUM;
+ afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs),
+ GFP_KERNEL);
+ if (!afe->irqs)
+ return -ENOMEM;
+
+ for (i = 0; i < afe->irqs_size; i++)
+ afe->irqs[i].irq_data = &irq_data[i];
+
+ /* request irq */
+ irq_id = platform_get_irq(pdev, 0);
+ if (irq_id < 0)
+ return irq_id;
+
+ ret = devm_request_irq(dev, irq_id, an7581_afe_irq_handler,
+ IRQF_TRIGGER_NONE, "asys-isr", (void *)afe);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request irq for asys-isr\n");
+
+ /* init memif */
+ afe->memif_size = AN7581_MEMIF_NUM;
+ afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif),
+ GFP_KERNEL);
+ if (!afe->memif)
+ return -ENOMEM;
+
+ for (i = 0; i < afe->memif_size; i++) {
+ int sel_irq = an7581_memif_irq_descs[i].irq;
+
+ afe->memif[i].data = &memif_data[i];
+ afe->memif[i].irq_usage = sel_irq;
+ afe->memif[i].const_irq = 1;
+ afe->irqs[sel_irq].irq_occupyed = true;
+ }
+
+ /* init sub_dais */
+ INIT_LIST_HEAD(&afe->sub_dais);
+
+ for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) {
+ ret = dai_register_cbs[i](afe);
+ if (ret)
+ return dev_err_probe(dev, ret, "DAI register failed, i: %d\n", i);
+ }
+
+ /* init dai_driver and component_driver */
+ ret = mtk_afe_combine_sub_dai(afe);
+ if (ret)
+ return dev_err_probe(dev, ret, "mtk_afe_combine_sub_dai fail\n");
+
+ afe->mtk_afe_hardware = &an7581_afe_hardware;
+ afe->memif_fs = an7581_memif_fs;
+ afe->irq_fs = an7581_irq_fs;
+
+ /* register component */
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &mtk_afe_pcm_platform,
+ NULL, 0);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot register AFE component\n");
+
+ ret = devm_snd_soc_register_component(afe->dev,
+ &an7581_afe_pcm_dai_component,
+ afe->dai_drivers,
+ afe->num_dai_drivers);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot register PCM DAI component\n");
+
+ return 0;
+}
+
+static const struct of_device_id an7581_afe_pcm_dt_match[] = {
+ { .compatible = "airoha,an7581-afe" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, an7581_afe_pcm_dt_match);
+
+static struct platform_driver an7581_afe_pcm_driver = {
+ .driver = {
+ .name = "an7581-audio",
+ .of_match_table = an7581_afe_pcm_dt_match,
+ },
+ .probe = an7581_afe_pcm_dev_probe,
+};
+module_platform_driver(an7581_afe_pcm_driver);
+
+MODULE_DESCRIPTION("Airoha SoC AFE platform driver for ALSA AN7581");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mediatek/an7581/an7581-dai-etdm.c b/sound/soc/mediatek/an7581/an7581-dai-etdm.c
new file mode 100644
index 000000000000..1022bedea991
--- /dev/null
+++ b/sound/soc/mediatek/an7581/an7581-dai-etdm.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Airoha ALSA SoC Audio DAI eTDM Control
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include "an7581-afe-common.h"
+#include "an7581-reg.h"
+
+#define HOPPING_CLK 0
+#define APLL_CLK 1
+#define MTK_DAI_ETDM_FORMAT_I2S 0
+#define MTK_DAI_ETDM_FORMAT_DSPA 4
+#define MTK_DAI_ETDM_FORMAT_DSPB 5
+
+enum {
+ MTK_ETDM_RATE_8K = 0,
+ MTK_ETDM_RATE_12K = 1,
+ MTK_ETDM_RATE_16K = 2,
+ MTK_ETDM_RATE_24K = 3,
+ MTK_ETDM_RATE_32K = 4,
+ MTK_ETDM_RATE_48K = 5,
+ MTK_ETDM_RATE_96K = 6,
+ MTK_ETDM_RATE_192K = 7,
+ MTK_ETDM_RATE_384K = 8,
+ MTK_ETDM_RATE_7K = 16,
+ MTK_ETDM_RATE_11K = 17,
+ MTK_ETDM_RATE_14K = 18,
+ MTK_ETDM_RATE_22K = 19,
+ MTK_ETDM_RATE_29K = 20,
+ MTK_ETDM_RATE_44K = 21,
+ MTK_ETDM_RATE_88K = 22,
+ MTK_ETDM_RATE_176K = 23,
+ MTK_ETDM_RATE_352K = 24,
+};
+
+struct mtk_dai_etdm_priv {
+ bool bck_inv;
+ bool lrck_inv;
+ bool slave_mode;
+ unsigned int format;
+};
+
+static unsigned int an7581_etdm_rate_transform(struct device *dev, unsigned int rate)
+{
+ switch (rate) {
+ case 7350:
+ return MTK_ETDM_RATE_7K;
+ case 8000:
+ return MTK_ETDM_RATE_8K;
+ case 11025:
+ return MTK_ETDM_RATE_11K;
+ case 12000:
+ return MTK_ETDM_RATE_12K;
+ case 14700:
+ return MTK_ETDM_RATE_14K;
+ case 16000:
+ return MTK_ETDM_RATE_16K;
+ case 22050:
+ return MTK_ETDM_RATE_22K;
+ case 24000:
+ return MTK_ETDM_RATE_24K;
+ case 29400:
+ return MTK_ETDM_RATE_29K;
+ case 32000:
+ return MTK_ETDM_RATE_32K;
+ case 44100:
+ return MTK_ETDM_RATE_44K;
+ case 48000:
+ return MTK_ETDM_RATE_48K;
+ case 88200:
+ return MTK_ETDM_RATE_88K;
+ case 96000:
+ return MTK_ETDM_RATE_96K;
+ case 176400:
+ return MTK_ETDM_RATE_176K;
+ case 192000:
+ return MTK_ETDM_RATE_192K;
+ case 352800:
+ return MTK_ETDM_RATE_352K;
+ case 384000:
+ return MTK_ETDM_RATE_384K;
+ default:
+ dev_warn(dev, "%s(), rate %u invalid, using %d!!!\n",
+ __func__, rate, MTK_ETDM_RATE_48K);
+ return MTK_ETDM_RATE_48K;
+ }
+}
+
+static int get_etdm_wlen(unsigned int bitwidth)
+{
+ return bitwidth <= 16 ? 16 : 32;
+}
+
+static const struct snd_soc_dapm_widget mtk_dai_etdm_widgets[] = {
+ /* DL */
+ SND_SOC_DAPM_MIXER("I150", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("I151", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ /* UL */
+ SND_SOC_DAPM_MIXER("O124", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("O125", SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route mtk_dai_etdm_routes[] = {
+ {"I150", NULL, "ETDM Capture"},
+ {"I151", NULL, "ETDM Capture"},
+ {"ETDM Playback", NULL, "O124"},
+ {"ETDM Playback", NULL, "O125"},
+ {"O124", NULL, "I032"},
+ {"O125", NULL, "I033"},
+};
+
+/* dai ops */
+static int mtk_dai_etdm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ struct an7581_afe_private *afe_priv = afe->platform_priv;
+
+ regmap_set_bits(afe->regmap, AFE_DAC_CON0,
+ BIT(AFE_AFE_ENABLE_SHIFT));
+
+ atomic_inc(&afe_priv->users);
+
+ return 0;
+}
+
+static void mtk_dai_etdm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ struct an7581_afe_private *afe_priv = afe->platform_priv;
+
+ if (atomic_dec_and_test(&afe_priv->users))
+ regmap_clear_bits(afe->regmap, AFE_DAC_CON0,
+ BIT(AFE_AFE_ENABLE_SHIFT));
+}
+
+static unsigned int get_etdm_ch_fixup(unsigned int channels)
+{
+ if (channels > 16)
+ return 24;
+ else if (channels > 8)
+ return 16;
+ else if (channels > 4)
+ return 8;
+ else if (channels > 2)
+ return 4;
+ else
+ return 2;
+}
+
+static int mtk_dai_etdm_config(struct mtk_base_afe *afe,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai,
+ int stream)
+{
+ struct an7581_afe_private *afe_priv = afe->platform_priv;
+ struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id];
+ unsigned int rate = params_rate(params);
+ unsigned int etdm_rate = an7581_etdm_rate_transform(afe->dev, rate);
+ unsigned int channels = params_channels(params);
+ unsigned int bit_width = params_width(params);
+ unsigned int wlen = get_etdm_wlen(bit_width);
+ unsigned int val = 0, val1 = 0;
+ unsigned int mask = 0, mask1 = 0;
+
+ dev_dbg(afe->dev, "%s(), stream %d, rate %u, bitwidth %u\n",
+ __func__, stream, rate, bit_width);
+
+ /* CON0 */
+ mask |= ETDM_SLAVE_MODE;
+ if (etdm_data->slave_mode)
+ val |= ETDM_SLAVE_MODE;
+ mask |= ETDM_BIT_LEN;
+ val |= FIELD_PREP(ETDM_BIT_LEN, bit_width - 1);
+ mask |= ETDM_WRD_LEN;
+ val |= FIELD_PREP(ETDM_WRD_LEN, wlen - 1);
+ mask |= ETDM_FMT;
+ val |= FIELD_PREP(ETDM_FMT, etdm_data->format);
+ mask |= ETDM_CH_NUM;
+ val |= FIELD_PREP(ETDM_CH_NUM, get_etdm_ch_fixup(channels) - 1);
+
+ /* CON1 */
+ mask1 |= EDTM_LRCK_AUTO_MODE;
+ val1 |= EDTM_LRCK_AUTO_MODE;
+ mask1 |= EDTM_CKEN_SEL;
+ val1 |= EDTM_CKEN_SEL;
+ mask1 |= EDTM_LRCK_AUTO_OFF;
+ val1 |= EDTM_LRCK_AUTO_OFF;
+ mask1 |= EDTM_INITIAL_POINT;
+ val1 |= FIELD_PREP(EDTM_INITIAL_POINT, 14);
+ mask1 |= EDTM_INITIAL_COUNT;
+ val1 |= FIELD_PREP(EDTM_INITIAL_COUNT, 14);
+
+ switch (stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ /* set ETDM_OUT1_CON0 */
+ regmap_update_bits(afe->regmap, ETDM_OUT1_CON0, mask, val);
+
+ mask1 |= EDTM_DIRECT_INPUT_MASTER_BCK;
+ val1 |= EDTM_DIRECT_INPUT_MASTER_BCK;
+
+ /* set ETDM_OUT1_CON1 */
+ regmap_update_bits(afe->regmap, ETDM_OUT1_CON1, mask1, val1);
+
+ /* set ETDM_OUT1_CON4 */
+ regmap_update_bits(afe->regmap, ETDM_OUT1_CON4, OUT_SEL_FS,
+ FIELD_PREP(OUT_SEL_FS, etdm_rate));
+
+ regmap_update_bits(afe->irqs[AN7581_IRQ_0].irq_data->regmap,
+ afe->irqs[AN7581_IRQ_0].irq_data->irq_en_reg,
+ AFE_IRQ_EN_SEL, AFE_IRQ_EN_SEL_I2SOUT);
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ /* set ETDM_IN1_CON0 */
+ regmap_update_bits(afe->regmap, ETDM_IN1_CON0, mask, val);
+
+ /* set ETDM_IN1_CON1 */
+ regmap_update_bits(afe->regmap, ETDM_IN1_CON1, mask1, val1);
+
+ /* set ETDM_IN1_CON3 */
+ regmap_update_bits(afe->regmap, ETDM_IN1_CON3, IN_SEL_FS,
+ FIELD_PREP(IN_SEL_FS, etdm_rate));
+
+ regmap_update_bits(afe->irqs[AN7581_IRQ_1].irq_data->regmap,
+ afe->irqs[AN7581_IRQ_1].irq_data->irq_en_reg,
+ AFE_IRQ_EN_SEL, AFE_IRQ_EN_SEL_I2SIN);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int mtk_dai_etdm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ unsigned int rate = params_rate(params);
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+
+ regmap_update_bits(afe->regmap, ETDM_COWORK_CON0,
+ EDTM_IN1_SLAVE_SEL,
+ EDTM_IN1_SLAVE_FROM_ETDMIN1_SLAVE);
+ regmap_update_bits(afe->regmap, ETDM_COWORK_CON0,
+ EDTM_OUT1_SLAVE_SEL,
+ EDTM_OUT1_SLAVE_FROM_ETDMOUT1_SLAVE);
+ regmap_update_bits(afe->regmap, ETDM_COWORK_CON1,
+ EDTM_IN1_SDATA0_SEL,
+ EDTM_IN1_SDATA0_FROM_PAD);
+
+ switch (rate) {
+ case 7350:
+ case 8000:
+ case 11025:
+ case 12000:
+ case 14700:
+ case 16000:
+ case 22050:
+ case 24000:
+ case 29400:
+ case 32000:
+ case 44100:
+ case 48000:
+ case 88200:
+ case 96000:
+ case 176400:
+ case 192000:
+ case 352800:
+ case 384000:
+ mtk_dai_etdm_config(afe, params, dai, substream->stream);
+ return 0;
+ default:
+ dev_err(afe->dev,
+ "Sample rate %d invalid\n",
+ rate);
+ return -EINVAL;
+ }
+}
+
+static int mtk_dai_etdm_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+
+ dev_dbg(afe->dev, "%s(), cmd %d, dai id %d\n", __func__, cmd, dai->id);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ regmap_set_bits(afe->regmap, ETDM_OUT1_CON0,
+ ETDM_OUT_EN);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ regmap_set_bits(afe->regmap, ETDM_IN1_CON0,
+ ETDM_IN_EN);
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ regmap_clear_bits(afe->regmap, ETDM_OUT1_CON0,
+ ETDM_OUT_EN);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ regmap_clear_bits(afe->regmap, ETDM_IN1_CON0,
+ ETDM_IN_EN);
+
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int mtk_dai_etdm_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ struct an7581_afe_private *afe_priv = afe->platform_priv;
+ struct mtk_dai_etdm_priv *etdm_data;
+ void *priv_data;
+
+ switch (dai->id) {
+ case AN7581_DAI_ETDM:
+ break;
+ default:
+ dev_warn(afe->dev, "%s(), id %d not support\n",
+ __func__, dai->id);
+ return -EINVAL;
+ }
+
+ priv_data = devm_kzalloc(afe->dev, sizeof(struct mtk_dai_etdm_priv),
+ GFP_KERNEL);
+ if (!priv_data)
+ return -ENOMEM;
+
+ afe_priv->dai_priv[dai->id] = priv_data;
+ etdm_data = afe_priv->dai_priv[dai->id];
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ etdm_data->format = MTK_DAI_ETDM_FORMAT_I2S;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ etdm_data->format = MTK_DAI_ETDM_FORMAT_DSPA;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ etdm_data->format = MTK_DAI_ETDM_FORMAT_DSPB;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ etdm_data->bck_inv = false;
+ etdm_data->lrck_inv = false;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ etdm_data->bck_inv = false;
+ etdm_data->lrck_inv = true;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ etdm_data->bck_inv = true;
+ etdm_data->lrck_inv = false;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ etdm_data->bck_inv = true;
+ etdm_data->lrck_inv = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ etdm_data->slave_mode = false;
+ break;
+ case SND_SOC_DAIFMT_BC_FC:
+ etdm_data->slave_mode = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops mtk_dai_etdm_ops = {
+ .startup = mtk_dai_etdm_startup,
+ .shutdown = mtk_dai_etdm_shutdown,
+ .hw_params = mtk_dai_etdm_hw_params,
+ .trigger = mtk_dai_etdm_trigger,
+ .set_fmt = mtk_dai_etdm_set_fmt,
+};
+
+/* dai driver */
+#define MTK_ETDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver mtk_dai_etdm_driver[] = {
+ {
+ .name = "ETDM",
+ .id = AN7581_DAI_ETDM,
+ .capture = {
+ .stream_name = "ETDM Capture",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = MTK_ETDM_FORMATS,
+ },
+ .playback = {
+ .stream_name = "ETDM Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = MTK_ETDM_FORMATS,
+ },
+ .ops = &mtk_dai_etdm_ops,
+ .symmetric_rate = 1,
+ .symmetric_sample_bits = 1,
+ },
+};
+
+int an7581_dai_etdm_register(struct mtk_base_afe *afe)
+{
+ struct mtk_base_afe_dai *dai;
+
+ dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL);
+ if (!dai)
+ return -ENOMEM;
+
+ list_add(&dai->list, &afe->sub_dais);
+
+ dai->dai_drivers = mtk_dai_etdm_driver;
+ dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_etdm_driver);
+
+ dai->dapm_widgets = mtk_dai_etdm_widgets;
+ dai->num_dapm_widgets = ARRAY_SIZE(mtk_dai_etdm_widgets);
+ dai->dapm_routes = mtk_dai_etdm_routes;
+ dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_etdm_routes);
+
+ return 0;
+}
diff --git a/sound/soc/mediatek/an7581/an7581-reg.h b/sound/soc/mediatek/an7581/an7581-reg.h
new file mode 100644
index 000000000000..a6ada3ca97ab
--- /dev/null
+++ b/sound/soc/mediatek/an7581/an7581-reg.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * an7581-reg.h -- Airoha AN7581 audio driver reg definition
+ */
+
+#ifndef _AN7581_REG_H_
+#define _AN7581_REG_H_
+
+#define AFE_DAC_CON0 0x0
+#define AFE_DL1_ENABLE_SHIFT 17
+#define AFE_UL1_ENABLE_SHIFT 1
+#define AFE_AFE_ENABLE_SHIFT 0
+
+#define ETDM_COWORK_CON0 0x4c
+#define EDTM_IN1_SLAVE_SEL GENMASK(27, 24)
+#define EDTM_IN1_SLAVE_FROM_ETDMIN1_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x1)
+#define EDTM_IN1_SLAVE_FROM_ETDMIN2_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x2)
+#define EDTM_IN1_SLAVE_FROM_ETDMIN2_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x3)
+#define EDTM_IN1_SLAVE_FROM_ETDMOUT1_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x8)
+#define EDTM_IN1_SLAVE_FROM_ETDMOUT1_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x9)
+#define EDTM_IN1_SLAVE_FROM_ETDMOUT2_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0xa)
+#define EDTM_IN1_SLAVE_FROM_ETDMOUT2_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0xb)
+#define EDTM_OUT1_SLAVE_SEL GENMASK(11, 8)
+#define EDTM_OUT1_SLAVE_FROM_ETDMIN1_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x0)
+#define EDTM_OUT1_SLAVE_FROM_ETDMIN1_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x1)
+#define EDTM_OUT1_SLAVE_FROM_ETDMIN2_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x2)
+#define EDTM_OUT1_SLAVE_FROM_ETDMIN2_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x3)
+#define EDTM_OUT1_SLAVE_FROM_ETDMOUT1_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x9)
+#define EDTM_OUT1_SLAVE_FROM_ETDMOUT2_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0xa)
+#define EDTM_OUT1_SLAVE_FROM_ETDMOUT2_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0xb)
+#define ETDM_COWORK_CON1 0x50
+#define EDTM_IN1_SDATA0_SEL GENMASK(3, 0)
+#define EDTM_IN1_SDATA0_FROM_PAD FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x0)
+#define EDTM_IN1_SDATA0_FROM_ETDMIN2 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x2)
+#define EDTM_IN1_SDATA0_FROM_ETDMOUT1 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x8)
+#define EDTM_IN1_SDATA0_FROM_ETDMOUT2 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0xa)
+#define ETDM_IN1_CON0 0x5c
+#define ETDM_CH_NUM GENMASK(26, 23)
+#define ETDM_WRD_LEN GENMASK(20, 16)
+#define ETDM_BIT_LEN GENMASK(15, 11)
+#define ETDM_FMT GENMASK(8, 6)
+#define ETDM_SLAVE_MODE BIT(5)
+#define ETDM_IN_EN BIT(0)
+#define ETDM_IN1_CON1 0x60
+#define EDTM_LRCK_AUTO_MODE BIT(29)
+#define EDTM_DIRECT_INPUT_MASTER_BCK BIT(30)
+#define EDTM_CKEN_SEL BIT(12)
+#define EDTM_LRCK_AUTO_OFF BIT(10)
+#define EDTM_INITIAL_POINT GENMASK(9, 5)
+#define EDTM_INITIAL_COUNT GENMASK(4, 0)
+#define ETDM_IN1_CON2 0x64
+#define IN_CLK_SRC GENMASK(12, 10)
+#define ETDM_IN1_CON3 0x68
+#define IN_SEL_FS GENMASK(30, 26)
+#define ETDM_IN1_CON4 0x6c
+#define IN_RELATCH GENMASK(24, 20)
+#define IN_CLK_INV BIT(18)
+#define ETDM_IN1_CON5 0x70
+#define ETDM_IN1_CON6 0x74
+#define ETDM_OUT1_CON0 0x7c
+#define ETDM_SYNC_MODE BIT(1)
+#define ETDM_OUT_EN BIT(0)
+#define ETDM_OUT1_CON1 0x80
+#define ETDM_OUT1_CON2 0x84
+#define ETDM_OUT1_CON3 0x88
+#define ETDM_OUT1_CON4 0x8c
+#define OUT_RELATCH GENMASK(28, 24)
+#define OUT_CLK_SRC GENMASK(8, 6)
+#define OUT_SEL_FS GENMASK(4, 0)
+#define ETDM_OUT1_CON5 0x90
+#define ETDM_CLK_DIV BIT(12)
+#define OUT_CLK_INV BIT(9)
+#define ETDM_OUT1_CON6 0x94
+#define ETDM_OUT1_CON7 0x98
+
+#define AFE_DL1_BASE 0xa8
+#define AFE_DL1_END 0xb0
+#define AFE_DL1_CUR 0xac
+#define AFE_DL1_CON0 0xb4
+#define AFE_PBUF_SIZE_SHIFT 16
+#define AFE_PBUF_SIZE_MASK GENMASK(1, 0)
+#define AFE_MINLEN_SHIFT 8
+#define AFE_MINLEN_MASK GENMASK(3, 0)
+#define AFE_HD_SHIFT 5
+
+#define AFE_UL1_BASE 0xc4
+#define AFE_UL1_END 0xc8
+#define AFE_UL1_CUR 0xcc
+#define AFE_UL1_CON0 0xd0
+#define AFE_HD_ALIGN_SHIFT 6
+
+#define AFE_IRQ_CON0 0xe4
+#define AFE_IRQ_EN_SEL BIT(4)
+#define AFE_IRQ_EN_SEL_I2SIN FIELD_PREP_CONST(AFE_IRQ_EN_SEL, 0x0)
+#define AFE_IRQ_EN_SEL_I2SOUT FIELD_PREP_CONST(AFE_IRQ_EN_SEL, 0x1)
+#define AFE_IRQ_MISS_FLG_CLR_SHIFT 3
+#define AFE_IRQ_CLR_SHIFT 2
+#define AFE_IRQ_ON_SHIFT 0
+#define AFE_IRQ_CNT 0xe8
+#define AFE_IRQ_CNT_SHIFT 0
+#define AFE_IRQ_CNT_MASK GENMASK(31, 0)
+
+#define AFE_IRQ_STS 0xf8
+#define AFE_IRQ_STS_PLAY BIT(1)
+#define AFE_IRQ_STS_RECORD BIT(0)
+
+#define AFE_MAX_REGISTER AFE_IRQ_STS
+
+#define AFE_IRQ1_CON0 0x0
+#define AFE_IRQ1_CNT 0x4
+
+#define AFE_IRQ1_MAX_REGISTER AFE_IRQ1_CNT
+
+#endif
diff --git a/sound/soc/mediatek/an7581/an7581-wm8960.c b/sound/soc/mediatek/an7581/an7581-wm8960.c
new file mode 100644
index 000000000000..4e60e112018c
--- /dev/null
+++ b/sound/soc/mediatek/an7581/an7581-wm8960.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Airoha ALSA SoC I2S platform driver for AN7581
+ *
+ */
+
+#include <linux/module.h>
+#include <sound/soc.h>
+
+#include "an7581-afe-common.h"
+
+SND_SOC_DAILINK_DEFS(playback,
+ DAILINK_COMP_ARRAY(COMP_CPU("DL1")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(capture,
+ DAILINK_COMP_ARRAY(COMP_CPU("UL1")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(codec,
+ DAILINK_COMP_ARRAY(COMP_CPU("ETDM")),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link an7581_wm8960_dai_links[] = {
+ /* FE */
+ {
+ .name = "wm8960-playback",
+ .stream_name = "wm8960-playback",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST,
+ SND_SOC_DPCM_TRIGGER_POST},
+ .dynamic = 1,
+ .playback_only = 1,
+ SND_SOC_DAILINK_REG(playback),
+ },
+ {
+ .name = "wm8960-capture",
+ .stream_name = "wm8960-capture",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST,
+ SND_SOC_DPCM_TRIGGER_POST},
+ .dynamic = 1,
+ .capture_only = 1,
+ SND_SOC_DAILINK_REG(capture),
+ },
+ /* BE */
+ {
+ .name = "wm8960-codec",
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBP_CFP |
+ SND_SOC_DAIFMT_GATED,
+ SND_SOC_DAILINK_REG(codec),
+ },
+};
+
+static struct snd_soc_card an7581_wm8960_card = {
+ .name = "an7581-wm8960",
+ .owner = THIS_MODULE,
+ .dai_link = an7581_wm8960_dai_links,
+ .num_links = ARRAY_SIZE(an7581_wm8960_dai_links),
+};
+
+static int an7581_wm8960_machine_probe(struct platform_device *pdev)
+{
+ struct device_node *platform_dai_node, *codec_dai_node;
+ struct snd_soc_card *card = &an7581_wm8960_card;
+ struct device_node *platform, *codec;
+ struct snd_soc_dai_link *dai_link;
+ int ret, i;
+
+ card->dev = &pdev->dev;
+
+ platform = of_get_child_by_name(pdev->dev.of_node, "platform");
+
+ if (platform) {
+ platform_dai_node = of_parse_phandle(platform, "sound-dai", 0);
+ of_node_put(platform);
+
+ if (!platform_dai_node) {
+ dev_err(&pdev->dev, "Failed to parse platform/sound-dai property\n");
+ return -EINVAL;
+ }
+ } else {
+ dev_err(&pdev->dev, "Property 'platform' missing or invalid\n");
+ return -EINVAL;
+ }
+
+ for_each_card_prelinks(card, i, dai_link) {
+ if (dai_link->platforms->name)
+ continue;
+ dai_link->platforms->of_node = platform_dai_node;
+ }
+
+ codec = of_get_child_by_name(pdev->dev.of_node, "codec");
+
+ if (codec) {
+ codec_dai_node = of_parse_phandle(codec, "sound-dai", 0);
+ of_node_put(codec);
+
+ if (!codec_dai_node) {
+ of_node_put(platform_dai_node);
+ dev_err(&pdev->dev, "Failed to parse codec/sound-dai property\n");
+ return -EINVAL;
+ }
+ } else {
+ of_node_put(platform_dai_node);
+ dev_err(&pdev->dev, "Property 'codec' missing or invalid\n");
+ return -EINVAL;
+ }
+
+ for_each_card_prelinks(card, i, dai_link) {
+ if (dai_link->codecs->name)
+ continue;
+ dai_link->codecs->of_node = codec_dai_node;
+ }
+
+ ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to parse audio-routing: %d\n", ret);
+ goto err_of_node_put;
+ }
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret) {
+ dev_err_probe(&pdev->dev, ret, "%s snd_soc_register_card fail\n", __func__);
+ goto err_of_node_put;
+ }
+
+ return 0;
+
+err_of_node_put:
+ of_node_put(platform_dai_node);
+ of_node_put(codec_dai_node);
+ return ret;
+}
+
+static const struct of_device_id an7581_wm8960_machine_dt_match[] = {
+ { .compatible = "airoha,an7581-wm8960-sound" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, an7581_wm8960_machine_dt_match);
+
+static struct platform_driver an7581_wm8960_driver = {
+ .driver = {
+ .name = "an7581-wm8960",
+ .of_match_table = an7581_wm8960_machine_dt_match,
+ },
+ .probe = an7581_wm8960_machine_probe,
+};
+module_platform_driver(an7581_wm8960_driver);
+
+MODULE_DESCRIPTION("Airoha SoC I2S platform driver for ALSA AN7581");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH v3 3/4] ASoC: mediatek: common: permit to provide dedicated regmap for irq
From: Christian Marangi @ 2026-05-28 17:48 UTC (permalink / raw)
To: Christian Marangi, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
Matthias Brugger, AngeloGioacchino Del Regno, Philipp Zabel,
Cyril Chao, Kuninori Morimoto, Chen-Yu Tsai, linux-sound,
devicetree, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260528174840.28644-1-ansuelsmth@gmail.com>
Some SoC might require dedicated regmap to configure some specific IRQ
register.
Add an extra entry in the irq_data struct and use the specific regmap if
defined.
If not defined then the global AFE regmap is used instead.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
sound/soc/mediatek/common/mtk-afe-fe-dai.c | 14 +++++++++-----
sound/soc/mediatek/common/mtk-base-afe.h | 2 ++
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c
index 3809068f5620..a8f4e70c8213 100644
--- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c
+++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c
@@ -204,11 +204,15 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd,
struct mtk_base_afe_irq *irqs = &afe->irqs[memif->irq_usage];
const struct mtk_base_irq_data *irq_data = irqs->irq_data;
unsigned int counter = runtime->period_size;
+ struct regmap *regmap = afe->regmap;
int fs;
int ret;
dev_dbg(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd);
+ if (irq_data->regmap)
+ regmap = irq_data->regmap;
+
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
@@ -220,7 +224,7 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd,
}
/* set irq counter */
- mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg,
+ mtk_regmap_update_bits(regmap, irq_data->irq_cnt_reg,
irq_data->irq_cnt_maskbit, counter,
irq_data->irq_cnt_shift);
@@ -230,12 +234,12 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd,
if (fs < 0)
return -EINVAL;
- mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg,
+ mtk_regmap_update_bits(regmap, irq_data->irq_fs_reg,
irq_data->irq_fs_maskbit, fs,
irq_data->irq_fs_shift);
/* enable interrupt */
- mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg,
+ mtk_regmap_update_bits(regmap, irq_data->irq_en_reg,
1, 1, irq_data->irq_en_shift);
return 0;
@@ -248,10 +252,10 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd,
}
/* disable interrupt */
- mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg,
+ mtk_regmap_update_bits(regmap, irq_data->irq_en_reg,
1, 0, irq_data->irq_en_shift);
/* and clear pending IRQ */
- mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg,
+ mtk_regmap_write(regmap, irq_data->irq_clr_reg,
1 << irq_data->irq_clr_shift);
return ret;
default:
diff --git a/sound/soc/mediatek/common/mtk-base-afe.h b/sound/soc/mediatek/common/mtk-base-afe.h
index a406f2e3e7a8..76d010f853f4 100644
--- a/sound/soc/mediatek/common/mtk-base-afe.h
+++ b/sound/soc/mediatek/common/mtk-base-afe.h
@@ -87,6 +87,8 @@ struct mtk_base_irq_data {
int irq_clr_reg;
int irq_clr_shift;
int irq_status_shift;
+
+ struct regmap *regmap;
};
struct device;
--
2.53.0
^ permalink raw reply related
* [PATCH v3 1/4] ASoC: dt-bindings: Add Airoha AN7581 AFE Sound card.
From: Christian Marangi @ 2026-05-28 17:48 UTC (permalink / raw)
To: Christian Marangi, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
Matthias Brugger, AngeloGioacchino Del Regno, Philipp Zabel,
Cyril Chao, Kuninori Morimoto, Chen-Yu Tsai, linux-sound,
devicetree, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260528174840.28644-1-ansuelsmth@gmail.com>
Add YAML schema for Airoha AN7581 AFE SoC sound card.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
.../bindings/sound/airoha,an7581-afe.yaml | 41 +++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml
diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml
new file mode 100644
index 000000000000..80d9e87f1470
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/airoha,an7581-afe.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Airoha AFE PCM controller for AN7581
+
+maintainers:
+ - Christian Marangi <ansuelsmth@gmail.com>
+
+properties:
+ compatible:
+ const: airoha,an7581-afe
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ afe@1fbe2200 {
+ compatible = "airoha,an7581-afe";
+ reg = <0x1fbe2200 0x9000>;
+
+ interrupts = <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+...
--
2.53.0
^ permalink raw reply related
* [PATCH v3 2/4] ASoC: dt-bindings: Add Airoha AN7581 AFE with WM8960 Codec schema
From: Christian Marangi @ 2026-05-28 17:48 UTC (permalink / raw)
To: Christian Marangi, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
Matthias Brugger, AngeloGioacchino Del Regno, Philipp Zabel,
Cyril Chao, Kuninori Morimoto, Chen-Yu Tsai, linux-sound,
devicetree, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260528174840.28644-1-ansuelsmth@gmail.com>
Add YAML schema for Airoha AN7581 AFE with the specific WM8960 i2c Codec.
This gives example on how to define and connect the AFE driver with the
WM9860 for full functionality.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
.../bindings/sound/airoha,an7581-wm8960.yaml | 71 +++++++++++++++++++
1 file changed, 71 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml
diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml
new file mode 100644
index 000000000000..b637c294657f
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/airoha,an7581-wm8960.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Airoha AN7581 sound card with WM8960 codec
+
+maintainers:
+ - Christian Marangi <ansuelsmth@gmail.com>
+
+allOf:
+ - $ref: sound-card-common.yaml#
+
+properties:
+ compatible:
+ const: airoha,an7581-wm8960-sound
+
+ platform:
+ type: object
+
+ properties:
+ sound-dai:
+ items:
+ - description: The phandle of AN7581 platform.
+
+ required:
+ - sound-dai
+
+ additionalProperties: false
+
+ codec:
+ type: object
+
+ properties:
+ sound-dai:
+ items:
+ - description: The phandle of WM8960 i2c codec.
+
+ required:
+ - sound-dai
+
+ additionalProperties: false
+
+unevaluatedProperties: false
+
+required:
+ - compatible
+ - audio-routing
+ - platform
+ - codec
+
+examples:
+ - |
+ sound {
+ compatible = "airoha,an7581-wm8960-sound";
+ model = "an7581-wm8960";
+ audio-routing =
+ "Headphone", "HP_L",
+ "Headphone", "HP_R",
+ "LINPUT1", "AMIC",
+ "RINPUT1", "AMIC";
+
+ platform {
+ sound-dai = <&afe>;
+ };
+
+ codec {
+ sound-dai = <&wm8960>;
+ };
+ };
--
2.53.0
^ permalink raw reply related
* [PATCH v3 0/4] ASoC: Add support for Airoha AN7581
From: Christian Marangi @ 2026-05-28 17:48 UTC (permalink / raw)
To: Christian Marangi, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
Matthias Brugger, AngeloGioacchino Del Regno, Philipp Zabel,
Cyril Chao, Kuninori Morimoto, Chen-Yu Tsai, linux-sound,
devicetree, linux-kernel, linux-arm-kernel, linux-mediatek
Add all the patch and documentation to support Airoha AN7581
sound driver.
The card follows similar implementation with Mediatek with
the codec present on i2c but with a simplified implementation
register wise and additional frequency supported.
Changes v3:
- Major fix to the ETDM code
- Add duplex support
- Improve Kconfig setup
- Rework and improve YAML schema
Changes v2:
- Fix wrong edit in MAINTAINERS file
Christian Marangi (4):
ASoC: dt-bindings: Add Airoha AN7581 AFE Sound card.
ASoC: dt-bindings: Add Airoha AN7581 AFE with WM8960 Codec schema
ASoC: mediatek: common: permit to provide dedicated regmap for irq
ASoC: airoha: Add AFE and machine driver for Airoha AN7581
.../bindings/sound/airoha,an7581-afe.yaml | 41 ++
.../bindings/sound/airoha,an7581-wm8960.yaml | 71 +++
MAINTAINERS | 8 +
sound/soc/mediatek/Kconfig | 27 +-
sound/soc/mediatek/Makefile | 1 +
sound/soc/mediatek/an7581/Makefile | 9 +
sound/soc/mediatek/an7581/an7581-afe-common.h | 48 ++
sound/soc/mediatek/an7581/an7581-afe-pcm.c | 529 ++++++++++++++++++
sound/soc/mediatek/an7581/an7581-dai-etdm.c | 453 +++++++++++++++
sound/soc/mediatek/an7581/an7581-reg.h | 114 ++++
sound/soc/mediatek/an7581/an7581-wm8960.c | 156 ++++++
sound/soc/mediatek/common/mtk-afe-fe-dai.c | 14 +-
sound/soc/mediatek/common/mtk-base-afe.h | 2 +
13 files changed, 1467 insertions(+), 6 deletions(-)
create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml
create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml
create mode 100644 sound/soc/mediatek/an7581/Makefile
create mode 100644 sound/soc/mediatek/an7581/an7581-afe-common.h
create mode 100644 sound/soc/mediatek/an7581/an7581-afe-pcm.c
create mode 100644 sound/soc/mediatek/an7581/an7581-dai-etdm.c
create mode 100644 sound/soc/mediatek/an7581/an7581-reg.h
create mode 100644 sound/soc/mediatek/an7581/an7581-wm8960.c
--
2.53.0
^ permalink raw reply
* Re: [PATCH net-next 1/6] net: airoha: Introduce airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-28 16:09 UTC (permalink / raw)
To: Alexander Lobakin
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, linux-arm-kernel, linux-mediatek,
netdev, Xuegang Lu
In-Reply-To: <433b9d9a-bd7c-44ad-b387-a789b326488e@intel.com>
[-- Attachment #1: Type: text/plain, Size: 2025 bytes --]
On May 28, Alexander Lobakin wrote:
> From: Lorenzo Bianconi <lorenzo@kernel.org>
> Date: Wed, 27 May 2026 12:21:15 +0200
>
> > EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
> > or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
> > As a result multiple net_devices can connect to the same GDM{3,4} port
> > and there is a theoretical "1:n" relation between GDM port and
> > net_devices.
> > Introduce airoha_gdm_dev struct to collect net_device related info (e.g.
> > net_device and external phy pointer). Please note this is just a
> > preliminary patch and we are still supporting a single net_device for
> > each GDM port. Subsequent patches will add support for multiple net_devices
> > connected to the same GDM port.
> >
> > Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
> > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
>
> [...]
>
> > diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> > index d3781103abb5..c78cabbec753 100644
> > --- a/drivers/net/ethernet/airoha/airoha_eth.h
> > +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> > @@ -535,10 +535,15 @@ struct airoha_qdma {
> > struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
> > };
> >
> > +struct airoha_gdm_dev {
> > + struct airoha_gdm_port *port;
> > + struct net_device *dev;
>
> Nit: we now have priv_to_netdev() (I hope I remember its name
> correctly), so that we no longer need to store a netdev backpointer in
> netdev_priv structures.
> Just an option, up to you.
I think you mean netdev_from_priv(), right?
I guess I can fix if I need to repost, otherwise I will fix with with a
dedicated patch. Agree?
Regards,
Lorenzo
>
> > + struct airoha_eth *eth;
> > +};
> > +
> > struct airoha_gdm_port {
> > struct airoha_qdma *qdma;
> > - struct airoha_eth *eth;
> > - struct net_device *dev;
> > + struct airoha_gdm_dev *dev;
> > int id;
> > int nbq;
> >
> Thanks,
> Olek
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH net-next 1/6] net: airoha: Introduce airoha_gdm_dev struct
From: Alexander Lobakin @ 2026-05-28 13:42 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, linux-arm-kernel, linux-mediatek,
netdev, Xuegang Lu
In-Reply-To: <20260527-airoha-eth-multi-serdes-preliminary-v1-1-ec6ed73ef7fc@kernel.org>
From: Lorenzo Bianconi <lorenzo@kernel.org>
Date: Wed, 27 May 2026 12:21:15 +0200
> EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
> or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
> As a result multiple net_devices can connect to the same GDM{3,4} port
> and there is a theoretical "1:n" relation between GDM port and
> net_devices.
> Introduce airoha_gdm_dev struct to collect net_device related info (e.g.
> net_device and external phy pointer). Please note this is just a
> preliminary patch and we are still supporting a single net_device for
> each GDM port. Subsequent patches will add support for multiple net_devices
> connected to the same GDM port.
>
> Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
[...]
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index d3781103abb5..c78cabbec753 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -535,10 +535,15 @@ struct airoha_qdma {
> struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
> };
>
> +struct airoha_gdm_dev {
> + struct airoha_gdm_port *port;
> + struct net_device *dev;
Nit: we now have priv_to_netdev() (I hope I remember its name
correctly), so that we no longer need to store a netdev backpointer in
netdev_priv structures.
Just an option, up to you.
> + struct airoha_eth *eth;
> +};
> +
> struct airoha_gdm_port {
> struct airoha_qdma *qdma;
> - struct airoha_eth *eth;
> - struct net_device *dev;
> + struct airoha_gdm_dev *dev;
> int id;
> int nbq;
>
Thanks,
Olek
^ permalink raw reply
* [PATCH RFC net-next v3] net: airoha: Add TCP LRO support
From: Lorenzo Bianconi @ 2026-05-28 12:56 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: Alexander Lobakin, linux-arm-kernel, linux-mediatek, netdev,
Madhur Agrawal
Add hardware TCP Large Receive Offload (LRO) support to the airoha_eth
driver, leveraging the EN7581/AN7583 SoC's 8 dedicated LRO hardware queues
mapped to RX queues 24–31. LRO hw offloading does not support
Scatter-Gather (SG) so it is required to increase the page_pool allocation
order to 2 for RX queues 24–31 (LRO queues).
Performance comparison between GRO and hw LRO has been carried out using
a 10Gbps NIC:
GRO: ~2.7 Gbps
LRO: ~8.1 Gbps
Please note with respect to the previous implementation, page_pool
allocation order has been reduced from 5 to 2.
Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
Changes in v3:
- Fix double-free of the page_pool of airoha_qdma_lro_rx_process()
fails.
- Set AIROHA_LRO_PAGE_ORDER according to PAGE_SIZE.
- Add missig gso metadata for the LRO packet.
- Link to v2: https://lore.kernel.org/r/20260526-airoha-eth-lro-v2-1-24e2a9e7a397@kernel.org
Changes in RFC v2:
- Improve performances fixing buf_size computation.
- Fix possible overflow in REG_CDM_LRO_LIMIT() register configuration.
- Require the device to be not running before configuring LRO.
- Fix configuration order in airoha_fe_lro_is_enabled().
- Check skb header length in airoha_qdma_lro_rx_process().
- Do not check net_device feature in airoha_qdma_rx_process() before
executing airoha_qdma_lro_rx_process() but rely on
airoha_qdma_lro_rx_process() logic.
- Fix possible double recycle in airoha_qdma_rx_process() for LRO
packets.
- Always use AIROHA_RXQ_LRO_MAX_AGG_COUNT macro for max LRO aggregated
fragments in airoha_fe_lro_init_rx_queue().
- Link to v1: https://lore.kernel.org/r/20260520-airoha-eth-lro-v1-1-129cc33766e9@kernel.org
---
drivers/net/ethernet/airoha/airoha_eth.c | 222 ++++++++++++++++++++++++++++--
drivers/net/ethernet/airoha/airoha_eth.h | 24 ++++
drivers/net/ethernet/airoha/airoha_regs.h | 22 ++-
3 files changed, 256 insertions(+), 12 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 6418fe0c9f80..6d9b23ebc13e 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -12,6 +12,7 @@
#include <net/dst_metadata.h>
#include <net/page_pool/helpers.h>
#include <net/pkt_cls.h>
+#include <net/tcp.h>
#include <uapi/linux/ppp_defs.h>
#include "airoha_regs.h"
@@ -431,6 +432,48 @@ static void airoha_fe_crsn_qsel_init(struct airoha_eth *eth)
CDM_CRSN_QSEL_Q1));
}
+static void airoha_fe_lro_init_rx_queue(struct airoha_eth *eth, int qdma_id,
+ int lro_queue_index, int qid,
+ int buf_size)
+{
+ int id = qdma_id + 1;
+
+ airoha_fe_rmw(eth, REG_CDM_LRO_LIMIT(id),
+ CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK,
+ FIELD_PREP(CDM_LRO_AGG_SIZE_MASK, buf_size) |
+ FIELD_PREP(CDM_LRO_AGG_NUM_MASK,
+ AIROHA_RXQ_LRO_MAX_AGG_COUNT));
+ airoha_fe_rmw(eth, REG_CDM_LRO_AGE_TIME(id),
+ CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK,
+ FIELD_PREP(CDM_LRO_AGE_TIME_MASK,
+ AIROHA_RXQ_LRO_MAX_AGE_TIME) |
+ FIELD_PREP(CDM_LRO_AGG_TIME_MASK,
+ AIROHA_RXQ_LRO_MAX_AGG_TIME));
+ airoha_fe_rmw(eth, REG_CDM_LRO_RXQ(id, lro_queue_index),
+ LRO_RXQ_MASK(lro_queue_index),
+ __field_prep(LRO_RXQ_MASK(lro_queue_index), qid));
+ airoha_fe_set(eth, REG_CDM_LRO_EN(id), BIT(lro_queue_index));
+}
+
+static void airoha_fe_lro_disable(struct airoha_eth *eth, int qdma_id)
+{
+ int i, id = qdma_id + 1;
+
+ airoha_fe_clear(eth, REG_CDM_LRO_EN(id), LRO_RXQ_EN_MASK);
+ airoha_fe_clear(eth, REG_CDM_LRO_LIMIT(id),
+ CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK);
+ airoha_fe_clear(eth, REG_CDM_LRO_AGE_TIME(id),
+ CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK);
+ for (i = 0; i < AIROHA_MAX_NUM_LRO_QUEUES; i++)
+ airoha_fe_clear(eth, REG_CDM_LRO_RXQ(id, i), LRO_RXQ_MASK(i));
+}
+
+static bool airoha_fe_lro_is_enabled(struct airoha_eth *eth, int qdma_id)
+{
+ return airoha_fe_get(eth, REG_CDM_LRO_EN(qdma_id + 1),
+ LRO_RXQ_EN_MASK);
+}
+
static int airoha_fe_init(struct airoha_eth *eth)
{
airoha_fe_maccr_init(eth);
@@ -587,6 +630,97 @@ static int airoha_qdma_get_gdm_port(struct airoha_eth *eth,
return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
}
+static int airoha_qdma_lro_rx_process(struct sk_buff *skb,
+ struct airoha_qdma_desc *desc)
+{
+ u32 desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
+ struct skb_shared_info *shinfo = skb_shinfo(skb);
+ u32 msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
+ u32 msg2 = le32_to_cpu(READ_ONCE(desc->msg2));
+ u32 msg3 = le32_to_cpu(READ_ONCE(desc->msg3));
+ u32 len, th_off, tcp_ack_seq, agg_count;
+ u16 tcp_win, l2_len;
+ struct tcphdr *th;
+ bool ipv4, ipv6;
+
+ agg_count = FIELD_GET(QDMA_ETH_RXMSG_AGG_COUNT_MASK, msg2);
+ if (agg_count <= 1)
+ return 0;
+
+ ipv4 = FIELD_GET(QDMA_ETH_RXMSG_IP4_MASK, msg1);
+ ipv6 = FIELD_GET(QDMA_ETH_RXMSG_IP6_MASK, msg1);
+ if (!ipv4 && !ipv6)
+ return -EOPNOTSUPP;
+
+ l2_len = FIELD_GET(QDMA_ETH_RXMSG_L2_LEN_MASK, msg2);
+ len = FIELD_GET(QDMA_DESC_LEN_MASK, desc_ctrl);
+ if (ipv4) {
+ struct iphdr *iph;
+
+ if (!pskb_may_pull(skb, l2_len + sizeof(*iph)))
+ return -EINVAL;
+
+ iph = (struct iphdr *)(skb->data + l2_len);
+ if (iph->protocol != IPPROTO_TCP)
+ return -EOPNOTSUPP;
+
+ th_off = l2_len + (iph->ihl << 2);
+ if (!pskb_may_pull(skb, th_off))
+ return -EINVAL;
+
+ iph->tot_len = cpu_to_be16(len - l2_len);
+ iph->check = 0;
+ iph->check = ip_fast_csum((void *)iph, iph->ihl);
+ } else {
+ struct ipv6hdr *ip6h;
+
+ th_off = l2_len + sizeof(*ip6h);
+ if (!pskb_may_pull(skb, th_off))
+ return -EINVAL;
+
+ ip6h = (struct ipv6hdr *)(skb->data + l2_len);
+ if (ip6h->nexthdr != NEXTHDR_TCP)
+ return -EOPNOTSUPP;
+
+ ip6h->payload_len = cpu_to_be16(len - th_off);
+ }
+
+ tcp_win = FIELD_GET(QDMA_ETH_RXMSG_TCP_WIN_MASK, msg3);
+ tcp_ack_seq = le32_to_cpu(READ_ONCE(desc->data));
+
+ if (!pskb_may_pull(skb, th_off + sizeof(*th)))
+ return -EINVAL;
+
+ th = (struct tcphdr *)(skb->data + th_off);
+ th->ack_seq = cpu_to_be32(tcp_ack_seq);
+ th->window = cpu_to_be16(tcp_win);
+
+ /* Check tcp timestamp option */
+ if (th->doff == (sizeof(*th) + TCPOLEN_TSTAMP_ALIGNED) / 4) {
+ __be32 *topt;
+
+ if (!pskb_may_pull(skb, th_off + (th->doff << 2)))
+ return -EINVAL;
+
+ topt = (__be32 *)(th + 1);
+ if (*topt == cpu_to_be32((TCPOPT_NOP << 24) |
+ (TCPOPT_NOP << 16) |
+ (TCPOPT_TIMESTAMP << 8) |
+ TCPOLEN_TIMESTAMP)) {
+ __le32 tcp_ts_reply = READ_ONCE(desc->tcp_ts_reply);
+
+ put_unaligned_be32(le32_to_cpu(tcp_ts_reply),
+ topt + 2);
+ }
+ }
+
+ shinfo->gso_size = (len - th_off - (th->doff << 2)) / agg_count;
+ shinfo->gso_type = ipv4 ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
+ shinfo->gso_segs = agg_count;
+
+ return 0;
+}
+
static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
{
enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
@@ -636,9 +770,16 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
__skb_put(q->skb, len);
skb_mark_for_recycle(q->skb);
q->skb->dev = port->dev;
- q->skb->protocol = eth_type_trans(q->skb, port->dev);
q->skb->ip_summed = CHECKSUM_UNNECESSARY;
skb_record_rx_queue(q->skb, qid);
+
+ if (airoha_qdma_lro_rx_process(q->skb, desc) < 0) {
+ dev_kfree_skb(q->skb);
+ q->skb = NULL;
+ continue;
+ }
+
+ q->skb->protocol = eth_type_trans(q->skb, port->dev);
} else { /* scattered frame */
struct skb_shared_info *shinfo = skb_shinfo(q->skb);
int nr_frags = shinfo->nr_frags;
@@ -727,12 +868,10 @@ static int airoha_qdma_rx_napi_poll(struct napi_struct *napi, int budget)
static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
struct airoha_qdma *qdma, int ndesc)
{
- const struct page_pool_params pp_params = {
- .order = 0,
+ struct page_pool_params pp_params = {
.pool_size = 256,
.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
.dma_dir = DMA_FROM_DEVICE,
- .max_len = PAGE_SIZE,
.nid = NUMA_NO_NODE,
.dev = qdma->eth->dev,
.napi = &q->napi,
@@ -740,9 +879,10 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
struct airoha_eth *eth = qdma->eth;
int qid = q - &qdma->q_rx[0], thr;
dma_addr_t dma_addr;
+ bool lro_q;
- q->buf_size = PAGE_SIZE / 2;
q->qdma = qdma;
+ lro_q = airoha_qdma_is_lro_queue(q);
q->entry = devm_kzalloc(eth->dev, ndesc * sizeof(*q->entry),
GFP_KERNEL);
@@ -754,6 +894,9 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
if (!q->desc)
return -ENOMEM;
+ pp_params.order = lro_q ? AIROHA_LRO_PAGE_ORDER : 0;
+ pp_params.max_len = PAGE_SIZE << pp_params.order;
+
q->page_pool = page_pool_create(&pp_params);
if (IS_ERR(q->page_pool)) {
int err = PTR_ERR(q->page_pool);
@@ -762,6 +905,7 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
return err;
}
+ q->buf_size = lro_q ? pp_params.max_len : pp_params.max_len / 2;
q->ndesc = ndesc;
netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
@@ -1993,6 +2137,64 @@ int airoha_get_fe_port(struct airoha_gdm_port *port)
}
}
+static int airoha_dev_set_features(struct net_device *dev,
+ netdev_features_t features)
+{
+ netdev_features_t diff = dev->features ^ features;
+ struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_qdma *qdma = port->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - ð->qdma[0];
+ int i;
+
+ if (!(diff & NETIF_F_LRO))
+ return 0;
+
+ /* reset LRO configuration */
+ if (features & NETIF_F_LRO) {
+ int lro_queue_index = 0;
+
+ if (airoha_fe_lro_is_enabled(eth, qdma_id))
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+ struct airoha_queue *q = &qdma->q_rx[i];
+ u32 size;
+
+ if (!q->ndesc)
+ continue;
+
+ if (!airoha_qdma_is_lro_queue(q))
+ continue;
+
+ size = SKB_WITH_OVERHEAD(AIROHA_RX_LEN(q->buf_size));
+ size = min_t(u32, size, CDM_LRO_AGG_SIZE_MASK);
+ airoha_fe_lro_init_rx_queue(eth, qdma_id,
+ lro_queue_index, i, size);
+ lro_queue_index++;
+ }
+ } else {
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *p = eth->ports[i];
+
+ if (!p)
+ continue;
+
+ if (p->qdma != qdma)
+ continue;
+
+ if (p->dev == dev)
+ continue;
+
+ if (p->dev->features & NETIF_F_LRO)
+ return 0;
+ }
+ airoha_fe_lro_disable(eth, qdma_id);
+ }
+
+ return 0;
+}
+
static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
struct net_device *dev)
{
@@ -2892,6 +3094,7 @@ static const struct net_device_ops airoha_netdev_ops = {
.ndo_stop = airoha_dev_stop,
.ndo_change_mtu = airoha_dev_change_mtu,
.ndo_select_queue = airoha_dev_select_queue,
+ .ndo_set_features = airoha_dev_set_features,
.ndo_start_xmit = airoha_dev_xmit,
.ndo_get_stats64 = airoha_dev_get_stats64,
.ndo_set_mac_address = airoha_dev_set_macaddr,
@@ -2989,12 +3192,9 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
dev->ethtool_ops = &airoha_ethtool_ops;
dev->max_mtu = AIROHA_MAX_MTU;
dev->watchdog_timeo = 5 * HZ;
- dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
- NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
- NETIF_F_SG | NETIF_F_TSO |
- NETIF_F_HW_TC;
- dev->features |= dev->hw_features;
- dev->vlan_features = dev->hw_features;
+ dev->hw_features = AIROHA_HW_FEATURES | NETIF_F_LRO;
+ dev->features |= AIROHA_HW_FEATURES;
+ dev->vlan_features = AIROHA_HW_FEATURES;
dev->dev.of_node = np;
SET_NETDEV_DEV(dev, eth->dev);
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index d3781103abb5..0cf2439e815f 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -43,6 +43,18 @@
(_n) == 15 ? 128 : \
(_n) == 0 ? 1024 : 16)
+#define AIROHA_LRO_PAGE_ORDER order_base_2(SZ_16K / PAGE_SIZE)
+#define AIROHA_MAX_NUM_LRO_QUEUES 8
+#define AIROHA_RXQ_LRO_EN_MASK 0xff000000
+#define AIROHA_RXQ_LRO_MAX_AGG_COUNT 64
+#define AIROHA_RXQ_LRO_MAX_AGG_TIME 100
+#define AIROHA_RXQ_LRO_MAX_AGE_TIME 2000 /* 1ms */
+
+#define AIROHA_HW_FEATURES \
+ (NETIF_F_IP_CSUM | NETIF_F_RXCSUM | \
+ NETIF_F_TSO6 | NETIF_F_IPV6_CSUM | \
+ NETIF_F_SG | NETIF_F_TSO | NETIF_F_HW_TC)
+
#define PSE_RSV_PAGES 128
#define PSE_QUEUE_RSV_PAGES 64
@@ -661,6 +673,18 @@ static inline bool airoha_is_7583(struct airoha_eth *eth)
return eth->soc->version == 0x7583;
}
+static inline bool airoha_qdma_is_lro_queue(struct airoha_queue *q)
+{
+ struct airoha_qdma *qdma = q->qdma;
+ int qid = q - &qdma->q_rx[0];
+
+ /* EN7581 SoC supports at most 8 LRO rx queues */
+ BUILD_BUG_ON(hweight32(AIROHA_RXQ_LRO_EN_MASK) >
+ AIROHA_MAX_NUM_LRO_QUEUES);
+
+ return !!(AIROHA_RXQ_LRO_EN_MASK & BIT(qid));
+}
+
int airoha_get_fe_port(struct airoha_gdm_port *port);
bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
struct airoha_gdm_port *port);
diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h
index 436f3c8779c1..dfc786583774 100644
--- a/drivers/net/ethernet/airoha/airoha_regs.h
+++ b/drivers/net/ethernet/airoha/airoha_regs.h
@@ -122,6 +122,20 @@
#define CDM_CRSN_QSEL_REASON_MASK(_n) \
GENMASK(4 + (((_n) % 4) << 3), (((_n) % 4) << 3))
+#define REG_CDM_LRO_RXQ(_n, _m) (CDM_BASE(_n) + 0x78 + ((_m) & 0x4))
+#define LRO_RXQ_MASK(_n) GENMASK(4 + (((_n) & 0x3) << 3), ((_n) & 0x3) << 3)
+
+#define REG_CDM_LRO_EN(_n) (CDM_BASE(_n) + 0x80)
+#define LRO_RXQ_EN_MASK GENMASK(7, 0)
+
+#define REG_CDM_LRO_LIMIT(_n) (CDM_BASE(_n) + 0x84)
+#define CDM_LRO_AGG_NUM_MASK GENMASK(23, 16)
+#define CDM_LRO_AGG_SIZE_MASK GENMASK(15, 0)
+
+#define REG_CDM_LRO_AGE_TIME(_n) (CDM_BASE(_n) + 0x88)
+#define CDM_LRO_AGE_TIME_MASK GENMASK(31, 16)
+#define CDM_LRO_AGG_TIME_MASK GENMASK(15, 0)
+
#define REG_GDM_FWD_CFG(_n) GDM_BASE(_n)
#define GDM_PAD_EN_MASK BIT(28)
#define GDM_DROP_CRC_ERR_MASK BIT(23)
@@ -883,9 +897,15 @@
#define QDMA_ETH_RXMSG_SPORT_MASK GENMASK(25, 21)
#define QDMA_ETH_RXMSG_CRSN_MASK GENMASK(20, 16)
#define QDMA_ETH_RXMSG_PPE_ENTRY_MASK GENMASK(15, 0)
+/* RX MSG2 */
+#define QDMA_ETH_RXMSG_AGG_COUNT_MASK GENMASK(31, 24)
+#define QDMA_ETH_RXMSG_L2_LEN_MASK GENMASK(6, 0)
+/* RX MSG3 */
+#define QDMA_ETH_RXMSG_AGG_LEN_MASK GENMASK(31, 16)
+#define QDMA_ETH_RXMSG_TCP_WIN_MASK GENMASK(15, 0)
struct airoha_qdma_desc {
- __le32 rsv;
+ __le32 tcp_ts_reply;
__le32 ctrl;
__le32 addr;
__le32 data;
---
base-commit: fabcf8cad67b4e2aa51b4c3f79f26fd215b50c8c
change-id: 20260520-airoha-eth-lro-a5d1c3631811
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* Re: [PATCH net-next 4/6] net: airoha: Move qos_sq_bmap in airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-28 12:49 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Simon Horman, linux-arm-kernel, linux-mediatek, netdev,
Xuegang Lu
In-Reply-To: <20260527-airoha-eth-multi-serdes-preliminary-v1-4-ec6ed73ef7fc@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 7919 bytes --]
> Since now multiple net_devices connected to different QDMA blocks can
> share the same GDM port, qos_sq_bmap field can be overwritten with the
> configuration obtained from a net_device connected to a different QDMA
> block. In order to fix the issue move qos_sq_bmap field from
> airoha_gdm_port struct to airoha_gdm_dev one.
> Add qos_channel_map bitmap in airoha_qdma struct to track if a shared
> QDMA channel is already in use by another net_device.
>
> Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 58 ++++++++++++++++++++------------
> drivers/net/ethernet/airoha/airoha_eth.h | 6 ++--
> 2 files changed, 40 insertions(+), 24 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index c5d25f7640ac..4f77a0c22162 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -2602,30 +2602,40 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
> return 0;
> }
>
> -static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> - struct tc_htb_qopt_offload *opt)
> +static int airoha_tc_htb_modify_queue(struct net_device *dev,
> + struct tc_htb_qopt_offload *opt)
> {
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
> - int err, num_tx_queues = netdev->real_num_tx_queues;
> - struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
>
> if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
> NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
> return -EINVAL;
> }
>
> - err = airoha_qdma_set_tx_rate_limit(netdev, channel, rate,
> - opt->quantum);
> - if (err) {
> + return airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
> +}
> +
> +static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> + struct tc_htb_qopt_offload *opt)
> +{
> + u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> + int err, num_tx_queues = netdev->real_num_tx_queues;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_qdma *qdma = dev->qdma;
> +
> + /* Here we need to check the requested QDMA channel is not already
> + * in use by another net_device running on the same QDMA block.
> + */
> + if (test_and_set_bit(channel, qdma->qos_channel_map)) {
> NL_SET_ERR_MSG_MOD(opt->extack,
> - "failed configuring htb offload");
> - return err;
> + "qdma qos channel already in use");
> + return -EBUSY;
> }
>
> - if (opt->command == TC_HTB_NODE_MODIFY)
> - return 0;
> + err = airoha_tc_htb_modify_queue(netdev, opt);
> + if (err)
> + goto error;
>
> err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
> if (err) {
> @@ -2633,13 +2643,17 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> opt->quantum);
> NL_SET_ERR_MSG_MOD(opt->extack,
> "failed setting real_num_tx_queues");
> - return err;
> + goto error;
> }
>
> - set_bit(channel, port->qos_sq_bmap);
> + set_bit(channel, dev->qos_sq_bmap);
> opt->qid = AIROHA_NUM_TX_RING + channel;
>
> return 0;
> +error:
> + clear_bit(channel, qdma->qos_channel_map);
> +
> + return err;
> }
>
> static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
> @@ -2820,11 +2834,13 @@ static int airoha_dev_setup_tc_block(struct net_device *dev,
> static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
> + struct airoha_qdma *qdma = dev->qdma;
>
> netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
> airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
> - clear_bit(queue, port->qos_sq_bmap);
> +
> + clear_bit(queue, qdma->qos_channel_map);
> + clear_bit(queue, dev->qos_sq_bmap);
> }
>
> static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
> @@ -2832,9 +2848,8 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
> {
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
>
> - if (!test_bit(channel, port->qos_sq_bmap)) {
> + if (!test_bit(channel, dev->qos_sq_bmap)) {
> NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
> return -EINVAL;
> }
> @@ -2847,10 +2862,9 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
> static int airoha_tc_htb_destroy(struct net_device *netdev)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
> int q;
>
> - for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
> + for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
> airoha_tc_remove_htb_queue(netdev, q);
>
> return 0;
> @@ -2861,9 +2875,8 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
> {
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
>
> - if (!test_bit(channel, port->qos_sq_bmap)) {
> + if (!test_bit(channel, dev->qos_sq_bmap)) {
> NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
> return -EINVAL;
> }
> @@ -2882,6 +2895,7 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
> case TC_HTB_DESTROY:
> return airoha_tc_htb_destroy(dev);
> case TC_HTB_NODE_MODIFY:
> + return airoha_tc_htb_modify_queue(dev, opt);
> case TC_HTB_LEAF_ALLOC_QUEUE:
> return airoha_tc_htb_alloc_leaf_queue(dev, opt);
> case TC_HTB_LEAF_DEL:
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index f6f59d25abd9..a308a770116b 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -533,6 +533,8 @@ struct airoha_qdma {
>
> struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
> struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
> +
> + DECLARE_BITMAP(qos_channel_map, AIROHA_NUM_QOS_CHANNELS);
> };
>
> struct airoha_gdm_dev {
> @@ -540,6 +542,8 @@ struct airoha_gdm_dev {
> struct airoha_qdma *qdma;
> struct net_device *dev;
> struct airoha_eth *eth;
> +
> + DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
> };
>
> struct airoha_gdm_port {
> @@ -549,8 +553,6 @@ struct airoha_gdm_port {
>
> struct airoha_hw_stats stats;
>
> - DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
> -
> /* qos stats counters */
> u64 cpu_tx_packets;
> u64 fwd_tx_packets;
>
> --
> 2.54.0
>
commenting on sashiko's report:
https://sashiko.dev/#/patchset/20260527-airoha-eth-multi-serdes-preliminary-v1-0-ec6ed73ef7fc%40kernel.org
- This isn't a bug introduced by this patch, but does this modify operation
skip verifying if the requesting net_device actually owns the channel?
- Since this issue has not been itroduced by this patch, I will fix it with a
dedicated patch.
- This is a pre-existing issue, but does this manage real_num_tx_queues as an
active queue counter rather than a bounding limit?
- Since this issue has not been itroduced by this patch, I will fix it with a
dedicated patch.
- This isn't a bug introduced by this patch, but is there an off-by-one error
when clearing the TX rate limit for the removed queue?
- Since this issue has not been itroduced by this patch, I will fix it with a
dedicated patch.
Regards,
Lorenzo
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH net-next 1/6] net: airoha: Introduce airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-28 12:38 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Simon Horman, linux-arm-kernel, linux-mediatek, netdev,
Xuegang Lu
In-Reply-To: <20260527-airoha-eth-multi-serdes-preliminary-v1-1-ec6ed73ef7fc@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 30674 bytes --]
> EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
> or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
> As a result multiple net_devices can connect to the same GDM{3,4} port
> and there is a theoretical "1:n" relation between GDM port and
> net_devices.
> Introduce airoha_gdm_dev struct to collect net_device related info (e.g.
> net_device and external phy pointer). Please note this is just a
> preliminary patch and we are still supporting a single net_device for
> each GDM port. Subsequent patches will add support for multiple net_devices
> connected to the same GDM port.
>
> Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 312 ++++++++++++++++++-------------
> drivers/net/ethernet/airoha/airoha_eth.h | 13 +-
> drivers/net/ethernet/airoha/airoha_ppe.c | 17 +-
> 3 files changed, 206 insertions(+), 136 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 6418fe0c9f80..b2684af2db76 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -600,6 +600,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> struct airoha_qdma_desc *desc = &q->desc[q->tail];
> u32 hash, reason, msg1, desc_ctrl;
> struct airoha_gdm_port *port;
> + struct net_device *netdev;
> int data_len, len, p;
> struct page *page;
>
> @@ -626,6 +627,10 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> goto free_frag;
>
> port = eth->ports[p];
> + if (!port->dev)
> + goto free_frag;
> +
> + netdev = port->dev->dev;
> if (!q->skb) { /* first buffer */
> q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
> q->buf_size);
> @@ -635,8 +640,8 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> skb_reserve(q->skb, AIROHA_RX_HEADROOM);
> __skb_put(q->skb, len);
> skb_mark_for_recycle(q->skb);
> - q->skb->dev = port->dev;
> - q->skb->protocol = eth_type_trans(q->skb, port->dev);
> + q->skb->dev = netdev;
> + q->skb->protocol = eth_type_trans(q->skb, netdev);
> q->skb->ip_summed = CHECKSUM_UNNECESSARY;
> skb_record_rx_queue(q->skb, qid);
> } else { /* scattered frame */
> @@ -654,7 +659,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
> continue;
>
> - if (netdev_uses_dsa(port->dev)) {
> + if (netdev_uses_dsa(netdev)) {
> /* PPE module requires untagged packets to work
> * properly and it provides DSA port index via the
> * DMA descriptor. Report DSA tag to the DSA stack
> @@ -848,6 +853,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
>
> for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
> struct airoha_gdm_port *port = eth->ports[i];
> + struct airoha_gdm_dev *dev;
> int j;
>
> if (!port)
> @@ -856,11 +862,12 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
> if (port->qdma != qdma)
> continue;
>
> - for (j = 0; j < port->dev->num_tx_queues; j++) {
> + dev = port->dev;
> + for (j = 0; j < dev->dev->num_tx_queues; j++) {
> if (airoha_qdma_get_txq(qdma, j) != qid)
> continue;
>
> - netif_wake_subqueue(port->dev, j);
> + netif_wake_subqueue(dev->dev, j);
> }
> }
> q->txq_stopped = false;
> @@ -1700,19 +1707,20 @@ static void airoha_update_hw_stats(struct airoha_gdm_port *port)
> spin_unlock(&port->stats.lock);
> }
>
> -static int airoha_dev_open(struct net_device *dev)
> +static int airoha_dev_open(struct net_device *netdev)
> {
> - int err, len = ETH_HLEN + dev->mtu + ETH_FCS_LEN;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_qdma *qdma = port->qdma;
> u32 pse_port = FE_PSE_PORT_PPE1;
>
> - netif_tx_start_all_queues(dev);
> + netif_tx_start_all_queues(netdev);
> err = airoha_set_vip_for_gdm_port(port, true);
> if (err)
> return err;
>
> - if (netdev_uses_dsa(dev))
> + if (netdev_uses_dsa(netdev))
> airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
> GDM_STAG_EN_MASK);
> else
> @@ -1740,16 +1748,17 @@ static int airoha_dev_open(struct net_device *dev)
> return 0;
> }
>
> -static int airoha_dev_stop(struct net_device *dev)
> +static int airoha_dev_stop(struct net_device *netdev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_qdma *qdma = port->qdma;
> int i;
>
> - netif_tx_disable(dev);
> + netif_tx_disable(netdev);
> airoha_set_vip_for_gdm_port(port, false);
> - for (i = 0; i < dev->num_tx_queues; i++)
> - netdev_tx_reset_subqueue(dev, i);
> + for (i = 0; i < netdev->num_tx_queues; i++)
> + netdev_tx_reset_subqueue(netdev, i);
>
> airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
> FE_PSE_PORT_DROP);
> @@ -1770,16 +1779,17 @@ static int airoha_dev_stop(struct net_device *dev)
> return 0;
> }
>
> -static int airoha_dev_set_macaddr(struct net_device *dev, void *p)
> +static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int err;
>
> - err = eth_mac_addr(dev, p);
> + err = eth_mac_addr(netdev, p);
> if (err)
> return err;
>
> - airoha_set_macaddr(port, dev->dev_addr);
> + airoha_set_macaddr(port, netdev->dev_addr);
>
> return 0;
> }
> @@ -1845,16 +1855,17 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
> return 0;
> }
>
> -static int airoha_dev_init(struct net_device *dev)
> +static int airoha_dev_init(struct net_device *netdev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> - struct airoha_eth *eth = port->eth;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> + struct airoha_eth *eth = dev->eth;
> int i;
>
> /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
> port->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
> - port->dev->irq = port->qdma->irq_banks[0].irq;
> - airoha_set_macaddr(port, dev->dev_addr);
> + dev->dev->irq = port->qdma->irq_banks[0].irq;
> + airoha_set_macaddr(port, netdev->dev_addr);
>
> switch (port->id) {
> case AIROHA_GDM3_IDX:
> @@ -1879,10 +1890,11 @@ static int airoha_dev_init(struct net_device *dev)
> return 0;
> }
>
> -static void airoha_dev_get_stats64(struct net_device *dev,
> +static void airoha_dev_get_stats64(struct net_device *netdev,
> struct rtnl_link_stats64 *storage)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> unsigned int start;
>
> airoha_update_hw_stats(port);
> @@ -1901,36 +1913,39 @@ static void airoha_dev_get_stats64(struct net_device *dev,
> } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> }
>
> -static int airoha_dev_change_mtu(struct net_device *dev, int mtu)
> +static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_eth *eth = port->qdma->eth;
> u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
>
> airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
> GDM_LONG_LEN_MASK,
> FIELD_PREP(GDM_LONG_LEN_MASK, len));
> - WRITE_ONCE(dev->mtu, mtu);
> + WRITE_ONCE(netdev->mtu, mtu);
>
> return 0;
> }
>
> -static u16 airoha_dev_select_queue(struct net_device *dev, struct sk_buff *skb,
> +static u16 airoha_dev_select_queue(struct net_device *netdev,
> + struct sk_buff *skb,
> struct net_device *sb_dev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int queue, channel;
>
> /* For dsa device select QoS channel according to the dsa user port
> * index, rely on port id otherwise. Select QoS queue based on the
> * skb priority.
> */
> - channel = netdev_uses_dsa(dev) ? skb_get_queue_mapping(skb) : port->id;
> + channel = netdev_uses_dsa(netdev) ? skb_get_queue_mapping(skb) : port->id;
> channel = channel % AIROHA_NUM_QOS_CHANNELS;
> queue = (skb->priority - 1) % AIROHA_NUM_QOS_QUEUES; /* QoS queue */
> queue = channel * AIROHA_NUM_QOS_QUEUES + queue;
>
> - return queue < dev->num_tx_queues ? queue : 0;
> + return queue < netdev->num_tx_queues ? queue : 0;
> }
>
> static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
> @@ -1994,9 +2009,10 @@ int airoha_get_fe_port(struct airoha_gdm_port *port)
> }
>
> static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> - struct net_device *dev)
> + struct net_device *netdev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_qdma *qdma = port->qdma;
> u32 nr_frags, tag, msg0, msg1, len;
> struct airoha_queue_entry *e;
> @@ -2009,7 +2025,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> u8 fport;
>
> qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
> - tag = airoha_get_dsa_tag(skb, dev);
> + tag = airoha_get_dsa_tag(skb, netdev);
>
> msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
> qid / AIROHA_NUM_QOS_QUEUES) |
> @@ -2045,7 +2061,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
>
> spin_lock_bh(&q->lock);
>
> - txq = skb_get_tx_queue(dev, skb);
> + txq = skb_get_tx_queue(netdev, skb);
> nr_frags = 1 + skb_shinfo(skb)->nr_frags;
>
> if (q->queued + nr_frags >= q->ndesc) {
> @@ -2069,9 +2085,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> dma_addr_t addr;
> u32 val;
>
> - addr = dma_map_single(dev->dev.parent, data, len,
> + addr = dma_map_single(netdev->dev.parent, data, len,
> DMA_TO_DEVICE);
> - if (unlikely(dma_mapping_error(dev->dev.parent, addr)))
> + if (unlikely(dma_mapping_error(netdev->dev.parent, addr)))
> goto error_unmap;
>
> list_move_tail(&e->list, &tx_list);
> @@ -2120,7 +2136,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
>
> error_unmap:
> list_for_each_entry(e, &tx_list, list) {
> - dma_unmap_single(dev->dev.parent, e->dma_addr, e->dma_len,
> + dma_unmap_single(netdev->dev.parent, e->dma_addr, e->dma_len,
> DMA_TO_DEVICE);
> e->dma_addr = 0;
> }
> @@ -2129,25 +2145,27 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> spin_unlock_bh(&q->lock);
> error:
> dev_kfree_skb_any(skb);
> - dev->stats.tx_dropped++;
> + netdev->stats.tx_dropped++;
>
> return NETDEV_TX_OK;
> }
>
> -static void airoha_ethtool_get_drvinfo(struct net_device *dev,
> +static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
> struct ethtool_drvinfo *info)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_eth *eth = port->qdma->eth;
>
> strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
> strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
> }
>
> -static void airoha_ethtool_get_mac_stats(struct net_device *dev,
> +static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
> struct ethtool_eth_mac_stats *stats)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> unsigned int start;
>
> airoha_update_hw_stats(port);
> @@ -2175,11 +2193,12 @@ static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
> };
>
> static void
> -airoha_ethtool_get_rmon_stats(struct net_device *dev,
> +airoha_ethtool_get_rmon_stats(struct net_device *netdev,
> struct ethtool_rmon_stats *stats,
> const struct ethtool_rmon_hist_range **ranges)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_hw_stats *hw_stats = &port->stats;
> unsigned int start;
>
> @@ -2204,11 +2223,12 @@ airoha_ethtool_get_rmon_stats(struct net_device *dev,
> } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> }
>
> -static int airoha_qdma_set_chan_tx_sched(struct net_device *dev,
> +static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
> int channel, enum tx_sched_mode mode,
> const u16 *weights, u8 n_weights)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int i;
>
> for (i = 0; i < AIROHA_NUM_TX_RING; i++)
> @@ -2293,10 +2313,12 @@ static int airoha_qdma_set_tx_ets_sched(struct net_device *dev, int channel,
> ARRAY_SIZE(w));
> }
>
> -static int airoha_qdma_get_tx_ets_stats(struct net_device *dev, int channel,
> +static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
> struct tc_ets_qopt_offload *opt)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> +
> u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
> REG_CNTR_VAL(channel << 1));
> u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
> @@ -2558,11 +2580,12 @@ static int airoha_qdma_set_trtcm_token_bucket(struct airoha_qdma *qdma,
> mode, val);
> }
>
> -static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
> +static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
> int channel, u32 rate,
> u32 bucket_size)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int i, err;
>
> for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
> @@ -2582,20 +2605,22 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
> return 0;
> }
>
> -static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
> +static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> struct tc_htb_qopt_offload *opt)
> {
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
> - int err, num_tx_queues = dev->real_num_tx_queues;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + int err, num_tx_queues = netdev->real_num_tx_queues;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
>
> if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
> NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
> return -EINVAL;
> }
>
> - err = airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
> + err = airoha_qdma_set_tx_rate_limit(netdev, channel, rate,
> + opt->quantum);
> if (err) {
> NL_SET_ERR_MSG_MOD(opt->extack,
> "failed configuring htb offload");
> @@ -2605,9 +2630,10 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
> if (opt->command == TC_HTB_NODE_MODIFY)
> return 0;
>
> - err = netif_set_real_num_tx_queues(dev, num_tx_queues + 1);
> + err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
> if (err) {
> - airoha_qdma_set_tx_rate_limit(dev, channel, 0, opt->quantum);
> + airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
> + opt->quantum);
> NL_SET_ERR_MSG_MOD(opt->extack,
> "failed setting real_num_tx_queues");
> return err;
> @@ -2697,11 +2723,12 @@ static int airoha_tc_matchall_act_validate(struct tc_cls_matchall_offload *f)
> return 0;
> }
>
> -static int airoha_dev_tc_matchall(struct net_device *dev,
> +static int airoha_dev_tc_matchall(struct net_device *netdev,
> struct tc_cls_matchall_offload *f)
> {
> enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> u32 rate = 0, bucket_size = 0;
>
> switch (f->command) {
> @@ -2736,18 +2763,19 @@ static int airoha_dev_tc_matchall(struct net_device *dev,
> static int airoha_dev_setup_tc_block_cb(enum tc_setup_type type,
> void *type_data, void *cb_priv)
> {
> - struct net_device *dev = cb_priv;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct net_device *netdev = cb_priv;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_eth *eth = port->qdma->eth;
>
> - if (!tc_can_offload(dev))
> + if (!tc_can_offload(netdev))
> return -EOPNOTSUPP;
>
> switch (type) {
> case TC_SETUP_CLSFLOWER:
> return airoha_ppe_setup_tc_block_cb(ð->ppe->dev, type_data);
> case TC_SETUP_CLSMATCHALL:
> - return airoha_dev_tc_matchall(dev, type_data);
> + return airoha_dev_tc_matchall(netdev, type_data);
> default:
> return -EOPNOTSUPP;
> }
> @@ -2794,47 +2822,51 @@ static int airoha_dev_setup_tc_block(struct net_device *dev,
> }
> }
>
> -static void airoha_tc_remove_htb_queue(struct net_device *dev, int queue)
> +static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
>
> - netif_set_real_num_tx_queues(dev, dev->real_num_tx_queues - 1);
> - airoha_qdma_set_tx_rate_limit(dev, queue + 1, 0, 0);
> + netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
> + airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
> clear_bit(queue, port->qos_sq_bmap);
> }
>
> -static int airoha_tc_htb_delete_leaf_queue(struct net_device *dev,
> +static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
> struct tc_htb_qopt_offload *opt)
> {
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
>
> if (!test_bit(channel, port->qos_sq_bmap)) {
> NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
> return -EINVAL;
> }
>
> - airoha_tc_remove_htb_queue(dev, channel);
> + airoha_tc_remove_htb_queue(netdev, channel);
>
> return 0;
> }
>
> -static int airoha_tc_htb_destroy(struct net_device *dev)
> +static int airoha_tc_htb_destroy(struct net_device *netdev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int q;
>
> for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
> - airoha_tc_remove_htb_queue(dev, q);
> + airoha_tc_remove_htb_queue(netdev, q);
>
> return 0;
> }
>
> -static int airoha_tc_get_htb_get_leaf_queue(struct net_device *dev,
> +static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
> struct tc_htb_qopt_offload *opt)
> {
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
>
> if (!test_bit(channel, port->qos_sq_bmap)) {
> NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
> @@ -2870,8 +2902,8 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
> return 0;
> }
>
> -static int airoha_dev_tc_setup(struct net_device *dev, enum tc_setup_type type,
> - void *type_data)
> +static int airoha_dev_tc_setup(struct net_device *dev,
> + enum tc_setup_type type, void *type_data)
> {
> switch (type) {
> case TC_SETUP_QDISC_ETS:
> @@ -2937,25 +2969,81 @@ static void airoha_metadata_dst_free(struct airoha_gdm_port *port)
> }
> }
>
> -bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
> - struct airoha_gdm_port *port)
> +bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
> + struct airoha_gdm_dev *dev)
> {
> int i;
>
> for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
> - if (eth->ports[i] == port)
> + struct airoha_gdm_port *port = eth->ports[i];
> +
> + if (!port)
> + continue;
> +
> + if (port->dev == dev)
> return true;
> }
>
> return false;
> }
>
> +static int airoha_alloc_gdm_device(struct airoha_eth *eth,
> + struct airoha_gdm_port *port,
> + struct device_node *np)
> +{
> + struct airoha_gdm_dev *dev;
> + struct net_device *netdev;
> + int err;
> +
> + netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
> + AIROHA_NUM_NETDEV_TX_RINGS,
> + AIROHA_NUM_RX_RING);
> + if (!netdev) {
> + dev_err(eth->dev, "alloc_etherdev failed\n");
> + return -ENOMEM;
> + }
> +
> + netdev->netdev_ops = &airoha_netdev_ops;
> + netdev->ethtool_ops = &airoha_ethtool_ops;
> + netdev->max_mtu = AIROHA_MAX_MTU;
> + netdev->watchdog_timeo = 5 * HZ;
> + netdev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO6 |
> + NETIF_F_IPV6_CSUM | NETIF_F_SG | NETIF_F_TSO |
> + NETIF_F_HW_TC;
> + netdev->features |= netdev->hw_features;
> + netdev->vlan_features = netdev->hw_features;
> + netdev->dev.of_node = np;
> + SET_NETDEV_DEV(netdev, eth->dev);
> +
> + /* reserve hw queues for HTB offloading */
> + err = netif_set_real_num_tx_queues(netdev, AIROHA_NUM_TX_RING);
> + if (err)
> + return err;
> +
> + err = of_get_ethdev_address(np, netdev);
> + if (err) {
> + if (err == -EPROBE_DEFER)
> + return err;
> +
> + eth_hw_addr_random(netdev);
> + dev_info(eth->dev, "generated random MAC address %pM\n",
> + netdev->dev_addr);
> + }
> +
> + dev = netdev_priv(netdev);
> + dev->dev = netdev;
> + dev->port = port;
> + port->dev = dev;
> + dev->eth = eth;
> +
> + return 0;
> +}
> +
> static int airoha_alloc_gdm_port(struct airoha_eth *eth,
> struct device_node *np)
> {
> const __be32 *id_ptr = of_get_property(np, "reg", NULL);
> struct airoha_gdm_port *port;
> - struct net_device *dev;
> int err, p;
> u32 id;
>
> @@ -2977,53 +3065,22 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
> return -EINVAL;
> }
>
> - dev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*port),
> - AIROHA_NUM_NETDEV_TX_RINGS,
> - AIROHA_NUM_RX_RING);
> - if (!dev) {
> - dev_err(eth->dev, "alloc_etherdev failed\n");
> + port = devm_kzalloc(eth->dev, sizeof(*port), GFP_KERNEL);
> + if (!port)
> return -ENOMEM;
> - }
> -
> - dev->netdev_ops = &airoha_netdev_ops;
> - dev->ethtool_ops = &airoha_ethtool_ops;
> - dev->max_mtu = AIROHA_MAX_MTU;
> - dev->watchdog_timeo = 5 * HZ;
> - dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
> - NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
> - NETIF_F_SG | NETIF_F_TSO |
> - NETIF_F_HW_TC;
> - dev->features |= dev->hw_features;
> - dev->vlan_features = dev->hw_features;
> - dev->dev.of_node = np;
> - SET_NETDEV_DEV(dev, eth->dev);
>
> - /* reserve hw queues for HTB offloading */
> - err = netif_set_real_num_tx_queues(dev, AIROHA_NUM_TX_RING);
> - if (err)
> - return err;
> -
> - err = of_get_ethdev_address(np, dev);
> - if (err) {
> - if (err == -EPROBE_DEFER)
> - return err;
> -
> - eth_hw_addr_random(dev);
> - dev_info(eth->dev, "generated random MAC address %pM\n",
> - dev->dev_addr);
> - }
> -
> - port = netdev_priv(dev);
> u64_stats_init(&port->stats.syncp);
> spin_lock_init(&port->stats.lock);
> - port->eth = eth;
> - port->dev = dev;
> port->id = id;
> /* XXX: Read nbq from DTS */
> port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
> eth->ports[p] = port;
>
> - return airoha_metadata_dst_alloc(port);
> + err = airoha_metadata_dst_alloc(port);
> + if (err)
> + return err;
> +
> + return airoha_alloc_gdm_device(eth, port, np);
> }
>
> static int airoha_register_gdm_devices(struct airoha_eth *eth)
> @@ -3037,7 +3094,7 @@ static int airoha_register_gdm_devices(struct airoha_eth *eth)
> if (!port)
> continue;
>
> - err = register_netdev(port->dev);
> + err = register_netdev(port->dev->dev);
> if (err)
> return err;
> }
> @@ -3146,12 +3203,14 @@ static int airoha_probe(struct platform_device *pdev)
>
> for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
> struct airoha_gdm_port *port = eth->ports[i];
> + struct airoha_gdm_dev *dev;
>
> if (!port)
> continue;
>
> - if (port->dev->reg_state == NETREG_REGISTERED)
> - unregister_netdev(port->dev);
> + dev = port->dev;
> + if (dev && dev->dev->reg_state == NETREG_REGISTERED)
> + unregister_netdev(dev->dev);
> airoha_metadata_dst_free(port);
> }
> airoha_hw_cleanup(eth);
> @@ -3172,11 +3231,14 @@ static void airoha_remove(struct platform_device *pdev)
>
> for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
> struct airoha_gdm_port *port = eth->ports[i];
> + struct airoha_gdm_dev *dev;
>
> if (!port)
> continue;
>
> - unregister_netdev(port->dev);
> + dev = port->dev;
> + if (dev)
> + unregister_netdev(dev->dev);
> airoha_metadata_dst_free(port);
> }
> airoha_hw_cleanup(eth);
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index d3781103abb5..c78cabbec753 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -535,10 +535,15 @@ struct airoha_qdma {
> struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
> };
>
> +struct airoha_gdm_dev {
> + struct airoha_gdm_port *port;
> + struct net_device *dev;
> + struct airoha_eth *eth;
> +};
> +
> struct airoha_gdm_port {
> struct airoha_qdma *qdma;
> - struct airoha_eth *eth;
> - struct net_device *dev;
> + struct airoha_gdm_dev *dev;
> int id;
> int nbq;
>
> @@ -662,8 +667,8 @@ static inline bool airoha_is_7583(struct airoha_eth *eth)
> }
>
> int airoha_get_fe_port(struct airoha_gdm_port *port);
> -bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
> - struct airoha_gdm_port *port);
> +bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
> + struct airoha_gdm_dev *dev);
>
> void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
> u8 fport);
> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
> index 26da519236bf..af7af4097b98 100644
> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
> @@ -298,12 +298,12 @@ static void airoha_ppe_foe_set_bridge_addrs(struct airoha_foe_bridge *br,
>
> static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
> struct airoha_foe_entry *hwe,
> - struct net_device *dev, int type,
> + struct net_device *netdev, int type,
> struct airoha_flow_data *data,
> int l4proto)
> {
> u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val;
> - int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&dev);
> + int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev);
> struct airoha_foe_mac_info_common *l2;
> u8 smac_id = 0xf;
>
> @@ -319,10 +319,11 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
> hwe->ib1 = val;
>
> val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f);
> - if (dev) {
> + if (netdev) {
> struct airoha_wdma_info info = {};
>
> - if (!airoha_ppe_get_wdma_info(dev, data->eth.h_dest, &info)) {
> + if (!airoha_ppe_get_wdma_info(netdev, data->eth.h_dest,
> + &info)) {
> val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, info.idx) |
> FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT,
> FE_PSE_PORT_CDM4);
> @@ -332,12 +333,14 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
> FIELD_PREP(AIROHA_FOE_MAC_WDMA_WCID,
> info.wcid);
> } else {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port;
> u8 pse_port, channel;
>
> - if (!airoha_is_valid_gdm_port(eth, port))
> + if (!airoha_is_valid_gdm_dev(eth, dev))
> return -EINVAL;
>
> + port = dev->port;
> if (dsa_port >= 0 || eth->ports[1])
> pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
> : port->id;
> @@ -1473,7 +1476,7 @@ void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
> void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
> {
> struct airoha_eth *eth = port->qdma->eth;
> - struct net_device *dev = port->dev;
> + struct net_device *dev = port->dev->dev;
> const u8 *addr = dev->dev_addr;
> u32 val;
>
>
> --
> 2.54.0
>
Commenting on sashiko's report:
https://sashiko.dev/#/patchset/20260527-airoha-eth-multi-serdes-preliminary-v1-0-ec6ed73ef7fc%40kernel.org
- This isn't a bug introduced by this patch, but does airoha_get_dsa_tag()
lack packet length and linearization checks?
I do not think this is a real issue since airoha_eth dsa support can run
just with MTK dsa driver that adds MTK_HDR_LEN bytes into the skb
headroom.
Moreover, as pointed out by sashiko, this issue has not been introduced by
this patch.
Regards,
Lorenzo
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* [PATCH v2] arm64: dts: mediatek: mt7986a-bananapi-bpi-r3: add ramoops region
From: Martino Dell'Ambrogio @ 2026-05-28 12:36 UTC (permalink / raw)
To: matthias.bgg, angelogioacchino.delregno
Cc: kees, tony.luck, gpiccoli, linux-mediatek, linux-arm-kernel,
devicetree, linux-kernel, Martino Dell'Ambrogio
In-Reply-To: <20260528092807.1936177-1-tillo@tillo.ch>
Reserve 64 KiB of RAM just below the ARM Trusted Firmware secmon region
(0x42ff0000-0x43000000) for persistent kernel log storage via pstore/ramoops,
allowing post-panic console output and oops dumps to be recovered after a
warm reset. Without it, kernel crash logs on this board are lost when the
SoC reboots.
The zone sizes (record-size=8 KiB, console-size=32 KiB, ftrace-size=8 KiB,
pmsg-size=8 KiB) consume the full 64 KiB carve-out. The requested ecc-size=16
reserves a small Reed-Solomon parity block from each zone's own allocation
in persistent_ram_new(), which lets pstore recover dumps even when the panic
path truncates writes mid-record.
The no-map property is required so the reserved region is kept out of the
kernel linear map. ramoops remaps the carve-out write-combine via
ioremap_wc(); on arm64, leaving the same physical RAM mapped cacheable in
the linear map at the same time is an attribute-mismatch and risks losing
panic data to dirty cache evictions from the linear alias.
The region sits immediately below the ATF block already declared at
0x43000000 in mt7986a.dtsi, so no other reserved-memory child is moved or
resized. BPI-R3 ships with 2 GiB of DRAM starting at 0x40000000, well above
0x43000000, so the region is always within installed memory.
For the carve-out to actually preserve content across a reset, the boot
loader must avoid touching this region on warm reset; on standard BPI-R3
boards with the stock OpenWrt U-Boot fork this already holds.
Signed-off-by: Martino Dell'Ambrogio <tillo@tillo.ch>
---
Changes in v2:
- Add no-map; to keep the carve-out out of the kernel linear map and
avoid the cacheable/write-combine attribute mismatch on arm64.
(sashiko-bot, gemini-3.1-pro)
- Rewrite the ECC paragraph in the commit log: the zone sizes already
fill the 64 KiB region, so there is no remainder; ecc-size is carved
from each zone in persistent_ram_new(). (sashiko-bot, gemini-3.1-pro)
v1: https://lore.kernel.org/all/20260528092807.1936177-1-tillo@tillo.ch/
.../boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts b/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts
index 19f538d..31ee189 100644
--- a/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts
+++ b/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts
@@ -140,6 +140,19 @@ sfp2: sfp-2 {
};
};
+&{/reserved-memory} {
+ ramoops@42ff0000 {
+ compatible = "ramoops";
+ reg = <0 0x42ff0000 0 0x10000>;
+ no-map;
+ record-size = <0x2000>;
+ console-size = <0x8000>;
+ ftrace-size = <0x2000>;
+ pmsg-size = <0x2000>;
+ ecc-size = <16>;
+ };
+};
+
&cpu_thermal {
cooling-maps {
map-cpu-active-high {
--
2.47.3
^ permalink raw reply related
* [PATCH v2] arm64: dts: mediatek: mt7988a-bananapi-bpi-r4: add ramoops region
From: Martino Dell'Ambrogio @ 2026-05-28 12:36 UTC (permalink / raw)
To: matthias.bgg, angelogioacchino.delregno
Cc: kees, tony.luck, gpiccoli, linux-mediatek, linux-arm-kernel,
devicetree, linux-kernel, Martino Dell'Ambrogio
In-Reply-To: <20260528093038.1945245-1-tillo@tillo.ch>
Reserve 1 MiB of RAM just below the ARM Trusted Firmware secmon region
(0x42f00000-0x43000000) for persistent kernel log storage via pstore/ramoops,
allowing post-panic console output and oops dumps to be recovered after a
reboot. Without it, kernel crash logs on this board are lost when the SoC
warm-resets and the on-chip console buffer is reinitialised.
With record-size=128 KiB, console-size=256 KiB, ftrace-size=64 KiB and
pmsg-size=64 KiB, ramoops_probe() carves the post-console remainder
(640 KiB) into five 128 KiB kmsg records, with the requested ecc-size=16
reserving a small Reed-Solomon parity block from each zone's own
allocation (per persistent_ram_new()). The ECC lets pstore recover dumps
even when the panic path truncates writes mid-record.
The no-map property is required so the reserved region is kept out of the
kernel linear map. ramoops remaps the carve-out write-combine via
ioremap_wc(); on arm64, leaving the same physical RAM mapped cacheable in
the linear map at the same time is an attribute-mismatch and risks losing
panic data to dirty cache evictions from the linear alias.
The carve-out sits immediately below the ATF region already declared at
0x43000000 in mt7988a.dtsi, so no other reserved-memory child is moved or
resized. BPI-R4 ships with at least 4 GiB of DRAM starting at 0x40000000,
so the region is well within installed memory on every variant.
For the carve-out to actually preserve content across a reset, the boot
loader must also avoid touching this region on warm reset; on standard
BPI-R4 boards with the stock OpenWrt U-Boot fork this already holds.
Signed-off-by: Martino Dell'Ambrogio <tillo@tillo.ch>
---
Changes in v2:
- Add no-map; to keep the carve-out out of the kernel linear map and
avoid the cacheable/write-combine attribute mismatch on arm64.
(sashiko-bot, gemini-3.1-pro)
- Rewrite the ECC paragraph in the commit log: ramoops carves ecc-size
from each zone's own allocation in persistent_ram_new() and the
post-console remainder is split into record-size'd kmsg records, not
one record plus a separate ECC pool. (sashiko-bot, gemini-3.1-pro)
v1: https://lore.kernel.org/all/20260528093038.1945245-1-tillo@tillo.ch/
.../boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi
index 0ff69da..f7d4944 100644
--- a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi
+++ b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi
@@ -80,6 +80,19 @@ sfp1: sfp1 {
};
};
+&{/reserved-memory} {
+ ramoops@42f00000 {
+ compatible = "ramoops";
+ reg = <0 0x42f00000 0 0x100000>;
+ no-map;
+ record-size = <0x20000>;
+ console-size = <0x40000>;
+ ftrace-size = <0x10000>;
+ pmsg-size = <0x10000>;
+ ecc-size = <16>;
+ };
+};
+
&cci {
proc-supply = <&rt5190_buck3>;
};
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox