[rdkb][common][bsp][Refactor and sync kernel from openwrt]

[Description]
f937161 [MAC80211][hnat][Add Netfilter Netlink Ftnl package]
7926340 [kernel][common][i2c][add dps368 sensor support]
3b54134 [MAC80211][hnat][Add DSCP offload support]
0adc1d4 [MAC80211][hnat][Fix PPE entry clear issue]

[Release-log]

Change-Id: I509e8f209979486c288df2365eb7ca2a3a5b601f
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-10g-spim-nand.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-10g-spim-nand.dts
index d35be90..fc9d17c 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-10g-spim-nand.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-10g-spim-nand.dts
@@ -171,6 +171,11 @@
 		compatible = "wlf,wm8960";
 		reg = <0x1a>;
 	};
+
+	dps368: dps368@77 {
+		compatible = "infineon,dps310";
+		reg = <0x77>;
+	};
 };
 
 &spi0 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-e2p5g-spim-nand.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-e2p5g-spim-nand.dts
index fee356d..74fa426 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-e2p5g-spim-nand.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-e2p5g-spim-nand.dts
@@ -134,6 +134,11 @@
 	pinctrl-names = "default";
 	pinctrl-0 = <&i2c1_pins>;
 	status = "okay";
+
+	dps368: dps368@77 {
+		compatible = "infineon,dps310";
+		reg = <0x77>;
+	};
 };
 
 &pwm {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/iio/pressure/dps310.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/iio/pressure/dps310.c
new file mode 100644
index 0000000..72640cb
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/iio/pressure/dps310.c
@@ -0,0 +1,848 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright IBM Corp 2019
+/*
+ * The DPS310 is a barometric pressure and temperature sensor.
+ * Currently only reading a single temperature is supported by
+ * this driver.
+ *
+ * https://www.infineon.com/dgdl/?fileId=5546d462576f34750157750826c42242
+ *
+ * Temperature calculation:
+ *   c0 * 0.5 + c1 * T_raw / kT °C
+ *
+ * TODO:
+ *  - Optionally support the FIFO
+ */
+
+#include <linux/i2c.h>
+#include <linux/limits.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define DPS310_DEV_NAME		"dps310"
+
+#define DPS310_PRS_B0		0x00
+#define DPS310_PRS_B1		0x01
+#define DPS310_PRS_B2		0x02
+#define DPS310_TMP_B0		0x03
+#define DPS310_TMP_B1		0x04
+#define DPS310_TMP_B2		0x05
+#define DPS310_PRS_CFG		0x06
+#define  DPS310_PRS_RATE_BITS	GENMASK(6, 4)
+#define  DPS310_PRS_PRC_BITS	GENMASK(3, 0)
+#define DPS310_TMP_CFG		0x07
+#define  DPS310_TMP_RATE_BITS	GENMASK(6, 4)
+#define  DPS310_TMP_PRC_BITS	GENMASK(3, 0)
+#define  DPS310_TMP_EXT		BIT(7)
+#define DPS310_MEAS_CFG		0x08
+#define  DPS310_MEAS_CTRL_BITS	GENMASK(2, 0)
+#define   DPS310_PRS_EN		BIT(0)
+#define   DPS310_TEMP_EN	BIT(1)
+#define   DPS310_BACKGROUND	BIT(2)
+#define  DPS310_PRS_RDY		BIT(4)
+#define  DPS310_TMP_RDY		BIT(5)
+#define  DPS310_SENSOR_RDY	BIT(6)
+#define  DPS310_COEF_RDY	BIT(7)
+#define DPS310_CFG_REG		0x09
+#define  DPS310_INT_HL		BIT(7)
+#define  DPS310_TMP_SHIFT_EN	BIT(3)
+#define  DPS310_PRS_SHIFT_EN	BIT(4)
+#define  DPS310_FIFO_EN		BIT(5)
+#define  DPS310_SPI_EN		BIT(6)
+#define DPS310_RESET		0x0c
+#define  DPS310_RESET_MAGIC	0x09
+#define DPS310_COEF_BASE	0x10
+
+/* Make sure sleep time is <= 20ms for usleep_range */
+#define DPS310_POLL_SLEEP_US(t)		min(20000, (t) / 8)
+/* Silently handle error in rate value here */
+#define DPS310_POLL_TIMEOUT_US(rc)	((rc) <= 0 ? 1000000 : 1000000 / (rc))
+
+#define DPS310_PRS_BASE		DPS310_PRS_B0
+#define DPS310_TMP_BASE		DPS310_TMP_B0
+
+/*
+ * These values (defined in the spec) indicate how to scale the raw register
+ * values for each level of precision available.
+ */
+static const int scale_factors[] = {
+	 524288,
+	1572864,
+	3670016,
+	7864320,
+	 253952,
+	 516096,
+	1040384,
+	2088960,
+};
+
+struct dps310_data {
+	struct i2c_client *client;
+	struct regmap *regmap;
+	struct mutex lock;	/* Lock for sequential HW access functions */
+
+	s32 c0, c1;
+	s32 c00, c10, c20, c30, c01, c11, c21;
+	s32 pressure_raw;
+	s32 temp_raw;
+};
+
+static const struct iio_chan_spec dps310_channels[] = {
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
+			BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+			BIT(IIO_CHAN_INFO_PROCESSED),
+	},
+	{
+		.type = IIO_PRESSURE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
+			BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+			BIT(IIO_CHAN_INFO_PROCESSED),
+	},
+};
+
+/* To be called after checking the COEF_RDY bit in MEAS_CFG */
+static int dps310_get_coefs(struct dps310_data *data)
+{
+	int rc;
+	u8 coef[18];
+	u32 c0, c1;
+	u32 c00, c10, c20, c30, c01, c11, c21;
+
+	/* Read all sensor calibration coefficients from the COEF registers. */
+	rc = regmap_bulk_read(data->regmap, DPS310_COEF_BASE, coef,
+			      sizeof(coef));
+	if (rc < 0)
+		return rc;
+
+	/*
+	 * Calculate temperature calibration coefficients c0 and c1. The
+	 * numbers are 12-bit 2's complement numbers.
+	 */
+	c0 = (coef[0] << 4) | (coef[1] >> 4);
+	data->c0 = sign_extend32(c0, 11);
+
+	c1 = ((coef[1] & GENMASK(3, 0)) << 8) | coef[2];
+	data->c1 = sign_extend32(c1, 11);
+
+	/*
+	 * Calculate pressure calibration coefficients. c00 and c10 are 20 bit
+	 * 2's complement numbers, while the rest are 16 bit 2's complement
+	 * numbers.
+	 */
+	c00 = (coef[3] << 12) | (coef[4] << 4) | (coef[5] >> 4);
+	data->c00 = sign_extend32(c00, 19);
+
+	c10 = ((coef[5] & GENMASK(3, 0)) << 16) | (coef[6] << 8) | coef[7];
+	data->c10 = sign_extend32(c10, 19);
+
+	c01 = (coef[8] << 8) | coef[9];
+	data->c01 = sign_extend32(c01, 15);
+
+	c11 = (coef[10] << 8) | coef[11];
+	data->c11 = sign_extend32(c11, 15);
+
+	c20 = (coef[12] << 8) | coef[13];
+	data->c20 = sign_extend32(c20, 15);
+
+	c21 = (coef[14] << 8) | coef[15];
+	data->c21 = sign_extend32(c21, 15);
+
+	c30 = (coef[16] << 8) | coef[17];
+	data->c30 = sign_extend32(c30, 15);
+
+	return 0;
+}
+
+static int dps310_get_pres_precision(struct dps310_data *data)
+{
+	int rc;
+	int val;
+
+	rc = regmap_read(data->regmap, DPS310_PRS_CFG, &val);
+	if (rc < 0)
+		return rc;
+
+	return BIT(val & GENMASK(2, 0));
+}
+
+static int dps310_get_temp_precision(struct dps310_data *data)
+{
+	int rc;
+	int val;
+
+	rc = regmap_read(data->regmap, DPS310_TMP_CFG, &val);
+	if (rc < 0)
+		return rc;
+
+	/*
+	 * Scale factor is bottom 4 bits of the register, but 1111 is
+	 * reserved so just grab bottom three
+	 */
+	return BIT(val & GENMASK(2, 0));
+}
+
+/* Called with lock held */
+static int dps310_set_pres_precision(struct dps310_data *data, int val)
+{
+	int rc;
+	u8 shift_en;
+
+	if (val < 0 || val > 128)
+		return -EINVAL;
+
+	shift_en = val >= 16 ? DPS310_PRS_SHIFT_EN : 0;
+	rc = regmap_write_bits(data->regmap, DPS310_CFG_REG,
+			       DPS310_PRS_SHIFT_EN, shift_en);
+	if (rc)
+		return rc;
+
+	return regmap_update_bits(data->regmap, DPS310_PRS_CFG,
+				  DPS310_PRS_PRC_BITS, ilog2(val));
+}
+
+/* Called with lock held */
+static int dps310_set_temp_precision(struct dps310_data *data, int val)
+{
+	int rc;
+	u8 shift_en;
+
+	if (val < 0 || val > 128)
+		return -EINVAL;
+
+	shift_en = val >= 16 ? DPS310_TMP_SHIFT_EN : 0;
+	rc = regmap_write_bits(data->regmap, DPS310_CFG_REG,
+			       DPS310_TMP_SHIFT_EN, shift_en);
+	if (rc)
+		return rc;
+
+	return regmap_update_bits(data->regmap, DPS310_TMP_CFG,
+				  DPS310_TMP_PRC_BITS, ilog2(val));
+}
+
+/* Called with lock held */
+static int dps310_set_pres_samp_freq(struct dps310_data *data, int freq)
+{
+	u8 val;
+
+	if (freq < 0 || freq > 128)
+		return -EINVAL;
+
+	val = ilog2(freq) << 4;
+
+	return regmap_update_bits(data->regmap, DPS310_PRS_CFG,
+				  DPS310_PRS_RATE_BITS, val);
+}
+
+/* Called with lock held */
+static int dps310_set_temp_samp_freq(struct dps310_data *data, int freq)
+{
+	u8 val;
+
+	if (freq < 0 || freq > 128)
+		return -EINVAL;
+
+	val = ilog2(freq) << 4;
+
+	return regmap_update_bits(data->regmap, DPS310_TMP_CFG,
+				  DPS310_TMP_RATE_BITS, val);
+}
+
+static int dps310_get_pres_samp_freq(struct dps310_data *data)
+{
+	int rc;
+	int val;
+
+	rc = regmap_read(data->regmap, DPS310_PRS_CFG, &val);
+	if (rc < 0)
+		return rc;
+
+	return BIT((val & DPS310_PRS_RATE_BITS) >> 4);
+}
+
+static int dps310_get_temp_samp_freq(struct dps310_data *data)
+{
+	int rc;
+	int val;
+
+	rc = regmap_read(data->regmap, DPS310_TMP_CFG, &val);
+	if (rc < 0)
+		return rc;
+
+	return BIT((val & DPS310_TMP_RATE_BITS) >> 4);
+}
+
+static int dps310_get_pres_k(struct dps310_data *data)
+{
+	int rc = dps310_get_pres_precision(data);
+
+	if (rc < 0)
+		return rc;
+
+	return scale_factors[ilog2(rc)];
+}
+
+static int dps310_get_temp_k(struct dps310_data *data)
+{
+	int rc = dps310_get_temp_precision(data);
+
+	if (rc < 0)
+		return rc;
+
+	return scale_factors[ilog2(rc)];
+}
+
+static int dps310_read_pres_raw(struct dps310_data *data)
+{
+	int rc;
+	int rate;
+	int ready;
+	int timeout;
+	s32 raw;
+	u8 val[3];
+
+	if (mutex_lock_interruptible(&data->lock))
+		return -EINTR;
+
+	rate = dps310_get_pres_samp_freq(data);
+	timeout = DPS310_POLL_TIMEOUT_US(rate);
+
+	/* Poll for sensor readiness; base the timeout upon the sample rate. */
+	rc = regmap_read_poll_timeout(data->regmap, DPS310_MEAS_CFG, ready,
+				      ready & DPS310_PRS_RDY,
+				      DPS310_POLL_SLEEP_US(timeout), timeout);
+	if (rc)
+		goto done;
+
+	rc = regmap_bulk_read(data->regmap, DPS310_PRS_BASE, val, sizeof(val));
+	if (rc < 0)
+		goto done;
+
+	raw = (val[0] << 16) | (val[1] << 8) | val[2];
+	data->pressure_raw = sign_extend32(raw, 23);
+
+done:
+	mutex_unlock(&data->lock);
+	return rc;
+}
+
+/* Called with lock held */
+static int dps310_read_temp_ready(struct dps310_data *data)
+{
+	int rc;
+	u8 val[3];
+	s32 raw;
+
+	rc = regmap_bulk_read(data->regmap, DPS310_TMP_BASE, val, sizeof(val));
+	if (rc < 0)
+		return rc;
+
+	raw = (val[0] << 16) | (val[1] << 8) | val[2];
+	data->temp_raw = sign_extend32(raw, 23);
+
+	return 0;
+}
+
+static int dps310_read_temp_raw(struct dps310_data *data)
+{
+	int rc;
+	int rate;
+	int ready;
+	int timeout;
+
+	if (mutex_lock_interruptible(&data->lock))
+		return -EINTR;
+
+	rate = dps310_get_temp_samp_freq(data);
+	timeout = DPS310_POLL_TIMEOUT_US(rate);
+
+	/* Poll for sensor readiness; base the timeout upon the sample rate. */
+	rc = regmap_read_poll_timeout(data->regmap, DPS310_MEAS_CFG, ready,
+				      ready & DPS310_TMP_RDY,
+				      DPS310_POLL_SLEEP_US(timeout), timeout);
+	if (rc < 0)
+		goto done;
+
+	rc = dps310_read_temp_ready(data);
+
+done:
+	mutex_unlock(&data->lock);
+	return rc;
+}
+
+static bool dps310_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case DPS310_PRS_CFG:
+	case DPS310_TMP_CFG:
+	case DPS310_MEAS_CFG:
+	case DPS310_CFG_REG:
+	case DPS310_RESET:
+	/* No documentation available on the registers below */
+	case 0x0e:
+	case 0x0f:
+	case 0x62:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool dps310_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case DPS310_PRS_B0:
+	case DPS310_PRS_B1:
+	case DPS310_PRS_B2:
+	case DPS310_TMP_B0:
+	case DPS310_TMP_B1:
+	case DPS310_TMP_B2:
+	case DPS310_MEAS_CFG:
+	case 0x32:	/* No documentation available on this register */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static int dps310_write_raw(struct iio_dev *iio,
+			    struct iio_chan_spec const *chan, int val,
+			    int val2, long mask)
+{
+	int rc;
+	struct dps310_data *data = iio_priv(iio);
+
+	if (mutex_lock_interruptible(&data->lock))
+		return -EINTR;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		switch (chan->type) {
+		case IIO_PRESSURE:
+			rc = dps310_set_pres_samp_freq(data, val);
+			break;
+
+		case IIO_TEMP:
+			rc = dps310_set_temp_samp_freq(data, val);
+			break;
+
+		default:
+			rc = -EINVAL;
+			break;
+		}
+		break;
+
+	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+		switch (chan->type) {
+		case IIO_PRESSURE:
+			rc = dps310_set_pres_precision(data, val);
+			break;
+
+		case IIO_TEMP:
+			rc = dps310_set_temp_precision(data, val);
+			break;
+
+		default:
+			rc = -EINVAL;
+			break;
+		}
+		break;
+
+	default:
+		rc = -EINVAL;
+		break;
+	}
+
+	mutex_unlock(&data->lock);
+	return rc;
+}
+
+static int dps310_calculate_pressure(struct dps310_data *data)
+{
+	int i;
+	int rc;
+	int t_ready;
+	int kpi = dps310_get_pres_k(data);
+	int kti = dps310_get_temp_k(data);
+	s64 rem = 0ULL;
+	s64 pressure = 0ULL;
+	s64 p;
+	s64 t;
+	s64 denoms[7];
+	s64 nums[7];
+	s64 rems[7];
+	s64 kp;
+	s64 kt;
+
+	if (kpi < 0)
+		return kpi;
+
+	if (kti < 0)
+		return kti;
+
+	kp = (s64)kpi;
+	kt = (s64)kti;
+
+	/* Refresh temp if it's ready, otherwise just use the latest value */
+	if (mutex_trylock(&data->lock)) {
+		rc = regmap_read(data->regmap, DPS310_MEAS_CFG, &t_ready);
+		if (rc >= 0 && t_ready & DPS310_TMP_RDY)
+			dps310_read_temp_ready(data);
+
+		mutex_unlock(&data->lock);
+	}
+
+	p = (s64)data->pressure_raw;
+	t = (s64)data->temp_raw;
+
+	/* Section 4.9.1 of the DPS310 spec; algebra'd to avoid underflow */
+	nums[0] = (s64)data->c00;
+	denoms[0] = 1LL;
+	nums[1] = p * (s64)data->c10;
+	denoms[1] = kp;
+	nums[2] = p * p * (s64)data->c20;
+	denoms[2] = kp * kp;
+	nums[3] = p * p * p * (s64)data->c30;
+	denoms[3] = kp * kp * kp;
+	nums[4] = t * (s64)data->c01;
+	denoms[4] = kt;
+	nums[5] = t * p * (s64)data->c11;
+	denoms[5] = kp * kt;
+	nums[6] = t * p * p * (s64)data->c21;
+	denoms[6] = kp * kp * kt;
+
+	/* Kernel lacks a div64_s64_rem function; denoms are all positive */
+	for (i = 0; i < 7; ++i) {
+		u64 irem;
+
+		if (nums[i] < 0LL) {
+			pressure -= div64_u64_rem(-nums[i], denoms[i], &irem);
+			rems[i] = -irem;
+		} else {
+			pressure += div64_u64_rem(nums[i], denoms[i], &irem);
+			rems[i] = (s64)irem;
+		}
+	}
+
+	/* Increase precision and calculate the remainder sum */
+	for (i = 0; i < 7; ++i)
+		rem += div64_s64((s64)rems[i] * 1000000000LL, denoms[i]);
+
+	pressure += div_s64(rem, 1000000000LL);
+	if (pressure < 0LL)
+		return -ERANGE;
+
+	return (int)min_t(s64, pressure, INT_MAX);
+}
+
+static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2,
+				long mask)
+{
+	int rc;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		rc = dps310_get_pres_samp_freq(data);
+		if (rc < 0)
+			return rc;
+
+		*val = rc;
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_PROCESSED:
+		rc = dps310_read_pres_raw(data);
+		if (rc)
+			return rc;
+
+		rc = dps310_calculate_pressure(data);
+		if (rc < 0)
+			return rc;
+
+		*val = rc;
+		*val2 = 1000; /* Convert Pa to KPa per IIO ABI */
+		return IIO_VAL_FRACTIONAL;
+
+	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+		rc = dps310_get_pres_precision(data);
+		if (rc < 0)
+			return rc;
+
+		*val = rc;
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int dps310_calculate_temp(struct dps310_data *data)
+{
+	s64 c0;
+	s64 t;
+	int kt = dps310_get_temp_k(data);
+
+	if (kt < 0)
+		return kt;
+
+	/* Obtain inverse-scaled offset */
+	c0 = div_s64((s64)kt * (s64)data->c0, 2);
+
+	/* Add the offset to the unscaled temperature */
+	t = c0 + ((s64)data->temp_raw * (s64)data->c1);
+
+	/* Convert to milliCelsius and scale the temperature */
+	return (int)div_s64(t * 1000LL, kt);
+}
+
+static int dps310_read_temp(struct dps310_data *data, int *val, int *val2,
+			    long mask)
+{
+	int rc;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		rc = dps310_get_temp_samp_freq(data);
+		if (rc < 0)
+			return rc;
+
+		*val = rc;
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_PROCESSED:
+		rc = dps310_read_temp_raw(data);
+		if (rc)
+			return rc;
+
+		rc = dps310_calculate_temp(data);
+		if (rc < 0)
+			return rc;
+
+		*val = rc;
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+		rc = dps310_get_temp_precision(data);
+		if (rc < 0)
+			return rc;
+
+		*val = rc;
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int dps310_read_raw(struct iio_dev *iio,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct dps310_data *data = iio_priv(iio);
+
+	switch (chan->type) {
+	case IIO_PRESSURE:
+		return dps310_read_pressure(data, val, val2, mask);
+
+	case IIO_TEMP:
+		return dps310_read_temp(data, val, val2, mask);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static void dps310_reset(void *action_data)
+{
+	struct dps310_data *data = action_data;
+
+	regmap_write(data->regmap, DPS310_RESET, DPS310_RESET_MAGIC);
+}
+
+static const struct regmap_config dps310_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.writeable_reg = dps310_is_writeable_reg,
+	.volatile_reg = dps310_is_volatile_reg,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 0x62, /* No documentation available on this register */
+};
+
+static const struct iio_info dps310_info = {
+	.read_raw = dps310_read_raw,
+	.write_raw = dps310_write_raw,
+};
+
+/*
+ * Some verions of chip will read temperatures in the ~60C range when
+ * its actually ~20C. This is the manufacturer recommended workaround
+ * to correct the issue. The registers used below are undocumented.
+ */
+static int dps310_temp_workaround(struct dps310_data *data)
+{
+	int rc;
+	int reg;
+
+	rc = regmap_read(data->regmap, 0x32, &reg);
+	if (rc < 0)
+		return rc;
+
+	/*
+	 * If bit 1 is set then the device is okay, and the workaround does not
+	 * need to be applied
+	 */
+	if (reg & BIT(1))
+		return 0;
+
+	rc = regmap_write(data->regmap, 0x0e, 0xA5);
+	if (rc < 0)
+		return rc;
+
+	rc = regmap_write(data->regmap, 0x0f, 0x96);
+	if (rc < 0)
+		return rc;
+
+	rc = regmap_write(data->regmap, 0x62, 0x02);
+	if (rc < 0)
+		return rc;
+
+	rc = regmap_write(data->regmap, 0x0e, 0x00);
+	if (rc < 0)
+		return rc;
+
+	return regmap_write(data->regmap, 0x0f, 0x00);
+}
+
+static int dps310_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct dps310_data *data;
+	struct iio_dev *iio;
+	int rc, ready;
+
+	iio = devm_iio_device_alloc(&client->dev,  sizeof(*data));
+	if (!iio)
+		return -ENOMEM;
+
+	data = iio_priv(iio);
+	data->client = client;
+	mutex_init(&data->lock);
+
+	iio->name = id->name;
+	iio->channels = dps310_channels;
+	iio->num_channels = ARRAY_SIZE(dps310_channels);
+	iio->info = &dps310_info;
+	iio->modes = INDIO_DIRECT_MODE;
+
+	data->regmap = devm_regmap_init_i2c(client, &dps310_regmap_config);
+	if (IS_ERR(data->regmap))
+		return PTR_ERR(data->regmap);
+
+	/* Register to run the device reset when the device is removed */
+	rc = devm_add_action_or_reset(&client->dev, dps310_reset, data);
+	if (rc)
+		return rc;
+
+	/*
+	 * Set up pressure sensor in single sample, one measurement per second
+	 * mode
+	 */
+	rc = regmap_write(data->regmap, DPS310_PRS_CFG, 0);
+
+	/*
+	 * Set up external (MEMS) temperature sensor in single sample, one
+	 * measurement per second mode
+	 */
+	rc = regmap_write(data->regmap, DPS310_TMP_CFG, DPS310_TMP_EXT);
+	if (rc < 0)
+		return rc;
+
+	/* Temp and pressure shifts are disabled when PRC <= 8 */
+	rc = regmap_write_bits(data->regmap, DPS310_CFG_REG,
+			       DPS310_PRS_SHIFT_EN | DPS310_TMP_SHIFT_EN, 0);
+	if (rc < 0)
+		return rc;
+
+	/* MEAS_CFG doesn't update correctly unless first written with 0 */
+	rc = regmap_write_bits(data->regmap, DPS310_MEAS_CFG,
+			       DPS310_MEAS_CTRL_BITS, 0);
+	if (rc < 0)
+		return rc;
+
+	/* Turn on temperature and pressure measurement in the background */
+	rc = regmap_write_bits(data->regmap, DPS310_MEAS_CFG,
+			       DPS310_MEAS_CTRL_BITS, DPS310_PRS_EN |
+			       DPS310_TEMP_EN | DPS310_BACKGROUND);
+	if (rc < 0)
+		return rc;
+
+	/*
+	 * Calibration coefficients required for reporting temperature.
+	 * They are available 40ms after the device has started
+	 */
+	rc = regmap_read_poll_timeout(data->regmap, DPS310_MEAS_CFG, ready,
+				      ready & DPS310_COEF_RDY, 10000, 40000);
+	if (rc < 0)
+		return rc;
+
+	rc = dps310_get_coefs(data);
+	if (rc < 0)
+		return rc;
+
+	rc = dps310_temp_workaround(data);
+	if (rc < 0)
+		return rc;
+
+	rc = devm_iio_device_register(&client->dev, iio);
+	if (rc)
+		return rc;
+
+	i2c_set_clientdata(client, iio);
+
+	return 0;
+}
+
+static int dps310_remove(struct i2c_client *client)
+{
+	return 0;
+}
+
+static const struct i2c_device_id dps310_id[] = {
+	{ DPS310_DEV_NAME, 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, dps310_id);
+
+static const struct acpi_device_id dps310_acpi_match[] = {
+	{ "IFX3100" },
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, dps310_acpi_match);
+
+static const struct of_device_id dps310_of_match[] = {
+	{ .compatible = "infineon,dps310", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, dps310_of_match);
+
+static struct i2c_driver dps310_driver = {
+	.driver = {
+		.name = DPS310_DEV_NAME,
+		.acpi_match_table = dps310_acpi_match,
+		.of_match_table = dps310_of_match,
+	},
+	.probe = dps310_probe,
+	.remove = dps310_remove,
+	.id_table = dps310_id,
+};
+module_i2c_driver(dps310_driver);
+
+MODULE_AUTHOR("Joel Stanley <joel@jms.id.au>");
+MODULE_DESCRIPTION("Infineon DPS310 pressure and temperature sensor");
+MODULE_LICENSE("GPL v2");
+
+
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9993-add-wed.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9993-add-wed.patch
index baa88ef..b4f8e4e 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9993-add-wed.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9993-add-wed.patch
@@ -654,7 +654,7 @@
  		entry->ipv6.ib2 = val;
  		l2 = &entry->ipv6.l2;
  	} else {
-@@ -329,32 +351,167 @@ int mtk_foe_entry_set_pppoe(struct mtk_foe_entry *entry, int sid)
+@@ -329,32 +351,168 @@ int mtk_foe_entry_set_pppoe(struct mtk_foe_entry *entry, int sid)
  	return 0;
  }
  
@@ -723,6 +723,7 @@
 +		ppe->foe_table[entry->hash].ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE,
 +							      MTK_FOE_STATE_INVALID);
 +		dma_wmb();
++		mtk_ppe_cache_clear(ppe);
 +	}
 +	entry->hash = 0xffff;
 +
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-2-flow-offload-add-mtkhnat-flow-accounting.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-2-flow-offload-add-mtkhnat-flow-accounting.patch
index f6243d0..5b6253f 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-2-flow-offload-add-mtkhnat-flow-accounting.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-2-flow-offload-add-mtkhnat-flow-accounting.patch
@@ -139,9 +139,10 @@
  static void mtk_ppe_cache_clear(struct mtk_ppe *ppe)
  {
  	ppe_set(ppe, MTK_PPE_CACHE_CTL, MTK_PPE_CACHE_CTL_CLEAR);
-@@ -412,6 +452,18 @@ __mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+@@ -412,7 +452,19 @@ __mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
  							      MTK_FOE_STATE_INVALID);
  		dma_wmb();
+ 		mtk_ppe_cache_clear(ppe);
 +
 +		if (ppe->accounting) {
 +			struct mtk_foe_accounting *acct, *acct_updated;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-8-flow-offload-add-mtkhnat-dscp.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-8-flow-offload-add-mtkhnat-dscp.patch
new file mode 100644
index 0000000..fe278a7
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-8-flow-offload-add-mtkhnat-dscp.patch
@@ -0,0 +1,169 @@
+diff --git a/drivers/net/ethernet/mediatek/mtk_ppe.c b/drivers/net/ethernet/mediatek/mtk_ppe.c
+index c2416b1..bc13a9b 100755
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -435,6 +435,17 @@ int mtk_foe_entry_set_qid(struct mtk_foe_entry *entry, int qid)
+ 
+ 	return 0;
+ }
++
++int mtk_foe_entry_set_dscp(struct mtk_foe_entry *entry, int dscp)
++{
++	u32 *ib2 = mtk_foe_entry_ib2(entry);
++
++	*ib2 &= ~MTK_FOE_IB2_DSCP;
++	*ib2 |= FIELD_PREP(MTK_FOE_IB2_DSCP, dscp);
++
++	return 0;
++}
++
+ static inline bool mtk_foe_entry_usable(struct mtk_foe_entry *entry)
+ {
+ 	return !(entry->ib1 & MTK_FOE_IB1_STATIC) &&
+diff --git a/drivers/net/ethernet/mediatek/mtk_ppe.h b/drivers/net/ethernet/mediatek/mtk_ppe.h
+index e7ecbf7..df10040 100644
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -430,6 +430,7 @@ int mtk_foe_entry_set_pppoe(struct mtk_foe_entry *entry, int sid);
+ int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
+ 			   int bss, int wcid);
+ int mtk_foe_entry_set_qid(struct mtk_foe_entry *entry, int qid);
++int mtk_foe_entry_set_dscp(struct mtk_foe_entry *entry, int dscp);
+ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+index 23d2048..9bc0857 100644
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -246,6 +246,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f)
+ 	int wed_index = -1;
+ 	u16 addr_type = 0;
+ 	u8 l4proto = 0;
++	u8 dscp = 0;
+ 	int err = 0;
+ 	int i;
+ 
+@@ -282,6 +283,15 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f)
+ 		return -EOPNOTSUPP;
+ 	}
+ 
++	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) {
++		struct flow_match_ip match;
++
++		flow_rule_match_ip(rule, &match);
++		dscp = match.key->tos;
++	} else {
++		return -EOPNOTSUPP;
++	}
++
+ 	switch (addr_type) {
+ 	case 0:
+ 		offload_type = MTK_PPE_PKT_TYPE_BRIDGE;
+@@ -441,6 +451,8 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f)
+ 	if (data.pppoe.num == 1)
+ 		mtk_foe_entry_set_pppoe(&foe, data.pppoe.sid);
+ 
++	mtk_foe_entry_set_dscp(&foe, dscp);
++
+ 	err = mtk_flow_set_output_device(eth, &foe, odev, f->flow->ct, data.eth.h_dest,
+ 					 &wed_index);
+ 	if (err)
+diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
+index 55359dd..1a23c03 100644
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -36,6 +36,7 @@ struct nf_flow_key {
+ 	};
+ 	struct flow_dissector_key_tcp			tcp;
+ 	struct flow_dissector_key_ports			tp;
++	struct flow_dissector_key_ip			ip;
+ } __aligned(BITS_PER_LONG / 8); /* Ensure that we can do comparisons as longs. */
+ 
+ struct nf_flow_match {
+@@ -145,6 +146,7 @@ struct flow_offload_tuple {
+ 			u8		h_dest[ETH_ALEN];
+ 		} out;
+ 	};
++	u8				tos;
+ };
+ 
+ struct flow_offload_tuple_rhash {
+diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
+index 61cc518..c1a5f64 100644
+--- a/net/netfilter/nf_flow_table_offload.c
++++ b/net/netfilter/nf_flow_table_offload.c
+@@ -104,6 +104,7 @@ static int nf_flow_rule_match(struct nf_flow_match *match,
+ 	NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_IPV6_ADDRS, ipv6);
+ 	NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_TCP, tcp);
+ 	NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_PORTS, tp);
++	NF_FLOW_DISSECTOR(match, FLOW_DISSECTOR_KEY_IP, ip);
+ 
+ 	if (other_dst && other_dst->lwtstate) {
+ 		tun_info = lwt_tun_info(other_dst->lwtstate);
+@@ -183,10 +184,14 @@ static int nf_flow_rule_match(struct nf_flow_match *match,
+ 	key->tp.dst = tuple->dst_port;
+ 	mask->tp.dst = 0xffff;
+ 
++	key->ip.tos = tuple->tos;
++	mask->ip.tos = 0xff;
++
+ 	match->dissector.used_keys |= BIT(FLOW_DISSECTOR_KEY_META) |
+ 				      BIT(FLOW_DISSECTOR_KEY_CONTROL) |
+ 				      BIT(FLOW_DISSECTOR_KEY_BASIC) |
+-				      BIT(FLOW_DISSECTOR_KEY_PORTS);
++				      BIT(FLOW_DISSECTOR_KEY_PORTS) |
++				      BIT(FLOW_DISSECTOR_KEY_IP);
+ 	return 0;
+ }
+ 
+diff --git a/net/netfilter/xt_FLOWOFFLOAD.c b/net/netfilter/xt_FLOWOFFLOAD.c
+index 2d5c3cc..b231dd7 100644
+--- a/net/netfilter/xt_FLOWOFFLOAD.c
++++ b/net/netfilter/xt_FLOWOFFLOAD.c
+@@ -49,6 +49,35 @@ static DEFINE_SPINLOCK(hooks_lock);
+ 
+ struct xt_flowoffload_table flowtable[2];
+ 
++static int
++xt_flowoffload_dscp_init(struct sk_buff *skb, struct flow_offload *flow,
++			 enum ip_conntrack_dir dir)
++{
++	const struct flow_offload_tuple *flow_tuple = &flow->tuplehash[dir].tuple;
++	struct iphdr *iph;
++	struct ipv6hdr *ip6h;
++	u32 offset = 0;
++	u8 tos = 0;
++
++	switch (flow_tuple->l3proto) {
++	case NFPROTO_IPV4:
++		iph = (struct iphdr *)(skb_network_header(skb) + offset);
++		tos = iph->tos;
++		break;
++	case NFPROTO_IPV6:
++		ip6h = (struct ipv6hdr *)(skb_network_header(skb) + offset);
++		tos = ipv6_get_dsfield(ip6h);
++		break;
++	default:
++		return -1;
++	};
++
++	flow->tuplehash[dir].tuple.tos = tos;
++	flow->tuplehash[!dir].tuple.tos = tos;
++
++	return 0;
++}
++
+ static unsigned int
+ xt_flowoffload_net_hook(void *priv, struct sk_buff *skb,
+ 			const struct nf_hook_state *state)
+@@ -599,6 +628,9 @@ flowoffload_tg(struct sk_buff *skb, const struct xt_action_param *par)
+ 	if (flow_offload_route_init(flow, &route) < 0)
+ 		goto err_flow_add;
+ 
++	if (xt_flowoffload_dscp_init(skb, flow, dir) < 0)
++		goto err_flow_add;
++
+ 	if (tcph) {
+ 		ct->proto.tcp.seen[0].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
+ 		ct->proto.tcp.seen[1].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-9-flow-offload-add-mtkhnat-netlink.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-9-flow-offload-add-mtkhnat-netlink.patch
new file mode 100644
index 0000000..3b9e863
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-9-flow-offload-add-mtkhnat-netlink.patch
@@ -0,0 +1,337 @@
+diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
+index 8a84de3..1a23c03 100644
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -276,6 +276,7 @@ int nf_flow_table_init(struct nf_flowtable *flow_table);
+ void nf_flow_table_free(struct nf_flowtable *flow_table);
+ 
+ void flow_offload_teardown(struct flow_offload *flow);
++void flow_offload_teardown_by_tuple(struct flow_offload_tuple *tuple);
+ 
+ int nf_flow_table_iterate(struct nf_flowtable *flow_table,
+ 			  void (*iter)(struct flow_offload *flow, void *data),
+diff --git a/include/uapi/linux/netfilter/nfnetlink.h b/include/uapi/linux/netfilter/nfnetlink.h
+index 5bc960f..603d9c0 100644
+--- a/include/uapi/linux/netfilter/nfnetlink.h
++++ b/include/uapi/linux/netfilter/nfnetlink.h
+@@ -60,7 +60,8 @@ struct nfgenmsg {
+ #define NFNL_SUBSYS_CTHELPER		9
+ #define NFNL_SUBSYS_NFTABLES		10
+ #define NFNL_SUBSYS_NFT_COMPAT		11
+-#define NFNL_SUBSYS_COUNT		12
++#define NFNL_SUBSYS_FLOWTABLE		12
++#define NFNL_SUBSYS_COUNT		13
+ 
+ /* Reserved control nfnetlink messages */
+ #define NFNL_MSG_BATCH_BEGIN		NLMSG_MIN_TYPE
+diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig
+index 5d690ab..8ec87aa 100644
+--- a/net/netfilter/Kconfig
++++ b/net/netfilter/Kconfig
+@@ -708,6 +708,15 @@ config NF_FLOW_TABLE
+ 
+ 	  To compile it as a module, choose M here.
+ 
++config NF_FLOW_TABLE_NETLINK
++	tristate "Netfilter flow table netlink module"
++	depends on NETFILTER_INGRESS
++	depends on NF_CONNTRACK
++	help
++	  This option adds the flow table core infrastructure.
++
++	  To compile it as a module, choose M here.
++
+ config NETFILTER_XTABLES
+ 	tristate "Netfilter Xtables support (required for ip_tables)"
+ 	default m if NETFILTER_ADVANCED=n
+diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile
+index d93a121..fa6ffb1 100644
+--- a/net/netfilter/Makefile
++++ b/net/netfilter/Makefile
+@@ -124,6 +124,7 @@ nf_flow_table-objs		:= nf_flow_table_core.o nf_flow_table_ip.o \
+ 				   nf_flow_table_offload.o
+ 
+ obj-$(CONFIG_NF_FLOW_TABLE_INET) += nf_flow_table_inet.o
++obj-$(CONFIG_NF_FLOW_TABLE_NETLINK)	+= nf_flow_table_netlink.o
+ 
+ # generic X tables
+ obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o
+diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
+index 1036558..a0f52f6 100644
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -373,6 +373,29 @@ void flow_offload_teardown(struct flow_offload *flow)
+ }
+ EXPORT_SYMBOL_GPL(flow_offload_teardown);
+ 
++void flow_offload_teardown_by_tuple(struct flow_offload_tuple *tuple)
++{
++	struct net_device *netdev;
++	struct nf_flowtable *flowtable;
++	struct flow_offload_tuple_rhash *tuplehash;
++	struct flow_offload *flow;
++	int dir;
++
++	list_for_each_entry(flowtable, &flowtables, list) {
++		for_each_netdev(&init_net, netdev) {
++			tuple->iifidx = netdev->ifindex;
++			tuplehash = flow_offload_lookup(flowtable, tuple);
++			if (!tuplehash)
++				continue;
++
++			dir = tuplehash->tuple.dir;
++			flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
++			flow_offload_teardown(flow);
++		}
++	};
++}
++EXPORT_SYMBOL_GPL(flow_offload_teardown_by_tuple);
++
+ struct flow_offload_tuple_rhash *
+ flow_offload_lookup(struct nf_flowtable *flow_table,
+ 		    struct flow_offload_tuple *tuple)
+diff --git a/net/netfilter/nf_flow_table_netlink.c b/net/netfilter/nf_flow_table_netlink.c
+new file mode 100644
+index 0000000..f05f29e
+--- /dev/null
++++ b/net/netfilter/nf_flow_table_netlink.c
+@@ -0,0 +1,239 @@
++#include <linux/types.h>
++#include <linux/kernel.h>
++#include <linux/init.h>
++#include <linux/module.h>
++#include <linux/netfilter.h>
++#include <linux/netlink.h>
++#include <net/netlink.h>
++#include <net/ip.h>
++#include <linux/netfilter/nfnetlink.h>
++#include <net/netfilter/nf_flow_table.h>
++
++enum ft_netlink_msg_types {
++	FT_MSG_DEL,
++	FT_MSG_ADD,
++	FT_MSG_FLUSH,
++	FT_MSG_MAX
++};
++
++enum ftattr_type {
++	FTA_UNSPEC,
++	FTA_TUPLE,
++	__FTA_MAX
++};
++#define FTA_MAX (__FTA_MAX - 1)
++
++enum ftattr_tuple {
++	FTA_TUPLE_UNSPEC,
++	FTA_TUPLE_IP,
++	FTA_TUPLE_PROTO,
++	FTA_TUPLE_ZONE,
++	__FTA_TUPLE_MAX
++};
++#define FTA_TUPLE_MAX (__FTA_TUPLE_MAX - 1)
++
++enum ftattr_ip {
++	FTA_IP_UNSPEC,
++	FTA_IP_V4_SRC,
++	FTA_IP_V4_DST,
++	__FTA_IP_MAX
++};
++#define FTA_IP_MAX (__FTA_IP_MAX - 1)
++
++enum ftattr_l4proto {
++	FTA_PROTO_UNSPEC,
++	FTA_PROTO_NUM,
++	FTA_PROTO_SPORT,
++	FTA_PROTO_DPORT,
++	__FTA_PROTO_MAX
++};
++#define FTA_PROTO_MAX (__FTA_PROTO_MAX - 1)
++
++static const struct nla_policy tuple_nla_policy[FTA_TUPLE_MAX + 1] = {
++	[FTA_TUPLE_IP]		= { .type = NLA_NESTED },
++	[FTA_TUPLE_PROTO]	= { .type = NLA_NESTED },
++	[FTA_TUPLE_ZONE]	= { .type = NLA_U16 },
++};
++
++static const struct nla_policy ip_nla_policy[FTA_IP_MAX + 1] = {
++	[FTA_IP_V4_SRC]		= { .type = NLA_U32 },
++	[FTA_IP_V4_DST]		= { .type = NLA_U32 },
++};
++
++static const struct nla_policy l4proto_nla_policy[FTA_PROTO_MAX + 1] = {
++	[FTA_PROTO_NUM]		= { .type = NLA_U8 },
++	[FTA_PROTO_SPORT]	= {.type = NLA_U16},
++	[FTA_PROTO_DPORT]	= {.type = NLA_U16},
++};
++
++static inline int ftnetlink_parse_tuple_ip(struct nlattr *attr,
++					   struct flow_offload_tuple *tuple)
++{
++	struct nlattr *tb[FTA_IP_MAX+1];
++	int err;
++
++	err = nla_parse_nested_deprecated(tb, FTA_IP_MAX, attr, ip_nla_policy, NULL);
++
++	if (err < 0)
++		return err;
++
++	switch (tuple->l3proto) {
++	case NFPROTO_IPV4:
++		if (!tb[FTA_IP_V4_SRC] || !tb[FTA_IP_V4_DST])
++			return -EINVAL;
++
++		tuple->src_v4.s_addr = nla_get_in_addr(tb[FTA_IP_V4_SRC]);
++		tuple->dst_v4.s_addr = nla_get_in_addr(tb[FTA_IP_V4_DST]);
++	}
++
++	return err;
++}
++
++static inline int ftnetlink_parse_tuple_proto(struct nlattr *attr,
++					      struct flow_offload_tuple *tuple)
++{
++	struct nlattr *tb[FTA_PROTO_MAX+1];
++	int err;
++
++	err = nla_parse_nested_deprecated(tb, FTA_PROTO_MAX, attr, l4proto_nla_policy, NULL);
++
++	if(err < 0)
++		return err;
++
++	if (!tb[FTA_PROTO_NUM] || !tb[FTA_PROTO_SPORT] || !tb[FTA_PROTO_DPORT])
++		return -EINVAL;
++
++	tuple->l4proto = nla_get_u8(tb[FTA_PROTO_NUM]);
++	tuple->src_port = nla_get_u16(tb[FTA_PROTO_SPORT]);
++	tuple->dst_port = nla_get_u16(tb[FTA_PROTO_DPORT]);
++
++	return err;
++}
++
++static int ftnetlink_parse_tuple(const struct nlattr * const cda[],
++				 struct flow_offload_tuple *tuple,
++				 int attrtype, int l3proto)
++{
++	struct nlattr *tb[FTA_TUPLE_MAX+1];
++	int err;
++
++	memset(tuple, 0, sizeof(*tuple));
++
++	err = nla_parse_nested_deprecated(tb, FTA_TUPLE_MAX, cda[attrtype], tuple_nla_policy, NULL);
++	if (err < 0)
++		return err;
++
++	if (!tb[FTA_TUPLE_IP])
++		return -EINVAL;
++
++	/* parse IP */
++	tuple->l3proto = l3proto;
++	err = ftnetlink_parse_tuple_ip(tb[FTA_TUPLE_IP], tuple);
++	if (err < 0)
++		return err;
++
++	/* parse proto */
++	if (!tb[FTA_TUPLE_PROTO])
++		return -EINVAL;
++	err = ftnetlink_parse_tuple_proto(tb[FTA_TUPLE_PROTO], tuple);
++
++	if (err >= 0)
++		printk("tuple info:sip=%pI4,dip=%pI4 proto=%d "
++		       "sport=%d dport=%d\n",
++		       &tuple->src_v4, &tuple->dst_v4, tuple->l4proto,
++		       ntohs(tuple->src_port), ntohs(tuple->dst_port));
++
++	return err;
++}
++
++static int ftnetlink_del_nf_flow(struct net *net, struct sock *ftnl, struct sk_buff *skb,
++				 const struct nlmsghdr *nlh,
++				 const struct nlattr * const cda[],
++				 struct netlink_ext_ack *extack)
++{
++	struct net_device *dev = skb->dev;
++	struct flow_offload_tuple tuple;
++	int err = -1;
++	struct nfgenmsg *nfmsg = nlmsg_data(nlh);
++	u_int8_t u3 = nfmsg->nfgen_family;
++
++	/* parse tuple */
++	if(!cda[FTA_TUPLE])
++		return -EINVAL;
++
++	err = ftnetlink_parse_tuple(cda, &tuple, FTA_TUPLE, u3);
++	if (err < 0)
++		return err;
++
++	/* teardown the flow */
++	flow_offload_teardown_by_tuple(&tuple);
++
++	return 0;
++}
++
++static int ftnetlink_add_nf_flow(struct net *net, struct sock *ftnl, struct sk_buff *skb,
++				 const struct nlmsghdr *nlh,
++				 const struct nlattr * const cda[],
++				 struct netlink_ext_ack *extack)
++{
++	return 0;
++}
++
++static int ftnetlink_flush_table(struct net *net, struct sock *ftnl, struct sk_buff *skb,
++				 const struct nlmsghdr *nlh,
++				 const struct nlattr * const cda[],
++				 struct netlink_ext_ack *extack)
++{
++	struct net_device *dev = skb->dev;
++
++	nf_flow_table_cleanup(dev);
++
++	return 0;
++}
++
++static const struct nla_policy ft_nla_policy[FTA_MAX + 1] = {
++	[FTA_TUPLE] = { .type = NLA_NESTED },
++};
++
++static const struct nfnl_callback flow_table_cb[FT_MSG_MAX] = {
++	[FT_MSG_DEL] = {
++		.call = ftnetlink_del_nf_flow,
++		.attr_count = FTA_MAX,
++		.policy = ft_nla_policy
++	},
++	[FT_MSG_ADD] = {
++		.call = ftnetlink_add_nf_flow,
++		.attr_count = FTA_MAX,
++		.policy = ft_nla_policy
++	},
++	[FT_MSG_FLUSH] = {
++		.call = ftnetlink_flush_table,
++		.attr_count = FTA_MAX,
++		.policy = ft_nla_policy
++	},
++};
++
++static const struct nfnetlink_subsystem ftnl_subsys = {
++	.name = "flowtable",
++	.subsys_id = NFNL_SUBSYS_FLOWTABLE,
++	.cb_count = FT_MSG_MAX,
++	.cb = flow_table_cb,
++};
++
++static int __init ftnetlink_init(void)
++{
++	int ret;
++
++	ret = nfnetlink_subsys_register(&ftnl_subsys);
++
++	return ret;
++}
++
++static void ftnetlink_exit(void)
++{
++	nfnetlink_subsys_unregister(&ftnl_subsys);
++}
++
++MODULE_LICENSE("GPL");
++module_init(ftnetlink_init);
++module_exit(ftnetlink_exit);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7988.cfg b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7988.cfg
index 7ff8795..2675268 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7988.cfg
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7988.cfg
@@ -3,11 +3,11 @@
 # CONFIG_AIROHA_EN8801SC_PHY is not set
 # CONFIG_AIROHA_EN8811H_PHY is not set
 CONFIG_AQUANTIA_PHY=y
-# CONFIG_AQUANTIA_PHY_MDI_SWAP is not set
 CONFIG_AQUANTIA_PHY_FW_DOWNLOAD=y
 CONFIG_AQUANTIA_PHY_FW_DOWNLOAD_GANG=y
 # CONFIG_AQUANTIA_PHY_FW_DOWNLOAD_SINGLE is not set
 CONFIG_AQUANTIA_PHY_FW_FILE="Rhe-05.06-Candidate7-AQR_Mediatek_23B_StartOff_ID45623_VER36657.cld"
+# CONFIG_AQUANTIA_PHY_MDI_SWAP is not set
 CONFIG_ARCH_CLOCKSOURCE_DATA=y
 CONFIG_ARCH_DMA_ADDR_T_64BIT=y
 CONFIG_ARCH_KEEP_MEMBLOCK=y
@@ -56,6 +56,7 @@
 CONFIG_ARM_MEDIATEK_CPUFREQ=y
 CONFIG_ARM_PMU=y
 CONFIG_ARM_PSCI_FW=y
+# CONFIG_ASN1 is not set
 CONFIG_ATA=y
 CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y
 CONFIG_BLK_DEV_DM=y
@@ -118,7 +119,6 @@
 # CONFIG_CPUFREQ_DT is not set
 CONFIG_CPU_FREQ=y
 CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
-# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set
 CONFIG_CPU_FREQ_GOV_ATTR_SET=y
 CONFIG_CPU_FREQ_GOV_COMMON=y
 CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
@@ -179,6 +179,7 @@
 CONFIG_DM_VERITY=y
 # CONFIG_DM_VERITY_FEC is not set
 # CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG is not set
+CONFIG_DPS310=y
 CONFIG_DRM_RCAR_WRITEBACK=y
 CONFIG_DTC=y
 CONFIG_DYNAMIC_DEBUG=y
@@ -203,6 +204,7 @@
 CONFIG_GENERIC_GETTIMEOFDAY=y
 CONFIG_GENERIC_IDLE_POLL_SETUP=y
 CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
+CONFIG_GENERIC_IRQ_MIGRATION=y
 CONFIG_GENERIC_IRQ_MULTI_HANDLER=y
 CONFIG_GENERIC_IRQ_SHOW=y
 CONFIG_GENERIC_IRQ_SHOW_LEVEL=y
@@ -238,6 +240,7 @@
 CONFIG_HZ_250=y
 CONFIG_I2C=y
 CONFIG_I2C_BOARDINFO=y
+CONFIG_I2C_CHARDEV=y
 CONFIG_I2C_MT65XX=y
 CONFIG_ICPLUS_PHY=y
 CONFIG_IIO=y
@@ -289,7 +292,6 @@
 CONFIG_MTD_NAND_MTK=y
 CONFIG_MTD_RAW_NAND=y
 CONFIG_MTD_SPI_NAND=y
-# CONFIG_MTD_SPI_NAND_W25N01KV is not set
 CONFIG_MTD_SPI_NOR=y
 CONFIG_MTD_SPLIT_FIRMWARE=y
 CONFIG_MTD_SPLIT_FIT_FW=y
@@ -305,9 +307,9 @@
 CONFIG_MTK_INFRACFG=y
 CONFIG_MTK_PMIC_WRAP=y
 CONFIG_MTK_SCPSYS=y
+CONFIG_MTK_SOC_THERMAL_LVTS=y
 CONFIG_MTK_SPI_NAND=y
 # CONFIG_MTK_THERMAL is not set
-CONFIG_MTK_SOC_THERMAL_LVTS=y
 CONFIG_MTK_TIMER=y
 # CONFIG_MTK_UART_APDMA is not set
 CONFIG_MUTEX_SPIN_ON_OWNER=y
@@ -360,12 +362,8 @@
 CONFIG_PHYLIB=y
 CONFIG_PHYLINK=y
 CONFIG_PHYS_ADDR_T_64BIT=y
-# CONFIG_PHY_MTK_A60810 is not set
-# CONFIG_PHY_MTK_A60931 is not set
-# CONFIG_PHY_MTK_A60XXX is not set
 CONFIG_PHY_MTK_TPHY=y
 # CONFIG_PHY_MTK_UFS is not set
-# CONFIG_PHY_MTK_USB3_I2C_FPGA is not set
 CONFIG_PHY_MTK_XSPHY=y
 CONFIG_PINCTRL=y
 # CONFIG_PINCTRL_MT2712 is not set
@@ -506,5 +504,3 @@
 CONFIG_ZLIB_DEFLATE=y
 CONFIG_ZLIB_INFLATE=y
 CONFIG_ZONE_DMA32=y
-# CONFIG_BPF_KPROBE_OVERRIDE is not set
-# CONFIG_HIST_TRIGGERS is not set