* Re: [PATCH v5 08/10] ACPI: APEI: share GHES CPER helpers
From: Ahmed Tiba @ 2026-06-11 12:42 UTC (permalink / raw)
To: Jonathan Cameron
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529173229.18843384@jic23-huawei>
On 29/05/2026 17:32, Jonathan Cameron wrote:
> On Fri, 29 May 2026 10:50:48 +0100
> Ahmed Tiba <ahmed.tiba@arm.com> wrote:
>
>> Wire GHES up to the helper routines in ghes_cper.c and remove the local
>> copies from ghes.c. This keeps the control flow identical while letting
>> the helpers be shared with other firmware-first providers.
>>
>> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> Mostly looks fine. The one bit that rather makes this exercise of breaking
> out generic code look dodgy is the ifdefs in the generic file.
>
As below.
>
>> ---
>> drivers/acpi/apei/ghes.c | 416 +--------------------------------------
>> drivers/acpi/apei/ghes_cper.c | 438 +++++++++++++++++++++++++++++++++++++++++-
>> include/acpi/ghes_cper.h | 20 ++
>> 3 files changed, 459 insertions(+), 415 deletions(-)
>>
>> diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
>> index 85be2ebf4d3e..f85b97c4db4c 100644
>> --- a/drivers/acpi/apei/ghes.c
>> +++ b/drivers/acpi/apei/ghes.c
>
>>
>> static void __ghes_panic(struct ghes *ghes,
>> diff --git a/drivers/acpi/apei/ghes_cper.c b/drivers/acpi/apei/ghes_cper.c
>> index d7a666a163c3..0ff9d06eb78f 100644
>> --- a/drivers/acpi/apei/ghes_cper.c
>> +++ b/drivers/acpi/apei/ghes_cper.c
>> @@ -13,22 +13,32 @@
>
>>
>> #include "apei-internal.h"
>>
>> +ATOMIC_NOTIFIER_HEAD(ghes_report_chain);
>> +
>> +#ifndef CONFIG_ACPI_APEI
>> +void __weak arch_apei_report_mem_error(int sev, struct cper_sec_mem_err *mem_err) { }
>> +#endif
> This is non obvious enough that the reasoning for a new weak function should be mentioned in
> the patch description. Why not stub it in include/acpi/apei.h?
>
Agreed. I should have explained that in the changelog.
The weak arch_apei_report_mem_error() fallback was only meant to keep
the shared helper buildable when GHES_CPER_HELPERS is enabled without
ACPI_APEI, while preserving the current GHES behaviour when ACPI_APEI
is enabled.
I kept it local because this fallback is only needed by this helper
split and I did not want to widen the APEI header API for that.
>> +
>> static struct ghes_estatus_cache __rcu *ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE];
>> static atomic_t ghes_estatus_cache_alloced;
>
>> +void __ghes_print_estatus(const char *pfx,
>> + const struct acpi_hest_generic *generic,
>> + const struct acpi_hest_generic_status *estatus)
>> +{
>> + static atomic_t seqno;
>> + unsigned int curr_seqno;
>> + char pfx_seq[64];
>> +
>> + if (!pfx) {
>> + if (ghes_severity(estatus->error_severity) <=
>> + GHES_SEV_CORRECTED)
>> + pfx = KERN_WARNING;
>> + else
>> + pfx = KERN_ERR;
>> + }
>> + curr_seqno = atomic_inc_return(&seqno);
>> + snprintf(pfx_seq, sizeof(pfx_seq), "%s{%u}" HW_ERR, pfx, curr_seqno);
>> + printk("%sHardware error from APEI Generic Hardware Error Source: %d\n",
>> + pfx_seq, generic->header.source_id);
>> + cper_estatus_print(pfx_seq, estatus);
>> +}
>> +
>> +int ghes_print_estatus(const char *pfx,
>> + const struct acpi_hest_generic *generic,
>> + const struct acpi_hest_generic_status *estatus)
>> +{
>> + /* Not more than 2 messages every 5 seconds */
>> + static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5 * HZ, 2);
>> + static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5 * HZ, 2);
>> + struct ratelimit_state *ratelimit;
>> +
>> + if (ghes_severity(estatus->error_severity) <= GHES_SEV_CORRECTED)
>> + ratelimit = &ratelimit_corrected;
>> + else
>> + ratelimit = &ratelimit_uncorrected;
>> + if (__ratelimit(ratelimit)) {
>> + __ghes_print_estatus(pfx, generic, estatus);
>> + return 1;
>> + }
>> + return 0;
>> +}
>> +
>> +#ifdef CONFIG_ACPI_APEI
>
> So after the effort to break the the generic stuff we end up with non generic
> bits in the broken out file? Is there no way to avoid this?
>
The intent here was not to create a new generic estatus core
but to keep the existing GHES control flow and lift the CPER helper flow
for reuse by the DT provider.
Splitting the remaining ACPI GHES specific read/clear path back out into
ghes.c would break that flow across files again. The CONFIG_ACPI_APEI
guard keeps that ACPI specific piece local while the DT side reuses the
same CPER parsing and status-dispatch path.
Best regards,
Ahmed
^ permalink raw reply
* Re: [PATCH v7 8/8] perf test: Add Arm CoreSight callchain test
From: Leo Yan @ 2026-06-11 12:42 UTC (permalink / raw)
To: James Clark
Cc: linux-arm-kernel, coresight, linux-perf-users,
Arnaldo Carvalho de Melo, John Garry, Will Deacon, Mike Leach,
Suzuki K Poulose, Namhyung Kim, Mark Rutland, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, Al Grant, Paschalis Mpeis,
Amir Ayupov
In-Reply-To: <c6f6766b-ef3f-4260-9569-620d351027ff@linaro.org>
On Thu, Jun 11, 2026 at 10:11:37AM +0100, James Clark wrote:
>
>
> On 11/06/2026 8:57 am, Leo Yan wrote:
> > Add a CoreSight shell test for synthesized callchains.
> >
> > The test uses the new callchain workload to generate trace and decodes
> > it with synthesis callchain. It then verifies that the instruction
> > samples show the expected callchain push and pop.
> >
> > Use control FIFOs so tracing starts only around the workload, which
> > keeps the trace data small. The test is limited to arm64 systems with
> > the cs_etm event available.
> >
> > After:
> >
> > perf test 136 -vvv
> > 136: CoreSight synthesized callchain:
> > --- start ---
> > test child forked, pid 3539
> > ---- end(0) ----
> > 136: CoreSight synthesized callchain : Ok
> >
> > Assisted-by: Codex:GPT-5.5
> > Signed-off-by: Leo Yan <leo.yan@arm.com>
> > ---
> > tools/perf/Documentation/perf-test.txt | 6 +-
> > tools/perf/tests/builtin-test.c | 1 +
> > tools/perf/tests/shell/coresight/callchain.sh | 168 ++++++++++++++++++++++++++
> > tools/perf/tests/tests.h | 1 +
> > tools/perf/tests/workloads/Build | 2 +
> > tools/perf/tests/workloads/callchain.c | 24 ++++
>
> Maybe "syscall" is a better name? There's not any difference between this
> one and others like the deterministic one I added with regards to the
> callchain.
The callchain workload has function calls in userspace and system call
for the switch between userspace and kernel.
Connecting to another comment for not static attribute to avoid LTO's
function name tweaking, I will rename functions with prefix
"callchain_", this can dismiss concern for naming conflict.
For above two reasons, I will keep the name.
Thanks,
Leo
^ permalink raw reply
* Re: [PATCH] pinctrl: meson: amlogic-a4: use nolock get range
From: Linus Walleij @ 2026-06-11 12:40 UTC (permalink / raw)
To: xianwei.zhao
Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
linux-amlogic, linux-gpio, linux-arm-kernel, linux-kernel
In-Reply-To: <20260611-pinctrl-nolock-v1-1-aca022d4d60f@amlogic.com>
On Thu, Jun 11, 2026 at 9:10 AM Xianwei Zhao via B4 Relay
<devnull+xianwei.zhao.amlogic.com@kernel.org> wrote:
> From: Xianwei Zhao <xianwei.zhao@amlogic.com>
>
> Use pinctrl_find_gpio_range_from_pin_nolock() instead of
> pinctrl_find_gpio_range_from_pin() when configuring a pin or
> setting a GPIO value.
>
> This avoids taking the lock and allows the code to be safely
> called from interrupt context.
>
> Signed-off-by: Xianwei Zhao <xianwei.zhao@amlogic.com>
Patch applied!
Yours,
Linus Walleij
^ permalink raw reply
* Re: [PATCH] ARM: remove references to removed CONFIG_CPU_ARM92x_CPU_IDLE options
From: Linus Walleij @ 2026-06-11 12:38 UTC (permalink / raw)
To: Ethan Nelson-Moore
Cc: linux-arm-kernel, Nathan Chancellor, Kees Cook, Russell King
In-Reply-To: <20260611004700.395759-1-enelsonmoore@gmail.com>
On Thu, Jun 11, 2026 at 2:47 AM Ethan Nelson-Moore
<enelsonmoore@gmail.com> wrote:
> Several assembly files in arch/arm/mm contain comments referring to
> CONFIG_CPU_ARM92x_CPU_IDLE options, which have not existed in the kernel
> since 2.4.21. Remove them.
>
> Discovered while searching for CONFIG_* symbols referenced in code but
> not defined in any Kconfig file.
>
> Signed-off-by: Ethan Nelson-Moore <enelsonmoore@gmail.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Please put this patch into Russell's patch tracker.
Yours,
Linus Walleij
^ permalink raw reply
* [PATCH net-next 2/3] net: ti: icssm-prueth: Add priority based RX IRQ handlers
From: Parvathi Pudi @ 2026-06-11 12:33 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
parvathi, rogerq, pmohan, afd, basharath, arnd
Cc: linux-kernel, netdev, linux-arm-kernel, pratheesh, j-rameshbabu,
vigneshr, praneeth, srk, rogerq, m-malladi, krishna, mohan
In-Reply-To: <20260611123636.376577-1-parvathi@couthit.com>
From: Roger Quadros <rogerq@ti.com>
This patch adds support for priority based interrupt handling for the STP/
RSTP Switch, HSR and PRP protocols along with extra logic to address first
come first served to avoid port dominance.
In RSTP switch, HSR, and PRP modes the host port can receive frames from
any one of PRU ports. Servicing RX interrupts in arrival order does not
guarantee the frames are delivered to the stack in wire-arrival order due
to port dominance.
In order to achieve that, each PRU records an IEP (Industrial Ethernet
Peripheral) arrival HW timestamp into the receive buffer and pass this
infor to driver. The driver will read the RX HW timestamp from frame and
process the frame which has arrived first among the two ports, giving the
stack wire-arrival ordering.
Dual-EMAC mode continues to use per-port interrupts.
Signed-off-by: Roger Quadros <rogerq@ti.com>
Signed-off-by: Andrew F. Davis <afd@ti.com>
Signed-off-by: Basharath Hussain Khaja <basharath@couthit.com>
Signed-off-by: Parvathi Pudi <parvathi@couthit.com>
---
drivers/net/ethernet/ti/Makefile | 2 +-
drivers/net/ethernet/ti/icssm/icssm_prueth.c | 146 ++++++++-
drivers/net/ethernet/ti/icssm/icssm_prueth.h | 30 ++
.../ethernet/ti/icssm/icssm_prueth_common.c | 285 ++++++++++++++++++
.../net/ethernet/ti/icssm/icssm_prueth_lre.c | 13 +
5 files changed, 462 insertions(+), 14 deletions(-)
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile
index e4a10d60e1a6..b6651fe73afd 100644
--- a/drivers/net/ethernet/ti/Makefile
+++ b/drivers/net/ethernet/ti/Makefile
@@ -4,7 +4,7 @@
#
obj-$(CONFIG_TI_PRUETH) += icssm-prueth.o
-icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o icssm/icssm_prueth_lre.o
+icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o icssm/icssm_prueth_lre.o icssm/icssm_prueth_common.o
ti-cpsw-common-y += cpsw-common.o davinci_cpdma.o
ti-cpsw-priv-y += cpsw_priv.o cpsw_ethtool.o
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
index 6ebc52ce7123..58a6935dd809 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.c
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
@@ -41,7 +41,21 @@
#define TX_CLK_DELAY_100M 0x6
#define HR_TIMER_TX_DELAY_US 100
-static const struct prueth_fw_offsets fw_offsets_v2_1;
+/* ICSSM (v2.1) - supports 64-bit IEP counter.
+ * Firmware stores packet timestamps using lower 32 bits
+ * which wraps at 0xffffffff.
+ */
+static const struct prueth_fw_offsets fw_offsets_v2_1 = {
+ .iep_wrap = 0xffffffff,
+};
+
+/* ICSSM (v1.0) - supports 32-bit IEP counter, which resets the
+ * counter every one second (nanosecond resolution).
+ */
+static const struct prueth_fw_offsets fw_offsets_v1_0 = {
+ .iep_wrap = NSEC_PER_SEC,
+};
+
static void icssm_prueth_set_fw_offsets(struct prueth *prueth)
{
/* Set Multicast filter control and table offsets */
@@ -1069,11 +1083,25 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
goto iep_exit;
}
- ret = icssm_emac_request_irqs(emac);
- if (ret)
- goto rproc_shutdown;
+ if (PRUETH_IS_EMAC(prueth)) {
+ napi_enable(&emac->napi);
+ } else {
+ if (!prueth->emac_configured &&
+ (PRUETH_IS_SWITCH(prueth) || prueth_is_lre(prueth))) {
+ napi_enable(&prueth->napi_hpq);
+ napi_enable(&prueth->napi_lpq);
+ }
+ }
- napi_enable(&emac->napi);
+ /* In switch and LRE modes the shared HPQ/LPQ IRQs are used,
+ * register them here and reuse for both modes.
+ */
+ if (PRUETH_IS_EMAC(prueth))
+ ret = icssm_emac_request_irqs(emac);
+ else
+ ret = icssm_prueth_common_request_irqs(emac);
+ if (ret)
+ goto disable_napi;
/* start PHY */
phy_start(emac->phydev);
@@ -1090,7 +1118,17 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
return 0;
-rproc_shutdown:
+disable_napi:
+ if (PRUETH_IS_EMAC(prueth)) {
+ napi_disable(&emac->napi);
+ } else {
+ if (!prueth->emac_configured &&
+ (PRUETH_IS_SWITCH(prueth) || prueth_is_lre(prueth))) {
+ napi_disable(&prueth->napi_lpq);
+ napi_disable(&prueth->napi_hpq);
+ }
+ }
+
if (!PRUETH_IS_EMAC(prueth))
icssm_prueth_sw_shutdown_prus(emac, ndev);
else
@@ -1122,12 +1160,26 @@ static int icssm_emac_ndo_stop(struct net_device *ndev)
/* disable the mac port */
icssm_prueth_port_enable(emac, false);
+ netif_stop_queue(ndev);
+
/* stop PHY */
phy_stop(emac->phydev);
- napi_disable(&emac->napi);
hrtimer_cancel(&emac->tx_hrtimer);
+ if (PRUETH_IS_EMAC(prueth)) {
+ napi_disable(&emac->napi);
+ free_irq(emac->rx_irq, ndev);
+ } else {
+ if (!prueth->emac_configured &&
+ (PRUETH_IS_SWITCH(prueth) || prueth_is_lre(prueth))) {
+ napi_disable(&prueth->napi_lpq);
+ napi_disable(&prueth->napi_hpq);
+ }
+ /* Free IRQs on last port before halting PRU */
+ icssm_prueth_common_free_irqs(emac);
+ }
+
/* stop the PRU */
if (!PRUETH_IS_EMAC(prueth))
icssm_prueth_sw_shutdown_prus(emac, ndev);
@@ -1137,9 +1189,6 @@ static int icssm_emac_ndo_stop(struct net_device *ndev)
if (prueth_is_lre(prueth) && !prueth->emac_configured)
icssm_prueth_lre_cleanup(prueth);
- /* free rx interrupts */
- free_irq(emac->rx_irq, ndev);
-
/* free memory related to sw */
icssm_prueth_free_memory(emac->prueth);
@@ -1675,11 +1724,28 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,
ndev->dev.of_node = eth_node;
ndev->netdev_ops = &emac_netdev_ops;
- netif_napi_add(ndev, &emac->napi, icssm_emac_napi_poll);
+ if (PRUETH_IS_EMAC(prueth))
+ netif_napi_add(ndev, &emac->napi, icssm_emac_napi_poll);
+
+ if ((prueth->support_lre || fw_data->support_switch) &&
+ emac->port_id == PRUETH_PORT_MII0) {
+ netif_napi_add(ndev, &prueth->napi_hpq,
+ icssm_prueth_lre_napi_poll_hpq);
+ netif_napi_add(ndev, &prueth->napi_lpq,
+ icssm_prueth_lre_napi_poll_lpq);
+ }
hrtimer_setup(&emac->tx_hrtimer, &icssm_emac_tx_timer_callback,
CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED);
+ if ((prueth->support_lre || fw_data->support_switch) &&
+ emac->port_id == PRUETH_PORT_MII0) {
+ prueth->hp->ndev = ndev;
+ prueth->hp->priority = 0;
+ prueth->lp->ndev = ndev;
+ prueth->lp->priority = 1;
+ }
+
return 0;
free:
emac->ndev = NULL;
@@ -1691,6 +1757,7 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,
static void icssm_prueth_netdev_exit(struct prueth *prueth,
struct device_node *eth_node)
{
+ const struct prueth_private_data *fw_data = prueth->fw_data;
struct prueth_emac *emac;
enum prueth_mac mac;
@@ -1704,7 +1771,16 @@ static void icssm_prueth_netdev_exit(struct prueth *prueth,
phy_disconnect(emac->phydev);
- netif_napi_del(&emac->napi);
+ if (PRUETH_IS_EMAC(prueth)) {
+ netif_napi_del(&emac->napi);
+ } else {
+ if (emac->port_id == PRUETH_PORT_MII0 &&
+ (fw_data->support_switch || prueth->support_lre)) {
+ netif_napi_del(&prueth->napi_hpq);
+ netif_napi_del(&prueth->napi_lpq);
+ }
+ }
+
prueth->emac[mac] = NULL;
}
@@ -2002,7 +2078,13 @@ static int icssm_prueth_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, prueth);
prueth->dev = dev;
prueth->fw_data = device_get_match_data(dev);
- prueth->fw_offsets = fw_offsets_v2_1;
+
+ if (prueth->fw_data->fw_rev == FW_REV_V1_0)
+ prueth->fw_offsets = fw_offsets_v1_0;
+ else if (prueth->fw_data->fw_rev == FW_REV_V2_1)
+ prueth->fw_offsets = fw_offsets_v2_1;
+ else
+ return -EINVAL;
eth_ports_node = of_get_child_by_name(np, "ethernet-ports");
if (!eth_ports_node)
@@ -2157,6 +2239,41 @@ static int icssm_prueth_probe(struct platform_device *pdev)
if (has_lre && (!eth0_node || !eth1_node))
has_lre = false;
+ /* Switch and LRE share HPQ/LPQ IRQs across both ports,
+ * allocate the shared priority structures once here
+ */
+ if (prueth->fw_data->support_switch || has_lre) {
+ prueth->hp = devm_kzalloc(dev,
+ sizeof(struct prueth_ndev_priority),
+ GFP_KERNEL);
+ if (!prueth->hp) {
+ ret = -ENOMEM;
+ goto free_pool;
+ }
+ prueth->lp = devm_kzalloc(dev,
+ sizeof(struct prueth_ndev_priority),
+ GFP_KERNEL);
+ if (!prueth->lp) {
+ ret = -ENOMEM;
+ goto free_pool;
+ }
+
+ prueth->rx_lpq_irq = of_irq_get_byname(np, "rx_lp");
+ if (prueth->rx_lpq_irq < 0) {
+ ret = prueth->rx_lpq_irq;
+ if (ret != -EPROBE_DEFER)
+ dev_err(prueth->dev, "could not get rx_lp irq\n");
+ goto free_pool;
+ }
+ prueth->rx_hpq_irq = of_irq_get_byname(np, "rx_hp");
+ if (prueth->rx_hpq_irq < 0) {
+ ret = prueth->rx_hpq_irq;
+ if (ret != -EPROBE_DEFER)
+ dev_err(prueth->dev, "could not get rx_hp irq\n");
+ goto free_pool;
+ }
+ }
+
prueth->support_lre = has_lre;
/* setup netdev interfaces */
if (eth0_node) {
@@ -2396,6 +2513,7 @@ static struct prueth_private_data am335x_prueth_pdata = {
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am335x-pru1-prusw-fw.elf",
},
+ .fw_rev = FW_REV_V1_0,
.support_lre = true,
.support_switch = true,
};
@@ -2423,6 +2541,7 @@ static struct prueth_private_data am437x_prueth_pdata = {
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am437x-pru1-prusw-fw.elf",
},
+ .fw_rev = FW_REV_V1_0,
.support_lre = true,
.support_switch = true,
};
@@ -2451,6 +2570,7 @@ static struct prueth_private_data am57xx_prueth_pdata = {
"ti-pruss/am57xx-pru1-prusw-fw.elf",
},
+ .fw_rev = FW_REV_V2_1,
.support_lre = true,
.support_switch = true,
};
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.h b/drivers/net/ethernet/ti/icssm/icssm_prueth.h
index 098d81599415..60dc451f79e5 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.h
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.h
@@ -178,11 +178,22 @@ enum prueth_mem {
PRUETH_MEM_MAX,
};
+/* PRU firmware revision */
+enum fw_revision {
+ FW_REV_INVALID = 0,
+ FW_REV_V1_0,
+ FW_REV_V2_1
+};
+
/* Firmware offsets/size information */
struct prueth_fw_offsets {
u32 mc_ctrl_offset;
u32 mc_filter_mask;
u32 mc_filter_tbl;
+ /* IEP wrap is used in the rx packet ordering logic and
+ * is different for ICSSM v1.0 vs 2.1
+ */
+ u32 iep_wrap;
};
enum pruss_device {
@@ -196,12 +207,14 @@ enum pruss_device {
* struct prueth_private_data - PRU Ethernet private data
* @driver_data: PRU Ethernet device name
* @fw_pru: firmware names to be used for PRUSS ethernet usecases
+ * @fw_rev: Firmware revision identifier
* @support_switch: boolean to indicate if switch is enabled
* @support_lre: boolean to indicate if LRE is enabled
*/
struct prueth_private_data {
enum pruss_device driver_data;
const struct prueth_firmware fw_pru[PRUSS_NUM_PRUS];
+ enum fw_revision fw_rev;
bool support_switch;
bool support_lre;
};
@@ -254,6 +267,11 @@ struct prueth_emac {
int offload_fwd_mark;
};
+struct prueth_ndev_priority {
+ struct net_device *ndev;
+ int priority;
+};
+
struct prueth {
struct device *dev;
struct pruss *pruss;
@@ -269,6 +287,12 @@ struct prueth {
struct device_node *eth_node[PRUETH_NUM_MACS];
struct prueth_emac *emac[PRUETH_NUM_MACS];
struct net_device *registered_netdevs[PRUETH_NUM_MACS];
+ struct prueth_ndev_priority *hp, *lp;
+ /* NAPI for lp and hp queue scans */
+ struct napi_struct napi_lpq;
+ struct napi_struct napi_hpq;
+ int rx_lpq_irq;
+ int rx_hpq_irq;
bool support_lre;
unsigned int tbl_check_mask;
@@ -300,6 +324,12 @@ void icssm_emac_mc_filter_bin_allow(struct prueth_emac *emac, u8 hash);
void icssm_emac_mc_filter_bin_disallow(struct prueth_emac *emac, u8 hash);
u8 icssm_emac_get_mc_hash(u8 *mac, u8 *mask);
+int icssm_prueth_lre_napi_poll_lpq(struct napi_struct *napi, int budget);
+int icssm_prueth_lre_napi_poll_hpq(struct napi_struct *napi, int budget);
+
+int icssm_prueth_common_request_irqs(struct prueth_emac *emac);
+void icssm_prueth_common_free_irqs(struct prueth_emac *emac);
+
static inline bool prueth_is_lre(struct prueth *prueth)
{
return PRUETH_IS_HSR(prueth) || PRUETH_IS_PRP(prueth);
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
new file mode 100644
index 000000000000..4820def5f9e1
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Texas Instruments ICSSM Ethernet Driver
+ *
+ * Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/if_vlan.h>
+
+#include "icssm_prueth.h"
+#include "icssm_prueth_switch.h"
+
+static int icssm_prueth_common_emac_rx_packets(struct prueth_emac *emac,
+ int quota, u8 qid1, u8 qid2)
+{
+ u16 bd_rd_ptr, bd_wr_ptr, update_rd_ptr, bd_rd_ptr_o, bd_wr_ptr_o;
+ const struct prueth_queue_info *rxqueue, *rxqueue_o, *rxqueue_p;
+ struct net_device_stats *ndevstats, *ndevstats_o, *ndevstats_p;
+ struct prueth_queue_desc __iomem *queue_desc, *queue_desc_o;
+ struct prueth_packet_info pkt_info, pkt_info_o, *pkt_info_p;
+ u32 rd_buf_desc, rd_buf_desc_o, pkt_ts, pkt_ts_o, iep_wrap;
+ int ret, used = 0, port, port0_q_empty, port1_q_empty;
+ struct prueth_emac *emac_p, *other_emac;
+ void __iomem *shared_ram, *ocmc_ram;
+ u8 overflow_cnt, overflow_cnt_o;
+ u16 *bd_rd_ptr_p, *bd_wr_ptr_p;
+ unsigned int emac_max_pktlen;
+ struct prueth *prueth;
+
+ prueth = emac->prueth;
+ ocmc_ram = prueth->mem[PRUETH_MEM_OCMC].va;
+ shared_ram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+ other_emac = prueth->emac[(emac->port_id == PRUETH_PORT_MII0) ?
+ PRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1];
+ ndevstats = &emac->ndev->stats;
+ ndevstats_o = &other_emac->ndev->stats;
+ emac_max_pktlen = EMAC_MAX_FRM_SUPPORT;
+
+ iep_wrap = prueth->fw_offsets.iep_wrap;
+ /* search host queues for packets */
+ queue_desc = emac->rx_queue_descs + qid1;
+ queue_desc_o = other_emac->rx_queue_descs + qid2;
+
+ rxqueue = &sw_queue_infos[PRUETH_PORT_HOST][qid1];
+ rxqueue_o = &sw_queue_infos[PRUETH_PORT_HOST][qid2];
+
+ overflow_cnt = readb(&queue_desc->overflow_cnt);
+ overflow_cnt_o = readb(&queue_desc_o->overflow_cnt);
+
+ if (overflow_cnt > 0) {
+ emac->ndev->stats.rx_over_errors += overflow_cnt;
+ writeb(0, &queue_desc->overflow_cnt);
+ }
+ if (overflow_cnt_o > 0) {
+ other_emac->ndev->stats.rx_over_errors += overflow_cnt_o;
+ writeb(0, &queue_desc_o->overflow_cnt);
+ }
+
+ bd_rd_ptr = readw(&queue_desc->rd_ptr);
+ bd_wr_ptr = readw(&queue_desc->wr_ptr);
+
+ bd_rd_ptr_o = readw(&queue_desc_o->rd_ptr);
+ bd_wr_ptr_o = readw(&queue_desc_o->wr_ptr);
+
+ port0_q_empty = (bd_rd_ptr == bd_wr_ptr);
+ port1_q_empty = (bd_rd_ptr_o == bd_wr_ptr_o);
+
+ while (!port0_q_empty || !port1_q_empty) {
+ rd_buf_desc = readl(shared_ram + bd_rd_ptr);
+ rd_buf_desc_o = readl(shared_ram + bd_rd_ptr_o);
+
+ icssm_parse_packet_info(prueth, rd_buf_desc, &pkt_info);
+ icssm_parse_packet_info(prueth, rd_buf_desc_o, &pkt_info_o);
+
+ pkt_ts = readl(ocmc_ram + ICSS_LRE_TIMESTAMP_ARRAY_OFFSET +
+ bd_rd_ptr - SRAM_START_OFFSET);
+ pkt_ts_o = readl(ocmc_ram + ICSS_LRE_TIMESTAMP_ARRAY_OFFSET +
+ bd_rd_ptr_o - SRAM_START_OFFSET);
+
+ if (!port0_q_empty && !port1_q_empty) {
+ /* Both ports have a pending frame, pick the
+ * earlier one by comparing timestamps and
+ * account for wraparound.
+ */
+ if (pkt_ts > pkt_ts_o)
+ port = (pkt_ts - pkt_ts_o) > (iep_wrap / 2) ?
+ 0 : 1;
+ else
+ port = (pkt_ts_o - pkt_ts) > (iep_wrap / 2) ?
+ 1 : 0;
+
+ } else if (!port0_q_empty) {
+ /* Packet(s) in port0 queue only */
+ port = 0;
+ } else {
+ /* Packet(s) in port1 queue only */
+ port = 1;
+ }
+
+ /* Select correct data structures for queue/packet selected */
+ if (port == 0) {
+ pkt_info_p = &pkt_info;
+ bd_wr_ptr_p = &bd_wr_ptr;
+ bd_rd_ptr_p = &bd_rd_ptr;
+ emac_p = emac;
+ ndevstats_p = ndevstats;
+ rxqueue_p = rxqueue;
+ } else {
+ pkt_info_p = &pkt_info_o;
+ bd_wr_ptr_p = &bd_wr_ptr_o;
+ bd_rd_ptr_p = &bd_rd_ptr_o;
+ emac_p = other_emac;
+ ndevstats_p = ndevstats_o;
+ rxqueue_p = rxqueue_o;
+ }
+
+ if ((*pkt_info_p).length == 0) {
+ /* A zero-length frame would stall the ring, rd_ptr
+ * would never advance. Firmware should not produce
+ * these, skip to wr_ptr and drop all remaining
+ * frames in this queue.
+ */
+ update_rd_ptr = *bd_wr_ptr_p;
+ ndevstats_p->rx_length_errors++;
+ } else if ((*pkt_info_p).length > emac_max_pktlen) {
+ /* Oversized frame: firmware should have filtered
+ * these before they reach the host queue. Advance
+ * the read pointer to skip it.
+ */
+ update_rd_ptr = *bd_wr_ptr_p;
+ ndevstats_p->rx_length_errors++;
+ } else {
+ update_rd_ptr = *bd_rd_ptr_p;
+ ret = icssm_emac_rx_packet(emac_p, &update_rd_ptr,
+ pkt_info_p, rxqueue_p);
+ if (ret)
+ return used;
+
+ used++;
+ }
+
+ /* Zero the BD after consuming it, a misaligned rd_ptr
+ * would otherwise mistake stale data for a valid incoming
+ * frame.
+ */
+ if (port == 0) {
+ writel(0, shared_ram + bd_rd_ptr);
+ writew(update_rd_ptr, &queue_desc->rd_ptr);
+ bd_rd_ptr = update_rd_ptr;
+ } else {
+ writel(0, shared_ram + bd_rd_ptr_o);
+ writew(update_rd_ptr, &queue_desc_o->rd_ptr);
+ bd_rd_ptr_o = update_rd_ptr;
+ }
+
+ port0_q_empty = (bd_rd_ptr == bd_wr_ptr) ? 1 : 0;
+ port1_q_empty = (bd_rd_ptr_o == bd_wr_ptr_o) ? 1 : 0;
+
+ if (used >= quota)
+ return used;
+ }
+
+ return used;
+}
+
+int icssm_prueth_lre_napi_poll_lpq(struct napi_struct *napi, int budget)
+{
+ struct prueth_emac *emac;
+ struct net_device *ndev;
+ struct prueth *prueth;
+ int num_rx_packets;
+ u8 qid1, qid2;
+
+ prueth = container_of(napi, struct prueth, napi_lpq);
+ ndev = prueth->lp->ndev;
+ emac = netdev_priv(ndev);
+ qid1 = PRUETH_QUEUE2;
+ qid2 = PRUETH_QUEUE4;
+
+ num_rx_packets = icssm_prueth_common_emac_rx_packets(emac, budget,
+ qid1, qid2);
+ if (num_rx_packets < budget && napi_complete_done(napi, num_rx_packets))
+ enable_irq(prueth->rx_lpq_irq);
+
+ return num_rx_packets;
+}
+
+int icssm_prueth_lre_napi_poll_hpq(struct napi_struct *napi, int budget)
+{
+ struct prueth_emac *emac;
+ struct net_device *ndev;
+ struct prueth *prueth;
+ int num_rx_packets;
+ u8 qid1, qid2;
+
+ prueth = container_of(napi, struct prueth, napi_hpq);
+ ndev = prueth->hp->ndev;
+ emac = netdev_priv(ndev);
+ qid1 = PRUETH_QUEUE1;
+ qid2 = PRUETH_QUEUE3;
+
+ num_rx_packets = icssm_prueth_common_emac_rx_packets(emac, budget,
+ qid1, qid2);
+ if (num_rx_packets < budget && napi_complete_done(napi, num_rx_packets))
+ enable_irq(prueth->rx_hpq_irq);
+
+ return num_rx_packets;
+}
+
+static irqreturn_t icssm_prueth_common_emac_rx_hardirq(int irq, void *dev_id)
+{
+ struct prueth_ndev_priority *ndev_prio;
+ struct prueth_emac *emac;
+ struct net_device *ndev;
+ struct prueth *prueth;
+
+ ndev_prio = (struct prueth_ndev_priority *)dev_id;
+ ndev = ndev_prio->ndev;
+ emac = netdev_priv(ndev);
+ prueth = emac->prueth;
+
+ /* disable Rx system event */
+ if (ndev_prio->priority == 1) {
+ disable_irq_nosync(prueth->rx_lpq_irq);
+ napi_schedule(&prueth->napi_lpq);
+ } else {
+ disable_irq_nosync(prueth->rx_hpq_irq);
+ napi_schedule(&prueth->napi_hpq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+int icssm_prueth_common_request_irqs(struct prueth_emac *emac)
+{
+ struct prueth *prueth = emac->prueth;
+ int ret;
+
+ /* Request irq when first port is initialized */
+ if (prueth->emac_configured)
+ return 0;
+
+ ret = request_irq(prueth->rx_hpq_irq,
+ icssm_prueth_common_emac_rx_hardirq,
+ IRQF_TRIGGER_HIGH, "eth_hp_int", prueth->hp);
+ if (ret) {
+ netdev_err(emac->ndev, "unable to request RX HPQ IRQ\n");
+ return ret;
+ }
+
+ ret = request_irq(prueth->rx_lpq_irq,
+ icssm_prueth_common_emac_rx_hardirq,
+ IRQF_TRIGGER_HIGH, "eth_lp_int", prueth->lp);
+ if (ret) {
+ netdev_err(emac->ndev, "unable to request RX LPQ IRQ\n");
+ goto free_rx_hpq_irq;
+ }
+
+ return 0;
+
+free_rx_hpq_irq:
+ free_irq(prueth->rx_hpq_irq, prueth->hp);
+
+ return ret;
+}
+
+/**
+ * icssm_prueth_common_free_irqs - free irq
+ *
+ * @emac: EMAC data structure
+ *
+ */
+void icssm_prueth_common_free_irqs(struct prueth_emac *emac)
+{
+ struct prueth *prueth = emac->prueth;
+
+ /* HSR/PRP: free irqs when last port is down */
+ if (prueth->emac_configured)
+ return;
+
+ free_irq(prueth->rx_lpq_irq, prueth->lp);
+ free_irq(prueth->rx_hpq_irq, prueth->hp);
+}
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
index 930bc59df4c5..be6a503a5754 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
@@ -152,6 +152,14 @@ static void icssm_prueth_lre_protocol_init(struct prueth *prueth)
dram1 + ICSS_LRE_SUP_ADDR_LOW);
}
+static void icssm_prueth_lre_config_packet_timestamping(struct prueth *prueth)
+{
+ void __iomem *sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+
+ writeb(1, sram + ICSS_LRE_PRIORITY_INTRS_STATUS_OFFSET);
+ writeb(1, sram + ICSS_LRE_TIMESTAMP_PKTS_STATUS_OFFSET);
+}
+
static enum hrtimer_restart icssm_prueth_lre_timer(struct hrtimer *timer)
{
struct prueth *prueth;
@@ -202,6 +210,11 @@ void icssm_prueth_lre_config(struct prueth *prueth)
icssm_prueth_lre_init(prueth);
icssm_prueth_lre_dbg_init(prueth);
icssm_prueth_lre_protocol_init(prueth);
+ /* Enable per-packet timestamping so the driver can order
+ * received frames by arrival time across the two slave ports.
+ */
+ icssm_prueth_lre_config_packet_timestamping(prueth);
+
}
void icssm_prueth_lre_cleanup(struct prueth *prueth)
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 1/3] net: ti: icssm-prueth: Add HSR and PRP HW offload mode support for AM57xx, AM437x and AM335x
From: Parvathi Pudi @ 2026-06-11 12:33 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
parvathi, rogerq, pmohan, afd, basharath, arnd
Cc: linux-kernel, netdev, linux-arm-kernel, pratheesh, j-rameshbabu,
vigneshr, praneeth, srk, rogerq, m-malladi, krishna, mohan
In-Reply-To: <20260611123636.376577-1-parvathi@couthit.com>
From: Roger Quadros <rogerq@ti.com>
The PRU-ICSS subsystem on AM335x, AM437x and AM57xx SoCs supports dedicated
firmware implementing the IEC 62439-3 redundancy protocols: HSR and PRP.
Extend the ICSSM PRUETH driver to enable these operating modes in addition
to the existing dual-EMAC and RSTP switch configurations.
In both HSR and PRP modes, the two PRU Ethernet ports operate as LRE (Link
Redundancy Entity) slave ports, while the host port acts as the master.
In case of HW offload mode, Frame duplicate detection/discard for both HSR
and PRP and L2 forwarding in case of HSR are handled entirely by firmware
within the PRU cores.
For HSR, frames received on one PRU port are forwarded to the host and to
the peer PRU port (store-and-forward or cut-through), providing a redundant
ring path. For PRP, any one of the PRU port forwards received frames to the
host after firmware discards the duplicates.
The PRU-ICSS subsystem loads the Dual EMAC firmware by default. To enable
HSR or PRP functionality, the firmware must be changed accordingly. The
required reconfiguration steps are detailed below.
To switch from dual-EMAC to HSR (example: eth2 and eth3 as slave raw
ports):
$ ip link set eth2 down && ip link set eth3 down
$ ip link set eth2 address <mac-addr>
$ ip link set eth3 address <mac-addr>
$ ethtool -K eth2 hsr-tag-rm-offload on
$ ethtool -K eth2 hsr-fwd-offload on
$ ethtool -K eth2 hsr-dup-offload on
$ ethtool -K eth3 hsr-tag-rm-offload on
$ ethtool -K eth3 hsr-fwd-offload on
$ ethtool -K eth3 hsr-dup-offload on
$ ip link add name hsr0 type hsr slave1 eth2 slave2 eth3 supervison 45
version 1
$ ip link set eth2 up
$ ip link set eth3 up
To switch from dual-EMAC to PRP (example: eth2 and eth3 as slave raw
ports):
$ ip link set eth2 down && ip link set eth3 down
$ ip link set eth2 address <mac-addr>
$ ip link set eth3 address <mac-addr>
$ ethtool -K eth2 hsr-tag-rm-offload on
$ ethtool -K eth2 hsr-dup-offload on
$ ethtool -K eth3 hsr-tag-rm-offload on
$ ethtool -K eth3 hsr-dup-offload on
$ ip link add name prp0 type hsr slave1 eth2 slave2 eth3 supervision 45
proto 1
$ ip link set eth2 up
$ ip link set eth3 up
To revert back to dual-EMAC:
$ ip link delete hsr0 or prp0
$ ip link set eth2 down && ip link set eth3 down
$ ethtool -K eth2 hsr-tag-rm-offload off
$ ethtool -K eth2 hsr-fwd-offload off
$ ethtool -K eth2 hsr-dup-offload off
$ ethtool -K eth3 hsr-tag-rm-offload off
$ ethtool -K eth3 hsr-fwd-offload off
$ ethtool -K eth3 hsr-dup-offload off
Signed-off-by: Roger Quadros <rogerq@ti.com>
Signed-off-by: Andrew F. Davis <afd@ti.com>
Signed-off-by: Parvathi Pudi <parvathi@couthit.com>
---
drivers/net/ethernet/ti/Makefile | 2 +-
.../ethernet/ti/icssm/icssm_lre_firmware.h | 141 ++++++++++
drivers/net/ethernet/ti/icssm/icssm_prueth.c | 252 +++++++++++++++++-
drivers/net/ethernet/ti/icssm/icssm_prueth.h | 29 +-
.../net/ethernet/ti/icssm/icssm_prueth_lre.c | 210 +++++++++++++++
.../net/ethernet/ti/icssm/icssm_prueth_lre.h | 19 ++
6 files changed, 640 insertions(+), 13 deletions(-)
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h
diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile
index f4276c9a7762..e4a10d60e1a6 100644
--- a/drivers/net/ethernet/ti/Makefile
+++ b/drivers/net/ethernet/ti/Makefile
@@ -4,7 +4,7 @@
#
obj-$(CONFIG_TI_PRUETH) += icssm-prueth.o
-icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o
+icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o icssm/icssm_prueth_lre.o
ti-cpsw-common-y += cpsw-common.o davinci_cpdma.o
ti-cpsw-priv-y += cpsw_priv.o cpsw_ethtool.o
diff --git a/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h b/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h
new file mode 100644
index 000000000000..b5ab0ec87c5f
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2017-2020 Texas Instruments Incorporated - http://www.ti.com */
+#ifndef __ICSS_LRE_FIRMWARE_H
+#define __ICSS_LRE_FIRMWARE_H
+
+#define ICSS_LRE_HSR_MODE_OFFSET 0x1E76
+#define ICSS_LRE_MODEH 0x01
+
+/* PRU0 DMEM */
+#define ICSS_LRE_DBG_START 0x1E00
+
+#define ICSS_LRE_DUPLICATE_HOST_TABLE 0x0200
+
+/* PRU1 DMEM */
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_PRU0 0x0200
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_PRU1 0x0E00
+
+/* Size and setup (N and M) of duplicate host table */
+#define ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE 0x1C08
+/* Size and setup (N and M) of duplicate port table (HSR Only) */
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE 0x1C1C
+/* Time after which an entry is removed from the duplicate
+ * table (10 ms resolution)
+ */
+#define ICSS_LRE_DUPLI_FORGET_TIME 0x1C24
+/* Time interval to check the port duplicate table */
+#define ICSS_LRE_DUPLI_PORT_CHECK_RESO 0x1C2C
+/* Time interval to check the host duplicate table */
+#define ICSS_LRE_DUPLI_HOST_CHECK_RESO 0x1C30
+/* NodeTable | Host | Port */
+#define ICSS_LRE_HOST_TIMER_CHECK_FLAGS 0x1C38
+/* Arbitration flag for the host duplicate table */
+#define ICSS_LRE_HOST_DUPLICATE_ARBITRATION 0x1C3C
+/* Supervision address in LRE */
+#define ICSS_LRE_SUP_ADDR 0x1C4C
+#define ICSS_LRE_SUP_ADDR_LOW 0x1C50
+
+/* Time in TimeTicks (1/100s) */
+#define ICSS_LRE_DUPLICATE_FORGET_TIME_400_MS 40
+#define ICSS_LRE_NODE_FORGET_TIME_60000_MS 6000
+#define ICSS_LRE_MAX_FORGET_TIME 0xFFDF
+
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_DMEM_SIZE 0x0C00
+#define ICSS_LRE_DUPLICATE_HOST_TABLE_DMEM_SIZE 0x1800
+#define ICSS_LRE_STATS_DMEM_SIZE 0x0080
+#define ICSS_LRE_DEBUG_COUNTER_DMEM_SIZE 0x0050
+
+#define ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE_INIT 0x800004 /* N = 128, M = 4 */
+#define ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE_INIT 0x400004 /* N = 64, M = 4 */
+#define ICSS_LRE_MASTER_SLAVE_BUSY_BITS_CLEAR 0x0
+#define ICSS_LRE_TABLE_CHECK_RESOLUTION_10_MS 0xA
+#define ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_HIGH 0x4E1501
+#define ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_LOW 0x1
+
+/* SHARED RAM */
+
+/* 8 bytes of VLAN PCP to RX QUEUE MAPPING */
+#define ICSS_LRE_QUEUE_2_PCP_MAP_OFFSET 0x124
+#define ICSS_LRE_START 0x140
+
+/* Count of HSR/PRP tagged frames successfully transmitted on port A/B */
+#define ICSS_LRE_CNT_TX_A (ICSS_LRE_START + 4)
+#define ICSS_LRE_DUPLICATE_DISCARD (ICSS_LRE_START + 104)
+#define ICSS_LRE_TRANSPARENT_RECEPTION (ICSS_LRE_START + 108)
+#define ICSS_LRE_CNT_NODES (ICSS_LRE_START + 52)
+
+/* SRAM */
+#define ICSS_LRE_IEC62439_CONST_DUPLICATE_ACCEPT 0x01
+#define ICSS_LRE_IEC62439_CONST_DUPLICATE_DISCARD 0x02
+#define ICSS_LRE_IEC62439_CONST_TRANSP_RECEPTION_REMOVE_RCT 0x01
+#define ICSS_LRE_IEC62439_CONST_TRANSP_RECEPTION_PASS_RCT 0x02
+
+/* Enable/disable interrupts for high/low priority instead of per port.
+ * 0 = disabled (default), 1 = enabled
+ */
+#define ICSS_LRE_PRIORITY_INTRS_STATUS_OFFSET 0x1FAA
+/* Enable/disable timestamping of packets. 0 = disabled (default) 1 = enabled */
+#define ICSS_LRE_TIMESTAMP_PKTS_STATUS_OFFSET 0x1FAB
+#define ICSS_LRE_TIMESTAMP_ARRAY_OFFSET 0xC200
+
+/* HOST_TIMER_CHECK_FLAGS bits */
+#define ICSS_LRE_HOST_TIMER_NODE_TABLE_CHECK_BIT BIT(0)
+#define ICSS_LRE_HOST_TIMER_NODE_TABLE_CLEAR_BIT BIT(4)
+#define ICSS_LRE_HOST_TIMER_HOST_TABLE_CHECK_BIT BIT(8)
+#define ICSS_LRE_HOST_TIMER_P1_TABLE_CHECK_BIT BIT(16)
+#define ICSS_LRE_HOST_TIMER_P2_TABLE_CHECK_BIT BIT(24)
+#define ICSS_LRE_HOST_TIMER_PORT_TABLE_CHECK_BITS \
+ (ICSS_LRE_HOST_TIMER_P1_TABLE_CHECK_BIT | \
+ ICSS_LRE_HOST_TIMER_P2_TABLE_CHECK_BIT)
+
+/* PRU1 DMEM */
+/* Node table offsets are different for AM3/4 vs AM57/K2G, set by firmware */
+#define ICSS_LRE_V1_0_HASH_MASK 0x3F
+#define ICSS_LRE_V1_0_INDEX_ARRAY_NT 0x60
+#define ICSS_LRE_V1_0_BIN_ARRAY 0x1A00
+#define ICSS_LRE_V1_0_NODE_TABLE_NEW 0x1FC0
+#define ICSS_LRE_V1_0_INDEX_ARRAY_LOC PRUETH_MEM_DRAM0
+#define ICSS_LRE_V1_0_BIN_ARRAY_LOC PRUETH_MEM_DRAM0
+#define ICSS_LRE_V1_0_NODE_TABLE_LOC PRUETH_MEM_SHARED_RAM
+#define ICSS_LRE_V1_0_INDEX_TBL_MAX_ENTRIES 64
+#define ICSS_LRE_V1_0_BIN_TBL_MAX_ENTRIES 128
+#define ICSS_LRE_V1_0_NODE_TBL_MAX_ENTRIES 128
+
+#define ICSS_LRE_V2_1_HASH_MASK 0xFF
+#define ICSS_LRE_V2_1_INDEX_ARRAY_NT 0x3000
+#define ICSS_LRE_V2_1_BIN_ARRAY \
+ (ICSS_LRE_V2_1_INDEX_ARRAY_NT + \
+ (ICSS_LRE_V2_1_INDEX_TBL_MAX_ENTRIES * 6))
+#define ICSS_LRE_V2_1_NODE_TABLE_NEW \
+ (ICSS_LRE_V2_1_BIN_ARRAY + \
+ (ICSS_LRE_V2_1_BIN_TBL_MAX_ENTRIES * 8))
+#define ICSS_LRE_V2_1_INDEX_ARRAY_LOC PRUETH_MEM_SHARED_RAM
+#define ICSS_LRE_V2_1_BIN_ARRAY_LOC PRUETH_MEM_SHARED_RAM
+#define ICSS_LRE_V2_1_NODE_TABLE_LOC PRUETH_MEM_SHARED_RAM
+#define ICSS_LRE_V2_1_INDEX_TBL_MAX_ENTRIES 256
+#define ICSS_LRE_V2_1_BIN_TBL_MAX_ENTRIES 256
+#define ICSS_LRE_V2_1_NODE_TBL_MAX_ENTRIES 256
+
+#define ICSS_LRE_NODE_FREE 0x10
+#define ICSS_LRE_NODE_TAKEN 0x01
+#define ICSS_LRE_NT_REM_NODE_TYPE_MASK 0x1F
+#define ICSS_LRE_NT_REM_NODE_TYPE_SHIFT 0x00
+
+#define ICSS_LRE_NT_REM_NODE_TYPE_SANA 0x01
+#define ICSS_LRE_NT_REM_NODE_TYPE_SANB 0x02
+#define ICSS_LRE_NT_REM_NODE_TYPE_SANAB 0x03
+#define ICSS_LRE_NT_REM_NODE_TYPE_DAN 0x04
+#define ICSS_LRE_NT_REM_NODE_TYPE_REDBOX 0x08
+#define ICSS_LRE_NT_REM_NODE_TYPE_VDAN 0x10
+
+#define ICSS_LRE_NT_REM_NODE_HSR_BIT 0x20 /* if set node is HSR */
+
+#define ICSS_LRE_NT_REM_NODE_DUP_MASK 0xC0
+#define ICSS_LRE_NT_REM_NODE_DUP_SHIFT 0x06
+
+/* Node entry duplicate type: DupAccept */
+#define ICSS_LRE_NT_REM_NODE_DUP_ACCEPT 0x40
+/* Node entry duplicate type: DupDiscard */
+#define ICSS_LRE_NT_REM_NODE_DUP_DISCARD 0x80
+
+#endif /* __ICSS_LRE_FIRMWARE_H */
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
index b7e94244355a..6ebc52ce7123 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.c
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
@@ -30,6 +30,7 @@
#include "icssm_prueth.h"
#include "icssm_prueth_switch.h"
+#include "icssm_prueth_lre.h"
#include "icssm_vlan_mcast_filter_mmap.h"
#include "../icssg/icssg_mii_rt.h"
#include "../icssg/icss_iep.h"
@@ -40,6 +41,27 @@
#define TX_CLK_DELAY_100M 0x6
#define HR_TIMER_TX_DELAY_US 100
+static const struct prueth_fw_offsets fw_offsets_v2_1;
+static void icssm_prueth_set_fw_offsets(struct prueth *prueth)
+{
+ /* Set Multicast filter control and table offsets */
+ if (PRUETH_IS_EMAC(prueth) || PRUETH_IS_SWITCH(prueth)) {
+ prueth->fw_offsets.mc_ctrl_offset =
+ ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET;
+ prueth->fw_offsets.mc_filter_mask =
+ ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET;
+ prueth->fw_offsets.mc_filter_tbl =
+ ICSS_EMAC_FW_MULTICAST_FILTER_TABLE;
+ } else {
+ prueth->fw_offsets.mc_ctrl_offset =
+ ICSS_LRE_FW_MULTICAST_TABLE_SEARCH_OP_CONTROL_BIT;
+ prueth->fw_offsets.mc_filter_mask =
+ ICSS_LRE_FW_MULTICAST_FILTER_MASK;
+ prueth->fw_offsets.mc_filter_tbl =
+ ICSS_LRE_FW_MULTICAST_FILTER_TABLE;
+ }
+}
+
static void icssm_prueth_write_reg(struct prueth *prueth,
enum prueth_mem region,
unsigned int reg, u32 val)
@@ -309,12 +331,15 @@ static void icssm_prueth_hostinit(struct prueth *prueth)
icssm_prueth_mii_init(prueth);
}
-/* This function initialize the driver in EMAC mode
+/* Initialize the driver in EMAC, HSR or PRP mode
* based on eth_type
*/
static void icssm_prueth_init_ethernet_mode(struct prueth *prueth)
{
+ icssm_prueth_set_fw_offsets(prueth);
icssm_prueth_hostinit(prueth);
+ if (prueth_is_lre(prueth))
+ icssm_prueth_lre_config(prueth);
}
static void icssm_prueth_port_enable(struct prueth_emac *emac, bool enable)
@@ -609,6 +634,9 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
/* update first buffer descriptor */
wr_buf_desc = (pktlen << PRUETH_BD_LENGTH_SHIFT) &
PRUETH_BD_LENGTH_MASK;
+ if (PRUETH_IS_HSR(prueth))
+ wr_buf_desc |= BIT(PRUETH_BD_HSR_FRAME_SHIFT);
+
sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
if (!PRUETH_IS_EMAC(prueth))
writel(wr_buf_desc, sram + readw(&queue_desc->wr_ptr));
@@ -627,6 +655,12 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor,
struct prueth_packet_info *pkt_info)
{
+ if (prueth_is_lre(prueth))
+ pkt_info->start_offset = !!(buffer_descriptor &
+ PRUETH_BD_START_FLAG_MASK);
+ else
+ pkt_info->start_offset = false;
+
pkt_info->port = (buffer_descriptor & PRUETH_BD_PORT_MASK) >>
PRUETH_BD_PORT_SHIFT;
pkt_info->length = (buffer_descriptor & PRUETH_BD_LENGTH_MASK) >>
@@ -661,10 +695,14 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
unsigned int actual_pkt_len;
bool buffer_wrapped = false;
void *src_addr, *dst_addr;
+ u16 start_offset = 0;
struct sk_buff *skb;
int pkt_block_size;
void *ocmc_ram;
+ if (PRUETH_IS_HSR(emac->prueth))
+ start_offset = (pkt_info->start_offset ?
+ ICSSM_LRE_TAG_SIZE : 0);
/* the PRU firmware deals mostly in pointers already
* offset into ram, we would like to deal in indexes
* within the queue we are working with for code
@@ -687,7 +725,8 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
/* calculate new pointer in ram */
*bd_rd_ptr = rxqueue->buffer_desc_offset + (update_block * BD_SIZE);
- actual_pkt_len = pkt_info->length;
+ /* Exclude the HSR tag bytes already stripped by firmware, if any. */
+ actual_pkt_len = pkt_info->length - start_offset;
/* Allocate a socket buffer for this packet */
skb = netdev_alloc_skb_ip_align(ndev, actual_pkt_len);
@@ -707,6 +746,7 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
*/
src_addr = ocmc_ram + rxqueue->buffer_offset +
(read_block * ICSS_BLOCK_SIZE);
+ src_addr += start_offset;
/* Copy the data from PRU buffers(OCMC) to socket buffer(DRAM) */
if (buffer_wrapped) { /* wrapped around buffer */
@@ -720,6 +760,9 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
if (pkt_info->length < bytes)
bytes = pkt_info->length;
+ /* If applicable, account for the HSR tag removed */
+ bytes -= start_offset;
+
/* copy non-wrapped part */
memcpy(dst_addr, src_addr, bytes);
@@ -741,6 +784,12 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
icssm_prueth_sw_learn_fdb(emac, skb->data + ETH_ALEN);
}
+ /* For PRP, the RCT trailer is at the frame tail, exclude it from
+ * the length to avoid passing it up the stack.
+ */
+ if (PRUETH_IS_PRP(emac->prueth) && pkt_info->start_offset)
+ actual_pkt_len -= ICSSM_LRE_TAG_SIZE;
+
skb_put(skb, actual_pkt_len);
/* send packet up the stack */
@@ -982,14 +1031,20 @@ static int icssm_emac_ndo_open(struct net_device *ndev)
icssm_prueth_init_ethernet_mode(prueth);
/* reset and start PRU firmware */
- if (PRUETH_IS_SWITCH(prueth)) {
+ if (!PRUETH_IS_EMAC(prueth)) {
+ /* Switch, HSR and PRP protocols share same queue structure */
ret = icssm_prueth_sw_emac_config(emac);
if (ret)
return ret;
- ret = icssm_prueth_sw_init_fdb_table(prueth);
- if (ret)
- return ret;
+ if (PRUETH_IS_SWITCH(prueth)) {
+ ret = icssm_prueth_sw_init_fdb_table(prueth);
+ if (ret)
+ return ret;
+ } else {
+ /* LRE mode: set up duplicate-table check flags */
+ icssm_prueth_lre_config_check_flags(prueth);
+ }
} else {
icssm_prueth_emac_config(emac);
}
@@ -1079,6 +1134,9 @@ static int icssm_emac_ndo_stop(struct net_device *ndev)
else
rproc_shutdown(emac->pru);
+ if (prueth_is_lre(prueth) && !prueth->emac_configured)
+ icssm_prueth_lre_cleanup(prueth);
+
/* free rx interrupts */
free_irq(emac->rx_irq, ndev);
@@ -1122,7 +1180,8 @@ static int icssm_prueth_change_mode(struct prueth *prueth,
}
}
- if (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH) {
+ if (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH ||
+ mode == PRUSS_ETHTYPE_HSR || mode == PRUSS_ETHTYPE_PRP) {
prueth->eth_type = mode;
} else {
dev_err(prueth->dev, "unknown mode\n");
@@ -1266,11 +1325,16 @@ static void icssm_emac_mc_filter_ctrl(struct prueth_emac *emac, bool enable)
{
struct prueth *prueth = emac->prueth;
void __iomem *mc_filter_ctrl;
+ u32 mc_ctrl_offset;
void __iomem *ram;
u32 reg;
ram = prueth->mem[emac->dram].va;
- mc_filter_ctrl = ram + ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET;
+ if (prueth_is_lre(prueth))
+ ram = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ mc_ctrl_offset = prueth->fw_offsets.mc_ctrl_offset;
+ mc_filter_ctrl = ram + mc_ctrl_offset;
if (enable)
reg = ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_ENABLED;
@@ -1289,7 +1353,10 @@ static void icssm_emac_mc_filter_reset(struct prueth_emac *emac)
void __iomem *ram;
ram = prueth->mem[emac->dram].va;
- mc_filter_tbl_base = ICSS_EMAC_FW_MULTICAST_FILTER_TABLE;
+ if (prueth_is_lre(prueth))
+ ram = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ mc_filter_tbl_base = prueth->fw_offsets.mc_filter_tbl;
mc_filter_tbl = ram + mc_filter_tbl_base;
memset_io(mc_filter_tbl, 0, ICSS_EMAC_FW_MULTICAST_TABLE_SIZE_BYTES);
@@ -1302,11 +1369,16 @@ static void icssm_emac_mc_filter_hashmask
{
struct prueth *prueth = emac->prueth;
void __iomem *mc_filter_mask;
+ u32 mc_filter_mask_base;
void __iomem *ram;
ram = prueth->mem[emac->dram].va;
+ if (prueth_is_lre(prueth))
+ ram = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ mc_filter_mask_base = prueth->fw_offsets.mc_filter_mask;
- mc_filter_mask = ram + ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET;
+ mc_filter_mask = ram + mc_filter_mask_base;
memcpy_toio(mc_filter_mask, mask,
ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES);
}
@@ -1316,11 +1388,16 @@ static void icssm_emac_mc_filter_bin_update(struct prueth_emac *emac, u8 hash,
{
struct prueth *prueth = emac->prueth;
void __iomem *mc_filter_tbl;
+ u32 mc_filter_tbl_base;
void __iomem *ram;
ram = prueth->mem[emac->dram].va;
+ if (prueth_is_lre(prueth))
+ ram = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ mc_filter_tbl_base = prueth->fw_offsets.mc_filter_tbl;
- mc_filter_tbl = ram + ICSS_EMAC_FW_MULTICAST_FILTER_TABLE;
+ mc_filter_tbl = ram + mc_filter_tbl_base;
writeb(val, mc_filter_tbl + hash);
}
@@ -1436,6 +1513,8 @@ static const struct net_device_ops emac_netdev_ops = {
.ndo_open = icssm_emac_ndo_open,
.ndo_stop = icssm_emac_ndo_stop,
.ndo_start_xmit = icssm_emac_ndo_start_xmit,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
.ndo_get_stats64 = icssm_emac_ndo_get_stats64,
.ndo_set_rx_mode = icssm_emac_ndo_set_rx_mode,
};
@@ -1589,6 +1668,10 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,
ndev->hw_features |= NETIF_F_HW_L2FW_DOFFLOAD;
}
+ if (prueth->support_lre)
+ ndev->hw_features |=
+ (NETIF_F_HW_HSR_FWD | NETIF_F_HW_HSR_TAG_RM);
+
ndev->dev.of_node = eth_node;
ndev->netdev_ops = &emac_netdev_ops;
@@ -1741,6 +1824,104 @@ static int icssm_prueth_ndev_port_unlink(struct net_device *ndev)
return ret;
}
+static int icssm_prueth_hsr_port_link(struct net_device *ndev,
+ struct net_device *hsr_ndev)
+{
+ struct prueth_emac *emac = netdev_priv(ndev);
+ struct prueth *prueth = emac->prueth;
+ struct prueth_emac *emac0;
+ struct prueth_emac *emac1;
+ enum pruss_ethtype mode;
+ enum hsr_version ver;
+ unsigned long flags;
+ int ret = 0;
+
+ if (PRUETH_IS_SWITCH(prueth))
+ return -EOPNOTSUPP;
+
+ hsr_get_version(hsr_ndev, &ver);
+
+ if (ver == HSR_V1)
+ mode = PRUSS_ETHTYPE_HSR;
+ else if (ver == PRP_V1)
+ mode = PRUSS_ETHTYPE_PRP;
+ else
+ return -EOPNOTSUPP;
+
+ emac0 = prueth->emac[PRUETH_MAC0];
+ emac1 = prueth->emac[PRUETH_MAC1];
+
+ spin_lock_irqsave(&emac->addr_lock, flags);
+
+ if (!prueth->hsr_members) {
+ prueth->hsr_dev = hsr_ndev;
+ } else {
+ /* Adding the port to a second bridge is not supported */
+ if (prueth->hsr_dev != hsr_ndev) {
+ spin_unlock_irqrestore(&emac->addr_lock, flags);
+ return -EOPNOTSUPP;
+ }
+ }
+
+ prueth->hsr_members |= BIT(emac->port_id);
+
+ spin_unlock_irqrestore(&emac->addr_lock, flags);
+
+ if (!prueth_is_lre(prueth)) {
+ if (prueth->hsr_members & BIT(PRUETH_PORT_MII0) &&
+ prueth->hsr_members & BIT(PRUETH_PORT_MII1)) {
+ if (!(emac0->ndev->features &
+ (NETIF_F_HW_HSR_TAG_RM | NETIF_F_HW_HSR_FWD)) &&
+ !(emac1->ndev->features &
+ (NETIF_F_HW_HSR_TAG_RM | NETIF_F_HW_HSR_FWD)))
+ return -EOPNOTSUPP;
+
+ ret = icssm_prueth_change_mode(prueth, mode);
+ if (ret < 0) {
+ dev_err(prueth->dev, "Failed to enable HSR mode\n");
+ } else {
+ dev_info(prueth->dev,
+ "TI PRU ethernet now in %s mode\n",
+ (mode == PRUSS_ETHTYPE_HSR) ?
+ "HSR" : "PRP");
+ }
+ }
+ }
+
+ return ret;
+}
+
+static int icssm_prueth_hsr_port_unlink(struct net_device *ndev)
+{
+ struct prueth_emac *emac = netdev_priv(ndev);
+ struct prueth *prueth = emac->prueth;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&emac->addr_lock, flags);
+
+ prueth->hsr_members &= ~BIT(emac->port_id);
+
+ spin_unlock_irqrestore(&emac->addr_lock, flags);
+
+ if (prueth_is_lre(prueth)) {
+ ret = icssm_prueth_change_mode(prueth, PRUSS_ETHTYPE_EMAC);
+ if (ret < 0) {
+ dev_err(prueth->dev, "Failed to enable dual EMAC mode\n");
+ return ret;
+ }
+ }
+
+ spin_lock_irqsave(&emac->addr_lock, flags);
+
+ if (!prueth->hsr_members)
+ prueth->hsr_dev = NULL;
+
+ spin_unlock_irqrestore(&emac->addr_lock, flags);
+
+ return 0;
+}
+
static int icssm_prueth_ndev_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
@@ -1754,6 +1935,16 @@ static int icssm_prueth_ndev_event(struct notifier_block *unused,
switch (event) {
case NETDEV_CHANGEUPPER:
info = ptr;
+ if ((ndev->features &
+ (NETIF_F_HW_HSR_FWD | NETIF_F_HW_HSR_TAG_RM)) &&
+ is_hsr_master(info->upper_dev)) {
+ if (info->linking)
+ ret = icssm_prueth_hsr_port_link
+ (ndev, info->upper_dev);
+ else
+ ret = icssm_prueth_hsr_port_unlink(ndev);
+ }
+
if (netif_is_bridge_master(info->upper_dev)) {
if (info->linking)
ret = icssm_prueth_ndev_port_link
@@ -1796,6 +1987,7 @@ static int icssm_prueth_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct device_node *np;
struct prueth *prueth;
+ bool has_lre = false;
struct pruss *pruss;
int i, ret;
@@ -1810,6 +2002,7 @@ static int icssm_prueth_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, prueth);
prueth->dev = dev;
prueth->fw_data = device_get_match_data(dev);
+ prueth->fw_offsets = fw_offsets_v2_1;
eth_ports_node = of_get_child_by_name(np, "ethernet-ports");
if (!eth_ports_node)
@@ -1955,6 +2148,16 @@ static int icssm_prueth_probe(struct platform_device *pdev)
prueth->mem[PRUETH_MEM_OCMC].va,
prueth->mem[PRUETH_MEM_OCMC].size);
+ if (IS_ENABLED(CONFIG_HSR) && prueth->fw_data->support_lre)
+ has_lre = true;
+
+ /* LRE requires both ethernet nodes to be present in
+ * DT, otherwise clear the support flag
+ */
+ if (has_lre && (!eth0_node || !eth1_node))
+ has_lre = false;
+
+ prueth->support_lre = has_lre;
/* setup netdev interfaces */
if (eth0_node) {
ret = icssm_prueth_netdev_init(prueth, eth0_node);
@@ -2176,15 +2379,24 @@ static struct prueth_private_data am335x_prueth_pdata = {
.fw_pru[PRUSS_PRU0] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am335x-pru0-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am335x-pru0-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am335x-pru0-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am335x-pru0-prusw-fw.elf",
},
.fw_pru[PRUSS_PRU1] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am335x-pru1-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am335x-pru1-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am335x-pru1-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am335x-pru1-prusw-fw.elf",
},
+ .support_lre = true,
.support_switch = true,
};
@@ -2194,15 +2406,24 @@ static struct prueth_private_data am437x_prueth_pdata = {
.fw_pru[PRUSS_PRU0] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am437x-pru0-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am437x-pru0-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am437x-pru0-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am437x-pru0-prusw-fw.elf",
},
.fw_pru[PRUSS_PRU1] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am437x-pru1-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am437x-pru1-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am437x-pru1-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am437x-pru1-prusw-fw.elf",
},
+ .support_lre = true,
.support_switch = true,
};
@@ -2212,16 +2433,25 @@ static struct prueth_private_data am57xx_prueth_pdata = {
.fw_pru[PRUSS_PRU0] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am57xx-pru0-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am57xx-pru0-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am57xx-pru0-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am57xx-pru0-prusw-fw.elf",
},
.fw_pru[PRUSS_PRU1] = {
.fw_name[PRUSS_ETHTYPE_EMAC] =
"ti-pruss/am57xx-pru1-prueth-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_HSR] =
+ "ti-pruss/am57xx-pru1-pruhsr-fw.elf",
+ .fw_name[PRUSS_ETHTYPE_PRP] =
+ "ti-pruss/am57xx-pru1-pruprp-fw.elf",
.fw_name[PRUSS_ETHTYPE_SWITCH] =
"ti-pruss/am57xx-pru1-prusw-fw.elf",
},
+ .support_lre = true,
.support_switch = true,
};
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.h b/drivers/net/ethernet/ti/icssm/icssm_prueth.h
index d5b49b462c24..098d81599415 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.h
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.h
@@ -16,10 +16,13 @@
#include "icssm_switch.h"
#include "icssm_prueth_ptp.h"
#include "icssm_prueth_fdb_tbl.h"
+#include "icssm_lre_firmware.h"
/* ICSSM size of redundancy tag */
#define ICSSM_LRE_TAG_SIZE 6
+#define PRUETH_TIMER_MS (10)
+
/* PRUSS local memory map */
#define ICSS_LOCAL_SHARED_RAM 0x00010000
#define EMAC_MAX_PKTLEN (ETH_HLEN + VLAN_HLEN + ETH_DATA_LEN)
@@ -42,6 +45,8 @@ enum pruss_ethtype {
#define PRUETH_IS_EMAC(p) ((p)->eth_type == PRUSS_ETHTYPE_EMAC)
#define PRUETH_IS_SWITCH(p) ((p)->eth_type == PRUSS_ETHTYPE_SWITCH)
+#define PRUETH_IS_HSR(p) ((p)->eth_type == PRUSS_ETHTYPE_HSR)
+#define PRUETH_IS_PRP(p) ((p)->eth_type == PRUSS_ETHTYPE_PRP)
/**
* struct prueth_queue_desc - Queue descriptor
@@ -86,6 +91,7 @@ struct prueth_queue_info {
/**
* struct prueth_packet_info - Info about a packet in buffer
+ * @start_offset: true if frame carries an HSR/PRP start offset
* @shadow: this packet is stored in the collision queue
* @port: port packet is on
* @length: length of packet
@@ -96,6 +102,7 @@ struct prueth_queue_info {
* @timestamp: Specifies if timestamp is appended to the packet
*/
struct prueth_packet_info {
+ bool start_offset;
bool shadow;
unsigned int port;
unsigned int length;
@@ -171,6 +178,13 @@ enum prueth_mem {
PRUETH_MEM_MAX,
};
+/* Firmware offsets/size information */
+struct prueth_fw_offsets {
+ u32 mc_ctrl_offset;
+ u32 mc_filter_mask;
+ u32 mc_filter_tbl;
+};
+
enum pruss_device {
PRUSS_AM57XX = 0,
PRUSS_AM43XX,
@@ -183,11 +197,13 @@ enum pruss_device {
* @driver_data: PRU Ethernet device name
* @fw_pru: firmware names to be used for PRUSS ethernet usecases
* @support_switch: boolean to indicate if switch is enabled
+ * @support_lre: boolean to indicate if LRE is enabled
*/
struct prueth_private_data {
enum pruss_device driver_data;
const struct prueth_firmware fw_pru[PRUSS_NUM_PRUS];
bool support_switch;
+ bool support_lre;
};
struct prueth_emac_stats {
@@ -248,13 +264,18 @@ struct prueth {
struct icss_iep *iep;
const struct prueth_private_data *fw_data;
- struct prueth_fw_offsets *fw_offsets;
+ struct prueth_fw_offsets fw_offsets;
struct device_node *eth_node[PRUETH_NUM_MACS];
struct prueth_emac *emac[PRUETH_NUM_MACS];
struct net_device *registered_netdevs[PRUETH_NUM_MACS];
+ bool support_lre;
+ unsigned int tbl_check_mask;
+ struct hrtimer tbl_check_timer;
+
struct net_device *hw_bridge_dev;
+ struct net_device *hsr_dev;
struct fdb_tbl *fdb_tbl;
struct notifier_block prueth_netdevice_nb;
@@ -264,6 +285,7 @@ struct prueth {
unsigned int eth_type;
size_t ocmc_ram_size;
u8 emac_configured;
+ u8 hsr_members;
u8 br_members;
};
@@ -277,4 +299,9 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
void icssm_emac_mc_filter_bin_allow(struct prueth_emac *emac, u8 hash);
void icssm_emac_mc_filter_bin_disallow(struct prueth_emac *emac, u8 hash);
u8 icssm_emac_get_mc_hash(u8 *mac, u8 *mask);
+
+static inline bool prueth_is_lre(struct prueth *prueth)
+{
+ return PRUETH_IS_HSR(prueth) || PRUETH_IS_PRP(prueth);
+}
#endif /* __NET_TI_PRUETH_H */
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
new file mode 100644
index 000000000000..930bc59df4c5
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Texas Instruments PRUETH hsr/prp Link Redundancy Entity (LRE) Driver.
+ *
+ * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include "icssm_lre_firmware.h"
+#include "icssm_prueth_lre.h"
+#include "icssm_prueth.h"
+#include "icssm_prueth_switch.h"
+
+void icssm_prueth_lre_config_check_flags(struct prueth *prueth)
+{
+ void __iomem *dram1 = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ /* HSR/PRP: initialize check table when first port is up */
+ if (prueth->emac_configured)
+ return;
+
+ prueth->tbl_check_mask = ICSS_LRE_HOST_TIMER_HOST_TABLE_CHECK_BIT;
+ if (PRUETH_IS_HSR(prueth))
+ prueth->tbl_check_mask |=
+ ICSS_LRE_HOST_TIMER_PORT_TABLE_CHECK_BITS;
+ writel(prueth->tbl_check_mask, dram1 + ICSS_LRE_HOST_TIMER_CHECK_FLAGS);
+}
+
+/* A group of PCPs are mapped to a Queue. This is the size of firmware
+ * array in shared memory
+ */
+#define PCP_GROUP_TO_QUEUE_MAP_SIZE 4
+
+/* PRU firmware default PCP to priority Queue map for ingress & egress
+ *
+ * At ingress to Host
+ * ==================
+ * byte 0 => PRU 1, PCP 0-3 => Q3
+ * byte 1 => PRU 1, PCP 4-7 => Q2
+ * byte 2 => PRU 0, PCP 0-3 => Q1
+ * byte 3 => PRU 0, PCP 4-7 => Q0
+ *
+ * At egress to wire/network on PRU-0 and PRU-1
+ * ============================================
+ * byte 0 => Host, PCP 0-3 => Q3
+ * byte 1 => Host, PCP 4-7 => Q2
+ *
+ * PRU-0
+ * -----
+ * byte 2 => PRU-1, PCP 0-3 => Q1
+ * byte 3 => PRU-1, PCP 4-7 => Q0
+ *
+ * PRU-1
+ * -----
+ * byte 2 => PRU-0, PCP 0-3 => Q1
+ * byte 3 => PRU-0, PCP 4-7 => Q0
+ *
+ * queue names below are named 1 based. i.e PRUETH_QUEUE1 is Q0,
+ * PRUETH_QUEUE2 is Q1 and so forth. Firmware convention is that
+ * a lower queue number has higher priority than a higher queue
+ * number.
+ */
+static u8 fw_pcp_default_priority_queue_map[PCP_GROUP_TO_QUEUE_MAP_SIZE] = {
+ /* port 2 or PRU 1 */
+ PRUETH_QUEUE4, PRUETH_QUEUE3,
+ /* port 1 or PRU 0 */
+ PRUETH_QUEUE2, PRUETH_QUEUE1,
+};
+
+static void icssm_prueth_lre_pcp_queue_map_config(struct prueth *prueth)
+{
+ void __iomem *sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+
+ memcpy_toio(sram + ICSS_LRE_QUEUE_2_PCP_MAP_OFFSET,
+ &fw_pcp_default_priority_queue_map[0],
+ PCP_GROUP_TO_QUEUE_MAP_SIZE);
+}
+
+static void icssm_prueth_lre_host_table_init(struct prueth *prueth)
+{
+ void __iomem *dram0 = prueth->mem[PRUETH_MEM_DRAM0].va;
+ void __iomem *dram1 = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ memset_io(dram0 + ICSS_LRE_DUPLICATE_HOST_TABLE, 0,
+ ICSS_LRE_DUPLICATE_HOST_TABLE_DMEM_SIZE);
+
+ writel(ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE_INIT,
+ dram1 + ICSS_LRE_DUPLICATE_HOST_TABLE_SIZE);
+
+ writel(ICSS_LRE_TABLE_CHECK_RESOLUTION_10_MS,
+ dram1 + ICSS_LRE_DUPLI_HOST_CHECK_RESO);
+
+ writel(ICSS_LRE_MASTER_SLAVE_BUSY_BITS_CLEAR,
+ dram1 + ICSS_LRE_HOST_DUPLICATE_ARBITRATION);
+}
+
+static void icssm_prueth_lre_port_table_init(struct prueth *prueth)
+{
+ void __iomem *dram1 = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ if (PRUETH_IS_HSR(prueth)) {
+ memset_io(dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_PRU0, 0,
+ ICSS_LRE_DUPLICATE_PORT_TABLE_DMEM_SIZE);
+ memset_io(dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_PRU1, 0,
+ ICSS_LRE_DUPLICATE_PORT_TABLE_DMEM_SIZE);
+
+ writel(ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE_INIT,
+ dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE);
+ } else {
+ writel(0, dram1 + ICSS_LRE_DUPLICATE_PORT_TABLE_SIZE);
+ }
+
+ writel(ICSS_LRE_TABLE_CHECK_RESOLUTION_10_MS,
+ dram1 + ICSS_LRE_DUPLI_PORT_CHECK_RESO);
+}
+
+static void icssm_prueth_lre_init(struct prueth *prueth)
+{
+ void __iomem *sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
+
+ memset_io(sram + ICSS_LRE_START, 0, ICSS_LRE_STATS_DMEM_SIZE);
+
+ writel(ICSS_LRE_IEC62439_CONST_DUPLICATE_DISCARD,
+ sram + ICSS_LRE_DUPLICATE_DISCARD);
+ writel(ICSS_LRE_IEC62439_CONST_TRANSP_RECEPTION_REMOVE_RCT,
+ sram + ICSS_LRE_TRANSPARENT_RECEPTION);
+}
+
+static void icssm_prueth_lre_dbg_init(struct prueth *prueth)
+{
+ void __iomem *dram0 = prueth->mem[PRUETH_MEM_DRAM0].va;
+
+ memset_io(dram0 + ICSS_LRE_DBG_START, 0,
+ ICSS_LRE_DEBUG_COUNTER_DMEM_SIZE);
+}
+
+static void icssm_prueth_lre_protocol_init(struct prueth *prueth)
+{
+ void __iomem *dram0 = prueth->mem[PRUETH_MEM_DRAM0].va;
+ void __iomem *dram1 = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ if (PRUETH_IS_HSR(prueth))
+ writew(ICSS_LRE_MODEH, dram0 + ICSS_LRE_HSR_MODE_OFFSET);
+
+ writel(ICSS_LRE_DUPLICATE_FORGET_TIME_400_MS,
+ dram1 + ICSS_LRE_DUPLI_FORGET_TIME);
+ writel(ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_HIGH,
+ dram1 + ICSS_LRE_SUP_ADDR);
+ writel(ICSS_LRE_SUP_ADDRESS_INIT_OCTETS_LOW,
+ dram1 + ICSS_LRE_SUP_ADDR_LOW);
+}
+
+static enum hrtimer_restart icssm_prueth_lre_timer(struct hrtimer *timer)
+{
+ struct prueth *prueth;
+ unsigned int timeout;
+ void __iomem *dram;
+
+ prueth = container_of(timer, struct prueth, tbl_check_timer);
+ dram = prueth->mem[PRUETH_MEM_DRAM1].va;
+ timeout = PRUETH_TIMER_MS;
+
+ hrtimer_forward_now(timer, ms_to_ktime(timeout));
+ if (prueth->emac_configured !=
+ (BIT(PRUETH_PORT_MII0) | BIT(PRUETH_PORT_MII1)))
+ return HRTIMER_RESTART;
+
+ /* Set the flags for duplicate tables so the firmware checks and
+ * updates them every 10 milliseconds.
+ */
+ writel(prueth->tbl_check_mask, dram + ICSS_LRE_HOST_TIMER_CHECK_FLAGS);
+
+ return HRTIMER_RESTART;
+}
+
+static void icssm_prueth_lre_init_timer(struct prueth *prueth)
+{
+ hrtimer_setup(&prueth->tbl_check_timer, &icssm_prueth_lre_timer,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+}
+
+static void icssm_prueth_lre_start_timer(struct prueth *prueth)
+{
+ unsigned int timeout = PRUETH_TIMER_MS;
+
+ if (hrtimer_active(&prueth->tbl_check_timer))
+ return;
+
+ hrtimer_start(&prueth->tbl_check_timer, ms_to_ktime(timeout),
+ HRTIMER_MODE_REL);
+}
+
+void icssm_prueth_lre_config(struct prueth *prueth)
+{
+ icssm_prueth_lre_init_timer(prueth);
+ icssm_prueth_lre_start_timer(prueth);
+ icssm_prueth_lre_pcp_queue_map_config(prueth);
+ icssm_prueth_lre_host_table_init(prueth);
+ icssm_prueth_lre_port_table_init(prueth);
+ icssm_prueth_lre_init(prueth);
+ icssm_prueth_lre_dbg_init(prueth);
+ icssm_prueth_lre_protocol_init(prueth);
+}
+
+void icssm_prueth_lre_cleanup(struct prueth *prueth)
+{
+ hrtimer_cancel(&prueth->tbl_check_timer);
+}
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h
new file mode 100644
index 000000000000..0fe4d1ae5823
--- /dev/null
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
+ */
+
+#ifndef __NET_TI_PRUETH_LRE_H
+#define __NET_TI_PRUETH_LRE_H
+
+#include <linux/etherdevice.h>
+#include <linux/interrupt.h>
+#include <linux/if_vlan.h>
+
+#include "icssm_prueth.h"
+#include "icssm_lre_firmware.h"
+
+void icssm_prueth_lre_config(struct prueth *prueth);
+void icssm_prueth_lre_cleanup(struct prueth *prueth);
+void icssm_prueth_lre_config_check_flags(struct prueth *prueth);
+
+#endif /* __NET_TI_PRUETH_LRE_H */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 3/3] net: ti: icssm-prueth: Support duplicate HW offload feature for HSR and PRP
From: Parvathi Pudi @ 2026-06-11 12:33 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
parvathi, rogerq, pmohan, afd, basharath, arnd
Cc: linux-kernel, netdev, linux-arm-kernel, pratheesh, j-rameshbabu,
vigneshr, praneeth, srk, rogerq, m-malladi, krishna, mohan
In-Reply-To: <20260611123636.376577-1-parvathi@couthit.com>
From: Roger Quadros <rogerq@ti.com>
In HSR and PRP modes each outgoing frame must be sent on both PRU slave
ports.
Previously the driver was writing the frame into each port's transmit queue
independently after updating the tags resulting in performing two OCMC
buffer copy operations.
Frame duplicate offloading is implemented with a common shared queue
between the two ports. The driver writes the frame once into OCMC RAM,
each port reads from the shared queue and replicates the transmission to
both PRU ports, synchronising between PRU ports are maintained within
firmware with appropriate handling.
For HSR the driver inspects the encapsulated ethertype in the HSR tag.
PTP frames (ETH_P_1588) are sent on the directed port only to avoid double
duplication and all other HSR frames are duplicated to both ports.
VLAN-tagged HSR frames are handled by advancing past the 4-byte VLAN header
before reading the HSR tag.
For PRP the driver checks the 6-byte RCT trailer for the ETH_P_PRP suffix
to identify redundancy-tagged frames. Frames without an RCT are sent on the
originating port only.
Signed-off-by: Roger Quadros <rogerq@ti.com>
Signed-off-by: Andrew F. Davis <afd@ti.com>
Signed-off-by: Parvathi Pudi <parvathi@couthit.com>
---
drivers/net/ethernet/ti/icssm/icssm_prueth.c | 218 +++++++++++-
drivers/net/ethernet/ti/icssm/icssm_prueth.h | 9 +-
.../ethernet/ti/icssm/icssm_prueth_common.c | 7 +-
.../ethernet/ti/icssm/icssm_prueth_switch.c | 310 +++++++++++++++++-
.../ethernet/ti/icssm/icssm_prueth_switch.h | 1 +
drivers/net/ethernet/ti/icssm/icssm_switch.h | 35 +-
6 files changed, 549 insertions(+), 31 deletions(-)
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
index 58a6935dd809..e2d66239380b 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.c
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
@@ -36,6 +36,7 @@
#include "../icssg/icss_iep.h"
#define OCMC_RAM_SIZE (SZ_64K)
+#define PRUETH_ETHER_TYPE_OFFSET 12
#define TX_START_DELAY 0x40
#define TX_CLK_DELAY_100M 0x6
@@ -76,6 +77,32 @@ static void icssm_prueth_set_fw_offsets(struct prueth *prueth)
}
}
+/* Queue Descriptors initialization for HSR PRP */
+const struct prueth_queue_desc hsr_prp_txopt_queue_descs[][NUM_QUEUES] = {
+ [PRUETH_PORT_QUEUE_HOST] = {
+ { .rd_ptr = P0_Q1_BD_OFFSET, .wr_ptr = P0_Q1_BD_OFFSET, },
+ { .rd_ptr = P0_Q2_BD_OFFSET, .wr_ptr = P0_Q2_BD_OFFSET, },
+ { .rd_ptr = P0_Q3_BD_OFFSET, .wr_ptr = P0_Q3_BD_OFFSET, },
+ { .rd_ptr = P0_Q4_BD_OFFSET, .wr_ptr = P0_Q4_BD_OFFSET, },
+ },
+ [PRUETH_PORT_QUEUE_MII0] = {
+ { .rd_ptr = P0_Q3_BD_OFFSET, .wr_ptr = P0_Q3_BD_OFFSET, },
+ { .rd_ptr = P0_Q4_BD_OFFSET, .wr_ptr = P0_Q4_BD_OFFSET, },
+ { .rd_ptr = P1_Q3_TXOPT_BD_OFFSET,
+ .wr_ptr = P1_Q3_TXOPT_BD_OFFSET, },
+ { .rd_ptr = P2_Q1_TXOPT_BD_OFFSET,
+ .wr_ptr = P2_Q1_TXOPT_BD_OFFSET, },
+ },
+ [PRUETH_PORT_QUEUE_MII1] = {
+ { .rd_ptr = P0_Q1_BD_OFFSET, .wr_ptr = P0_Q1_BD_OFFSET, },
+ { .rd_ptr = P0_Q2_BD_OFFSET, .wr_ptr = P0_Q2_BD_OFFSET, },
+ { .rd_ptr = P1_Q3_TXOPT_BD_OFFSET,
+ .wr_ptr = P1_Q3_TXOPT_BD_OFFSET, },
+ { .rd_ptr = P2_Q1_TXOPT_BD_OFFSET,
+ .wr_ptr = P2_Q1_TXOPT_BD_OFFSET, },
+ }
+};
+
static void icssm_prueth_write_reg(struct prueth *prueth,
enum prueth_mem region,
unsigned int reg, u32 val)
@@ -94,6 +121,17 @@ static void icssm_prueth_write_reg(struct prueth *prueth,
static enum pruss_mem pruss_mem_ids[] = { PRUSS_MEM_DRAM0, PRUSS_MEM_DRAM1,
PRUSS_MEM_SHRD_RAM2 };
+struct prp_txopt_rct {
+ __be16 sequence_nr;
+ __be16 lan_id_and_lsdu_size;
+ __be16 prp_suffix;
+};
+
+struct hsr_txopt_ethhdr {
+ struct ethhdr ethhdr;
+ struct hsr_tag hsr_tag;
+};
+
static const struct prueth_queue_info queue_infos[][NUM_QUEUES] = {
[PRUETH_PORT_QUEUE_HOST] = {
[PRUETH_QUEUE1] = {
@@ -546,15 +584,25 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
struct sk_buff *skb,
enum prueth_queue_id queue_id)
{
+ struct prueth_queue_desc __iomem *queue_desc_other_port = NULL;
struct prueth_queue_desc __iomem *queue_desc;
const struct prueth_queue_info *txqueue;
struct net_device *ndev = emac->ndev;
struct prueth *prueth = emac->prueth;
+ struct hsr_txopt_ethhdr *hsr_ethhdr;
unsigned int buffer_desc_count;
+ struct prueth_emac *other_emac;
int free_blocks, update_block;
+ struct vlan_ethhdr *vlan_hdr;
bool buffer_wrapped = false;
int write_block, read_block;
+ int free_blocks_other_port;
+ int read_block_other_port;
void *src_addr, *dst_addr;
+ u16 bd_rd_ptr_other_port;
+ struct ethhdr *ethhdr;
+ bool is_vlan = false;
+ bool link_up = false;
int pkt_block_size;
void __iomem *sram;
void __iomem *dram;
@@ -562,6 +610,14 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
u16 update_wr_ptr;
u32 wr_buf_desc;
void *ocmc_ram;
+ __be16 proto;
+ u8 *hdr;
+
+ other_emac = emac->prueth->emac[(emac->port_id == PRUETH_PORT_MII0) ?
+ PRUETH_PORT_MII1 - 1 : PRUETH_PORT_MII0 - 1];
+
+ if (prueth_is_lre(prueth) && (emac->link || other_emac->link))
+ link_up = true;
if (!PRUETH_IS_EMAC(prueth))
dram = prueth->mem[PRUETH_MEM_DRAM1].va;
@@ -579,7 +635,10 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
pktlen = skb->len;
/* Get the tx queue */
queue_desc = emac->tx_queue_descs + queue_id;
- if (!PRUETH_IS_EMAC(prueth))
+ /* Tx queue context */
+ if (prueth_is_lre(prueth))
+ txqueue = &lre_queue_infos[txport][queue_id];
+ else if (PRUETH_IS_SWITCH(prueth))
txqueue = &sw_queue_infos[txport][queue_id];
else
txqueue = &queue_infos[txport][queue_id];
@@ -602,6 +661,29 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
free_blocks = buffer_desc_count;
}
+ /* Fetch queue state for the second LRE port */
+ if (prueth_is_lre(prueth) && link_up) {
+ queue_desc_other_port = emac->tx_queue_descs_other_port +
+ queue_id;
+ bd_rd_ptr_other_port = readw(&queue_desc_other_port->rd_ptr);
+
+ read_block_other_port = (bd_rd_ptr_other_port -
+ txqueue->buffer_desc_offset) / BD_SIZE;
+
+ if (write_block > read_block_other_port) {
+ free_blocks_other_port = buffer_desc_count -
+ write_block;
+ free_blocks_other_port += read_block_other_port;
+ } else if (write_block < read_block_other_port) {
+ free_blocks_other_port = read_block_other_port -
+ write_block;
+ } else {
+ free_blocks_other_port = buffer_desc_count;
+ }
+
+ if (free_blocks_other_port < free_blocks)
+ free_blocks = free_blocks_other_port;
+ }
pkt_block_size = DIV_ROUND_UP(pktlen, ICSS_BLOCK_SIZE);
if (pkt_block_size > free_blocks) /* out of queue space */
return -ENOBUFS;
@@ -651,6 +733,57 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
if (PRUETH_IS_HSR(prueth))
wr_buf_desc |= BIT(PRUETH_BD_HSR_FRAME_SHIFT);
+ if (prueth_is_lre(prueth)) {
+ ethhdr = (struct ethhdr *)skb_mac_header(skb);
+ proto = ethhdr->h_proto;
+
+ if (proto == htons(ETH_P_8021Q)) {
+ vlan_hdr = (struct vlan_ethhdr *)ethhdr;
+ proto = vlan_hdr->h_vlan_encapsulated_proto;
+ is_vlan = true;
+ }
+
+ /* Extract HSR sequence number and LAN ID
+ * from the tag for the Buffer Descriptor
+ */
+ if (proto == htons(ETH_P_HSR)) {
+ hdr = skb_mac_header(skb);
+
+ if (is_vlan) {
+ hsr_ethhdr =
+ (struct hsr_txopt_ethhdr *)(hdr +
+ VLAN_HLEN);
+ } else {
+ hsr_ethhdr = (struct hsr_txopt_ethhdr *)hdr;
+ }
+
+ /* PTP frames (ETH_P_1588) carry no LAN ID
+ * in the HSR tag
+ */
+ if (hsr_ethhdr->hsr_tag.encap_proto !=
+ htons(ETH_P_1588)) {
+ wr_buf_desc |= PRUETH_BD_LAN_INFO_MASK;
+ } else {
+ wr_buf_desc |= (txport <<
+ PRUETH_BD_LAN_A_SHIFT);
+ }
+ wr_buf_desc |= PRUETH_BD_RED_PKT_MASK;
+ } else {
+ /* Read PRP RCT to extract sequence number and LAN ID */
+ struct prp_txopt_rct *rct =
+ (struct prp_txopt_rct *)(skb_tail_pointer(skb) -
+ ICSSM_LRE_TAG_SIZE);
+
+ if (rct->prp_suffix == htons(ETH_P_PRP)) {
+ wr_buf_desc |= PRUETH_BD_LAN_INFO_MASK;
+ wr_buf_desc |= PRUETH_BD_RED_PKT_MASK;
+ } else {
+ wr_buf_desc |= (txport <<
+ PRUETH_BD_LAN_A_SHIFT);
+ }
+ }
+ }
+
sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va;
if (!PRUETH_IS_EMAC(prueth))
writel(wr_buf_desc, sram + readw(&queue_desc->wr_ptr));
@@ -663,6 +796,10 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
update_wr_ptr = txqueue->buffer_desc_offset + (update_block * BD_SIZE);
writew(update_wr_ptr, &queue_desc->wr_ptr);
+ /* update the write pointer in queue descriptor of other port */
+ if (prueth_is_lre(prueth) && link_up)
+ writew(update_wr_ptr, &queue_desc_other_port->wr_ptr);
+
return 0;
}
@@ -675,8 +812,10 @@ void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor,
else
pkt_info->start_offset = false;
- pkt_info->port = (buffer_descriptor & PRUETH_BD_PORT_MASK) >>
- PRUETH_BD_PORT_SHIFT;
+ /* Flag from BD to indicate packet is valid for HOST or not. */
+ pkt_info->host_recv_flag = !!(buffer_descriptor &
+ PRUETH_BD_HOST_RECV_MASK);
+
pkt_info->length = (buffer_descriptor & PRUETH_BD_LENGTH_MASK) >>
PRUETH_BD_LENGTH_SHIFT;
pkt_info->broadcast = !!(buffer_descriptor & PRUETH_BD_BROADCAST_MASK);
@@ -708,11 +847,13 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
int read_block, update_block;
unsigned int actual_pkt_len;
bool buffer_wrapped = false;
+ int adjust_for_hsr_tag = 0;
void *src_addr, *dst_addr;
u16 start_offset = 0;
struct sk_buff *skb;
int pkt_block_size;
void *ocmc_ram;
+ u16 type;
if (PRUETH_IS_HSR(emac->prueth))
start_offset = (pkt_info->start_offset ?
@@ -739,9 +880,19 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
/* calculate new pointer in ram */
*bd_rd_ptr = rxqueue->buffer_desc_offset + (update_block * BD_SIZE);
+ if (PRUETH_IS_HSR(emac->prueth)) {
+ if (!pkt_info->host_recv_flag)
+ return 0;
+ }
+
/* Exclude the HSR tag bytes already stripped by firmware, if any. */
actual_pkt_len = pkt_info->length - start_offset;
+ if (PRUETH_IS_HSR(emac->prueth)) {
+ if (!start_offset && !pkt_info->timestamp)
+ actual_pkt_len -= ICSSM_LRE_TAG_SIZE;
+ }
+
/* Allocate a socket buffer for this packet */
skb = netdev_alloc_skb_ip_align(ndev, actual_pkt_len);
if (!skb) {
@@ -762,6 +913,29 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
(read_block * ICSS_BLOCK_SIZE);
src_addr += start_offset;
+ /* Copy destination and source MAC address */
+ memcpy(dst_addr, src_addr, PRUETH_ETHER_TYPE_OFFSET);
+ src_addr += PRUETH_ETHER_TYPE_OFFSET;
+ dst_addr += PRUETH_ETHER_TYPE_OFFSET;
+
+ adjust_for_hsr_tag += PRUETH_ETHER_TYPE_OFFSET;
+
+ /* Check for VLAN tag */
+ type = get_unaligned_be16(src_addr);
+
+ if (type == ETH_P_8021Q) {
+ memcpy(dst_addr, src_addr, VLAN_HLEN);
+ src_addr += VLAN_HLEN;
+ dst_addr += VLAN_HLEN;
+ adjust_for_hsr_tag += VLAN_HLEN;
+ }
+
+ /* HSR tag removal handling */
+ if (PRUETH_IS_HSR(emac->prueth)) {
+ if (!start_offset && !pkt_info->timestamp)
+ src_addr += ICSSM_LRE_TAG_SIZE;
+ }
+
/* Copy the data from PRU buffers(OCMC) to socket buffer(DRAM) */
if (buffer_wrapped) { /* wrapped around buffer */
int bytes = (buffer_desc_count - read_block) * ICSS_BLOCK_SIZE;
@@ -777,19 +951,25 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
/* If applicable, account for the HSR tag removed */
bytes -= start_offset;
+ if (PRUETH_IS_HSR(emac->prueth)) {
+ if (!start_offset && !pkt_info->timestamp)
+ bytes -= ICSSM_LRE_TAG_SIZE;
+ }
+
/* copy non-wrapped part */
- memcpy(dst_addr, src_addr, bytes);
+ memcpy(dst_addr, src_addr, bytes - adjust_for_hsr_tag);
/* copy wrapped part */
- dst_addr += bytes;
+ dst_addr += (bytes - adjust_for_hsr_tag);
remaining = actual_pkt_len - bytes;
src_addr = ocmc_ram + rxqueue->buffer_offset;
memcpy(dst_addr, src_addr, remaining);
src_addr += remaining;
} else {
- memcpy(dst_addr, src_addr, actual_pkt_len);
- src_addr += actual_pkt_len;
+ memcpy(dst_addr, src_addr, actual_pkt_len -
+ adjust_for_hsr_tag);
+ src_addr += actual_pkt_len - adjust_for_hsr_tag;
}
if (PRUETH_IS_SWITCH(emac->prueth)) {
@@ -1313,11 +1493,20 @@ static enum netdev_tx icssm_emac_ndo_start_xmit(struct sk_buff *skb,
struct net_device *ndev)
{
struct prueth_emac *emac = netdev_priv(ndev);
+ raw_spinlock_t *lock_queue;
int ret;
u16 qid;
qid = icssm_prueth_get_tx_queue_id(emac->prueth, skb);
+ /* Select the TX queue spin lock for this queue ID */
+ if (prueth_is_lre(emac->prueth))
+ lock_queue = &emac->prueth->lre_host_queue_lock[qid - 2];
+ else
+ lock_queue = &emac->host_queue_lock[qid - 2];
+
+ raw_spin_lock(lock_queue);
ret = icssm_prueth_tx_enqueue(emac, skb, qid);
+ raw_spin_unlock(lock_queue);
if (ret) {
if (ret != -ENOBUFS && netif_msg_tx_err(emac) &&
net_ratelimit())
@@ -1682,6 +1871,9 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,
spin_lock_init(&emac->lock);
spin_lock_init(&emac->addr_lock);
+ raw_spin_lock_init(&emac->host_queue_lock[0]);
+ raw_spin_lock_init(&emac->host_queue_lock[1]);
+
/* get mac address from DT and set private and netdev addr */
ret = of_get_ethdev_address(eth_node, ndev);
if (!is_valid_ether_addr(ndev->dev_addr)) {
@@ -1719,7 +1911,9 @@ static int icssm_prueth_netdev_init(struct prueth *prueth,
if (prueth->support_lre)
ndev->hw_features |=
- (NETIF_F_HW_HSR_FWD | NETIF_F_HW_HSR_TAG_RM);
+ (NETIF_F_HW_HSR_FWD |
+ NETIF_F_HW_HSR_TAG_RM |
+ NETIF_F_HW_HSR_DUP);
ndev->dev.of_node = eth_node;
ndev->netdev_ops = &emac_netdev_ops;
@@ -1947,9 +2141,11 @@ static int icssm_prueth_hsr_port_link(struct net_device *ndev,
if (prueth->hsr_members & BIT(PRUETH_PORT_MII0) &&
prueth->hsr_members & BIT(PRUETH_PORT_MII1)) {
if (!(emac0->ndev->features &
- (NETIF_F_HW_HSR_TAG_RM | NETIF_F_HW_HSR_FWD)) &&
+ (NETIF_F_HW_HSR_TAG_RM | NETIF_F_HW_HSR_FWD |
+ NETIF_F_HW_HSR_DUP)) &&
!(emac1->ndev->features &
- (NETIF_F_HW_HSR_TAG_RM | NETIF_F_HW_HSR_FWD)))
+ (NETIF_F_HW_HSR_TAG_RM | NETIF_F_HW_HSR_FWD |
+ NETIF_F_HW_HSR_DUP)))
return -EOPNOTSUPP;
ret = icssm_prueth_change_mode(prueth, mode);
@@ -2275,6 +2471,8 @@ static int icssm_prueth_probe(struct platform_device *pdev)
}
prueth->support_lre = has_lre;
+ raw_spin_lock_init(&prueth->lre_host_queue_lock[0]);
+ raw_spin_lock_init(&prueth->lre_host_queue_lock[1]);
/* setup netdev interfaces */
if (eth0_node) {
ret = icssm_prueth_netdev_init(prueth, eth0_node);
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.h b/drivers/net/ethernet/ti/icssm/icssm_prueth.h
index 60dc451f79e5..b7f6919fbd45 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth.h
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.h
@@ -104,7 +104,7 @@ struct prueth_queue_info {
struct prueth_packet_info {
bool start_offset;
bool shadow;
- unsigned int port;
+ bool host_recv_flag;
unsigned int length;
bool broadcast;
bool error;
@@ -239,6 +239,8 @@ struct prueth_emac {
struct phy_device *phydev;
struct prueth_queue_desc __iomem *rx_queue_descs;
struct prueth_queue_desc __iomem *tx_queue_descs;
+ /* LRE duplicates each TX frame to both ports */
+ struct prueth_queue_desc __iomem *tx_queue_descs_other_port;
int link;
int speed;
@@ -262,6 +264,7 @@ struct prueth_emac {
spinlock_t lock;
spinlock_t addr_lock; /* serialize access to VLAN/MC filter table */
+ raw_spinlock_t host_queue_lock[NUM_QUEUES / 2];
struct hrtimer tx_hrtimer;
struct prueth_emac_stats stats;
int offload_fwd_mark;
@@ -311,9 +314,13 @@ struct prueth {
u8 emac_configured;
u8 hsr_members;
u8 br_members;
+
+ /* Per-queue TX lock - LRE uses only the two high-priority queues */
+ raw_spinlock_t lre_host_queue_lock[NUM_QUEUES / 2];
};
extern const struct prueth_queue_desc queue_descs[][NUM_QUEUES];
+extern const struct prueth_queue_desc hsr_prp_txopt_queue_descs[][NUM_QUEUES];
void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor,
struct prueth_packet_info *pkt_info);
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
index 4820def5f9e1..27f94a0e0735 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
@@ -141,16 +141,13 @@ static int icssm_prueth_common_emac_rx_packets(struct prueth_emac *emac,
used++;
}
- /* Zero the BD after consuming it, a misaligned rd_ptr
- * would otherwise mistake stale data for a valid incoming
- * frame.
+ /* Leave the BD intact after reading. Firmware reuses it to
+ * forward the frame to the second LRE port.
*/
if (port == 0) {
- writel(0, shared_ram + bd_rd_ptr);
writew(update_rd_ptr, &queue_desc->rd_ptr);
bd_rd_ptr = update_rd_ptr;
} else {
- writel(0, shared_ram + bd_rd_ptr_o);
writew(update_rd_ptr, &queue_desc_o->rd_ptr);
bd_rd_ptr_o = update_rd_ptr;
}
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c
index 66866ea37913..1b2486170ab3 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c
@@ -199,6 +199,189 @@ static const struct prueth_queue_info rx_queue_infos[][NUM_QUEUES] = {
},
};
+/* Tx Queue context for HSR and PRP */
+const struct prueth_queue_info lre_queue_infos[][NUM_QUEUES] = {
+ [PRUETH_PORT_QUEUE_HOST] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q1_BUFFER_OFFSET,
+ P0_QUEUE_DESC_OFFSET,
+ P0_Q1_BD_OFFSET,
+ P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q2_BUFFER_OFFSET,
+ P0_QUEUE_DESC_OFFSET + 8,
+ P0_Q2_BD_OFFSET,
+ P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P0_Q3_BUFFER_OFFSET,
+ P0_QUEUE_DESC_OFFSET + 16,
+ P0_Q3_BD_OFFSET,
+ P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P0_Q4_BUFFER_OFFSET,
+ P0_QUEUE_DESC_OFFSET + 24,
+ P0_Q4_BD_OFFSET,
+ P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
+ },
+ },
+ [PRUETH_PORT_QUEUE_MII0] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q3_BUFFER_OFFSET,
+ P0_Q3_BUFFER_OFFSET +
+ ((HOST_QUEUE_3_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P0_Q3_BD_OFFSET,
+ P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q4_BUFFER_OFFSET,
+ P0_Q4_BUFFER_OFFSET +
+ ((HOST_QUEUE_4_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P0_Q4_BD_OFFSET,
+ P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P1_Q3_TXOPT_BUFFER_OFFSET,
+ P1_Q3_TXOPT_BUFFER_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P1_Q3_TXOPT_BD_OFFSET,
+ P1_Q3_TXOPT_BD_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P2_Q1_TXOPT_BUFFER_OFFSET,
+ P2_Q1_TXOPT_BUFFER_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P2_Q1_TXOPT_BD_OFFSET,
+ P2_Q1_TXOPT_BD_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ },
+ [PRUETH_PORT_QUEUE_MII1] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q1_BUFFER_OFFSET,
+ P0_Q1_BUFFER_OFFSET +
+ ((HOST_QUEUE_1_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P0_Q1_BD_OFFSET,
+ P0_Q1_BD_OFFSET +
+ ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q2_BUFFER_OFFSET,
+ P0_Q2_BUFFER_OFFSET +
+ ((HOST_QUEUE_2_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P0_Q2_BD_OFFSET,
+ P0_Q2_BD_OFFSET +
+ ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P1_Q3_TXOPT_BUFFER_OFFSET,
+ P1_Q3_TXOPT_BUFFER_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P1_Q3_TXOPT_BD_OFFSET,
+ P1_Q3_TXOPT_BD_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P2_Q1_TXOPT_BUFFER_OFFSET,
+ P2_Q1_TXOPT_BUFFER_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * ICSS_BLOCK_SIZE),
+ P2_Q1_TXOPT_BD_OFFSET,
+ P2_Q1_TXOPT_BD_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+
+ },
+};
+
+/* Rx Queue Context for HSR and PRP */
+static const struct prueth_queue_info lre_rx_queue_infos[][NUM_QUEUES] = {
+ [PRUETH_PORT_QUEUE_HOST] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q1_BUFFER_OFFSET,
+ HOST_QUEUE_DESC_OFFSET,
+ P0_Q1_BD_OFFSET,
+ P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q2_BUFFER_OFFSET,
+ HOST_QUEUE_DESC_OFFSET + 8,
+ P0_Q2_BD_OFFSET,
+ P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P0_Q3_BUFFER_OFFSET,
+ HOST_QUEUE_DESC_OFFSET + 16,
+ P0_Q3_BD_OFFSET,
+ P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P0_Q4_BUFFER_OFFSET,
+ HOST_QUEUE_DESC_OFFSET + 24,
+ P0_Q4_BD_OFFSET,
+ P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
+ },
+ },
+ [PRUETH_PORT_QUEUE_MII0] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q3_BUFFER_OFFSET,
+ P1_QUEUE_DESC_OFFSET,
+ P0_Q3_BD_OFFSET,
+ P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q4_BUFFER_OFFSET,
+ P1_QUEUE_DESC_OFFSET + 8,
+ P0_Q4_BD_OFFSET,
+ P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P1_Q3_TXOPT_BUFFER_OFFSET,
+ P1_QUEUE_DESC_OFFSET + 16,
+ P1_Q3_TXOPT_BD_OFFSET,
+ P1_Q3_TXOPT_BD_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P2_Q1_TXOPT_BUFFER_OFFSET,
+ P1_QUEUE_DESC_OFFSET + 24,
+ P2_Q1_TXOPT_BD_OFFSET,
+ P2_Q1_TXOPT_BD_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ },
+ [PRUETH_PORT_QUEUE_MII1] = {
+ [PRUETH_QUEUE1] = {
+ P0_Q1_BUFFER_OFFSET,
+ P2_QUEUE_DESC_OFFSET,
+ P0_Q1_BD_OFFSET,
+ P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE2] = {
+ P0_Q2_BUFFER_OFFSET,
+ P2_QUEUE_DESC_OFFSET + 8,
+ P0_Q2_BD_OFFSET,
+ P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE3] = {
+ P1_Q3_TXOPT_BUFFER_OFFSET,
+ P2_QUEUE_DESC_OFFSET + 16,
+ P1_Q3_TXOPT_BD_OFFSET,
+ P1_Q3_TXOPT_BD_OFFSET +
+ ((QUEUE_3_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ [PRUETH_QUEUE4] = {
+ P2_Q1_TXOPT_BUFFER_OFFSET,
+ P2_QUEUE_DESC_OFFSET + 24,
+ P2_Q1_TXOPT_BD_OFFSET,
+ P2_Q1_TXOPT_BD_OFFSET +
+ ((QUEUE_4_TXOPT_SIZE - 1) * BD_SIZE),
+ },
+ },
+};
+
void icssm_prueth_sw_free_fdb_table(struct prueth *prueth)
{
if (prueth->emac_configured)
@@ -856,8 +1039,12 @@ void icssm_prueth_sw_hostconfig(struct prueth *prueth)
/* queue information table */
dram = dram1_base + P0_Q1_RX_CONTEXT_OFFSET;
- memcpy_toio(dram, sw_queue_infos[PRUETH_PORT_QUEUE_HOST],
- sizeof(sw_queue_infos[PRUETH_PORT_QUEUE_HOST]));
+ if (prueth_is_lre(prueth))
+ memcpy_toio(dram, lre_queue_infos[PRUETH_PORT_QUEUE_HOST],
+ sizeof(lre_queue_infos[PRUETH_PORT_QUEUE_HOST]));
+ else
+ memcpy_toio(dram, sw_queue_infos[PRUETH_PORT_QUEUE_HOST],
+ sizeof(sw_queue_infos[PRUETH_PORT_QUEUE_HOST]));
/* buffer descriptor offset table*/
dram = dram1_base + QUEUE_DESCRIPTOR_OFFSET_ADDR;
@@ -882,8 +1069,15 @@ void icssm_prueth_sw_hostconfig(struct prueth *prueth)
/* queue table */
dram = dram1_base + P0_QUEUE_DESC_OFFSET;
- memcpy_toio(dram, queue_descs[PRUETH_PORT_QUEUE_HOST],
- sizeof(queue_descs[PRUETH_PORT_QUEUE_HOST]));
+ if (prueth_is_lre(prueth))
+ memcpy_toio(dram,
+ hsr_prp_txopt_queue_descs[PRUETH_PORT_QUEUE_HOST],
+ sizeof(hsr_prp_txopt_queue_descs
+ [PRUETH_PORT_QUEUE_HOST]));
+ else
+ memcpy_toio(dram, queue_descs[PRUETH_PORT_QUEUE_HOST],
+ sizeof(queue_descs[PRUETH_PORT_QUEUE_HOST]));
+
}
static int icssm_prueth_sw_port_config(struct prueth *prueth,
@@ -975,6 +1169,109 @@ static int icssm_prueth_sw_port_config(struct prueth *prueth,
return 0;
}
+/* Configure TX/RX queue contexts and buffer descriptor tables for LRE port */
+static int icssm_prueth_lre_port_config(struct prueth *prueth,
+ enum prueth_port port_id)
+{
+ unsigned int tx_context_ofs_addr, rx_context_ofs, queue_desc_ofs;
+ void __iomem *dram, *dram_base, *dram_mac;
+ struct prueth_emac *emac;
+
+ emac = prueth->emac[port_id - 1];
+ switch (port_id) {
+ case PRUETH_PORT_MII0:
+ tx_context_ofs_addr = TX_CONTEXT_P1_Q1_OFFSET_ADDR;
+ rx_context_ofs = P1_Q1_RX_CONTEXT_OFFSET;
+ queue_desc_ofs = P1_QUEUE_DESC_OFFSET;
+ /* for switch PORT MII0 mac addr is in DRAM0. */
+ dram_mac = prueth->mem[PRUETH_MEM_DRAM0].va;
+ break;
+ case PRUETH_PORT_MII1:
+ tx_context_ofs_addr = TX_CONTEXT_P2_Q1_OFFSET_ADDR;
+ rx_context_ofs = P2_Q1_RX_CONTEXT_OFFSET;
+ queue_desc_ofs = P2_QUEUE_DESC_OFFSET;
+
+ /* for switch PORT MII1 mac addr is in DRAM1. */
+ dram_mac = prueth->mem[PRUETH_MEM_DRAM1].va;
+ break;
+ default:
+ netdev_err(emac->ndev, "invalid port\n");
+ return -EINVAL;
+ }
+
+ /* setup mac address */
+ memcpy_toio(dram_mac + PORT_MAC_ADDR, emac->mac_addr, ETH_ALEN);
+
+ /* Remaining switch port configs are in DRAM1 */
+ dram_base = prueth->mem[PRUETH_MEM_DRAM1].va;
+
+ /* queue information table */
+ memcpy_toio(dram_base + tx_context_ofs_addr,
+ lre_queue_infos[port_id],
+ sizeof(lre_queue_infos[port_id]));
+
+ memcpy_toio(dram_base + rx_context_ofs,
+ lre_rx_queue_infos[port_id],
+ sizeof(lre_rx_queue_infos[port_id]));
+
+ /* buffer descriptor offset table*/
+ dram = dram_base + QUEUE_DESCRIPTOR_OFFSET_ADDR +
+ (port_id * NUM_QUEUES * sizeof(u16));
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE1].buffer_desc_offset,
+ dram);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE2].buffer_desc_offset,
+ dram + 2);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE3].buffer_desc_offset,
+ dram + 4);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE4].buffer_desc_offset,
+ dram + 6);
+
+ /* buffer offset table */
+ dram = dram_base + QUEUE_OFFSET_ADDR +
+ port_id * NUM_QUEUES * sizeof(u16);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE1].buffer_offset, dram);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE2].buffer_offset,
+ dram + 2);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE3].buffer_offset,
+ dram + 4);
+ writew(lre_queue_infos[port_id][PRUETH_QUEUE4].buffer_offset,
+ dram + 6);
+
+ /* queue size lookup table */
+ dram = dram_base + QUEUE_SIZE_ADDR +
+ port_id * NUM_QUEUES * sizeof(u16);
+ writew(HOST_QUEUE_1_SIZE, dram);
+ writew(HOST_QUEUE_2_SIZE, dram + 2);
+ writew(QUEUE_3_TXOPT_SIZE, dram + 4);
+ writew(QUEUE_4_TXOPT_SIZE, dram + 6);
+
+ /* queue table */
+ memcpy_toio(dram_base + queue_desc_ofs,
+ &hsr_prp_txopt_queue_descs[port_id][0],
+ 4 * sizeof(hsr_prp_txopt_queue_descs[port_id][0]));
+
+ /* In HSR/PRP mode both slave ports share the host receive queue
+ * descriptor region (P0_QUEUE_DESC_OFFSET). The firmware arbitrates
+ * ownership; the driver always reads from the same host-side descriptor
+ * base regardless of which physical port the frame arrived on.
+ */
+ emac->rx_queue_descs = dram_base + P0_QUEUE_DESC_OFFSET;
+ emac->tx_queue_descs = dram_base +
+ lre_rx_queue_infos[port_id][PRUETH_QUEUE1].queue_desc_offset;
+
+ if (port_id == PRUETH_PORT_MII0) {
+ emac->tx_queue_descs_other_port = dram_base +
+ lre_rx_queue_infos
+ [port_id + 1][PRUETH_QUEUE1].queue_desc_offset;
+ } else if (port_id == PRUETH_PORT_MII1) {
+ emac->tx_queue_descs_other_port = dram_base +
+ lre_rx_queue_infos
+ [port_id - 1][PRUETH_QUEUE1].queue_desc_offset;
+ }
+
+ return 0;
+}
+
int icssm_prueth_sw_emac_config(struct prueth_emac *emac)
{
struct prueth *prueth = emac->prueth;
@@ -989,7 +1286,10 @@ int icssm_prueth_sw_emac_config(struct prueth_emac *emac)
if (prueth->emac_configured & BIT(emac->port_id))
return 0;
- ret = icssm_prueth_sw_port_config(prueth, emac->port_id);
+ if (prueth_is_lre(prueth))
+ ret = icssm_prueth_lre_port_config(prueth, emac->port_id);
+ else
+ ret = icssm_prueth_sw_port_config(prueth, emac->port_id);
if (ret)
return ret;
diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h
index e6111bba166e..0f4595c6075f 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h
+++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h
@@ -17,6 +17,7 @@ u8 icssm_prueth_sw_get_stp_state(struct prueth *prueth,
enum prueth_port port);
extern const struct prueth_queue_info sw_queue_infos[][4];
+extern const struct prueth_queue_info lre_queue_infos[][4];
void icssm_prueth_sw_fdb_tbl_init(struct prueth *prueth);
int icssm_prueth_sw_init_fdb_table(struct prueth *prueth);
diff --git a/drivers/net/ethernet/ti/icssm/icssm_switch.h b/drivers/net/ethernet/ti/icssm/icssm_switch.h
index 5ba9ce14da44..089e43cadc25 100644
--- a/drivers/net/ethernet/ti/icssm/icssm_switch.h
+++ b/drivers/net/ethernet/ti/icssm/icssm_switch.h
@@ -24,6 +24,9 @@
#define QUEUE_3_SIZE 97 /* Protocol specific */
#define QUEUE_4_SIZE 97 /* NRT (IP,ARP, ICMP) */
+#define QUEUE_3_TXOPT_SIZE 194 /* Protocol specific - High Priority */
+#define QUEUE_4_TXOPT_SIZE 194 /* NRT(IP,ARP, ICMP) - Low Priority*/
+
/* Host queue size (number of BDs). Each BD points to data buffer of 32 bytes.
* HOST PORT QUEUES can buffer up to 4 full sized frames per queue
*/
@@ -49,20 +52,18 @@
* For RED, NodeTable lookup was successful.
* 7 Flood Packet should be flooded (destination MAC
* address found in FDB). For switch only.
- * 8..12 Block_length number of valid bytes in this specific block.
- * Will be <=32 bytes on last block of packet
+ * 8 RED_INFO Set if the frame carries an HSR or PRP
+ * redundancy tag
+ * 10 HostRecv Set if the frame is destined for the host port
* 13 More "More" bit indicating that there are more blocks
* 14 Shadow indicates that "index" is pointing into shadow
* buffer
* 15 TimeStamp indicates that this packet has time stamp in
* separate buffer - only needed if PTP runs on
* host
- * 16..17 Port different meaning for ingress and egress,
- * Ingress: Port = 0 indicates phy port 1 and
- * Port = 1 indicates phy port 2.
- * Egress: 0 sends on phy port 1 and 1 sends on
- * phy port 2. Port = 2 goes over MAC table
- * look-up
+ * 16..17 LAN Destination LAN for transmission:
+ * bit 16 = LAN A, bit 17 = LAN B, set both to
+ * duplicate to both LANs.
* 18..28 Length 11 bit of total packet length which is put into
* first BD only so that host access only one BD
* 29 VlanTag indicates that packet has Length/Type field of
@@ -86,14 +87,21 @@
#define PRUETH_BD_SW_FLOOD_MASK BIT(7)
#define PRUETH_BD_SW_FLOOD_SHIFT 7
+#define PRUETH_BD_RED_PKT_MASK BIT(8)
+#define PRUETH_BD_RED_PKT 8
+
+#define PRUETH_BD_HOST_RECV_MASK BIT(10)
+#define PRUETH_BD_HOST_RECV_SHIFT 10
+
#define PRUETH_BD_SHADOW_MASK BIT(14)
#define PRUETH_BD_SHADOW_SHIFT 14
#define PRUETH_BD_TIMESTAMP_MASK BIT(15)
#define PRUETH_BD_TIMESTAMP_SHIFT 15
-#define PRUETH_BD_PORT_MASK GENMASK(17, 16)
-#define PRUETH_BD_PORT_SHIFT 16
+#define PRUETH_BD_LAN_INFO_MASK GENMASK(17, 16)
+#define PRUETH_BD_LAN_A_SHIFT 16
+#define PRUETH_BD_LAN_B_SHIFT 17
#define PRUETH_BD_LENGTH_MASK GENMASK(28, 18)
#define PRUETH_BD_LENGTH_SHIFT 18
@@ -298,6 +306,9 @@
#define P0_Q4_BD_OFFSET (P0_Q3_BD_OFFSET + HOST_QUEUE_3_SIZE * BD_SIZE)
#define P0_Q3_BD_OFFSET (P0_Q2_BD_OFFSET + HOST_QUEUE_2_SIZE * BD_SIZE)
#define P0_Q2_BD_OFFSET (P0_Q1_BD_OFFSET + HOST_QUEUE_1_SIZE * BD_SIZE)
+#define P1_Q3_TXOPT_BD_OFFSET (P0_Q4_BD_OFFSET + HOST_QUEUE_4_SIZE * BD_SIZE)
+#define P2_Q1_TXOPT_BD_OFFSET (P1_Q3_TXOPT_BD_OFFSET + \
+ QUEUE_3_TXOPT_SIZE * BD_SIZE)
#define P0_Q1_BD_OFFSET P0_BUFFER_DESC_OFFSET
#define P0_BUFFER_DESC_OFFSET SRAM_START_OFFSET
@@ -328,6 +339,10 @@
ICSS_BLOCK_SIZE)
#define P0_Q2_BUFFER_OFFSET (P0_Q1_BUFFER_OFFSET + HOST_QUEUE_1_SIZE * \
ICSS_BLOCK_SIZE)
+#define P1_Q3_TXOPT_BUFFER_OFFSET (P0_Q4_BUFFER_OFFSET + \
+ HOST_QUEUE_4_SIZE * ICSS_BLOCK_SIZE)
+#define P2_Q1_TXOPT_BUFFER_OFFSET (P1_Q3_TXOPT_BUFFER_OFFSET + \
+ QUEUE_3_TXOPT_SIZE * ICSS_BLOCK_SIZE)
#define P0_COL_BUFFER_OFFSET 0xEE00
#define P0_Q1_BUFFER_OFFSET 0x0000
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 0/3] Introduce HSR/PRP HW offload support for PRU-ICSSM Ethernet driver
From: Parvathi Pudi @ 2026-06-11 12:33 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
parvathi, rogerq, pmohan, afd, basharath, arnd
Cc: linux-kernel, netdev, linux-arm-kernel, pratheesh, j-rameshbabu,
vigneshr, praneeth, srk, rogerq, m-malladi, krishna, mohan
Hi,
This series introduces HSR and PRP protocol HW offload support for ICSSM-Prueth driver.
HW offload support for HSR/PRP is implemented using dedicated HSR/PRP firmware running
on 2 PRU cores(PRU-ICSS) as a "DAN" available in AM57xx, AM437x and AM335x.
The following features are offloaded to HW in case of HSR and PRP:
1. L2 forwarding of a HSR frame via traditional store and forward or via cut-through (only for HSR)
2. Transmit frame duplication is offloaded to HW
3. Tag removal on the receive is offloaded to HW
4. Redundant duplicate packet discard on the receive is also offloaded to HW
In HW offload mode, redundant tag insertion in the transmit path will be still done by HSR driver
and firmware updates the LAN information available in the tag on the fly when PRU is transmitting
frame in that respective LAN.
HSR Test Setup:
--------------
___________ ______________ ___________
| | Link AB | | Link BC | |
__| AM57* |_________|AM57/AM43/AM33|_________| AM57* |___
| | Station A | | Station B | | Station C | |
| |___________| |______________| |___________| |
| |
|_________________________________________________________________|
Link CA
Steps to switch to HSR forward offload mode:
-------------------------------------------------
Example assuming eth1, eth2 ports of ICSSM on AM57x, AM437x and AM335x EVM's
1) Bring down both slave interfaces
ip link set eth1 down
ip link set eth2 down
2) Set matching MAC addresses on both slave interfaces
ip link set eth1 address <mac-addr>
ip link set eth2 address <mac-addr>
3) Enable HSR offload for both interfaces
ethtool -K eth1 hsr-fwd-offload on
ethtool -K eth1 hsr-dup-offload on
ethtool -K eth1 hsr-tag-rm-offload on
ethtool -K eth2 hsr-fwd-offload on
ethtool -K eth2 hsr-dup-offload on
ethtool -K eth2 hsr-tag-rm-offload on
4) Create HSR interface and add slave interfaces to it
ip link add name hsr0 type hsr slave1 eth1 slave2 eth2 \
supervision 45 version 1
5) Add IP address to the HSR interface
ip addr add <IP_ADDR>/24 dev hsr0
6) Bring up the HSR interface
ip link set hsr0 up
7) Bring up the both slave ports
ip link set eth1 up
ip link set eth2 up
Switching back to default mode:
--------------------------------
1) Delete HSR interface
ip link delete hsr0
2) Disable HSR port-to-port offloading mode, packet duplication
ethtool -K eth1 hsr-fwd-offload off
ethtool -K eth1 hsr-dup-offload off
ethtool -K eth1 hsr-tag-rm-offload off
ethtool -K eth2 hsr-fwd-offload off
ethtool -K eth2 hsr-dup-offload off
ethtool -K eth2 hsr-tag-rm-offload off
Testing the port-to-port frame forward offload feature:
-------------------------------------------------------
1) Connect the LAN cables as shown in the test setup.
2) Configure Station A and Station C in HSR non-offload mode.
3) Configure Station B is HSR offload mode.
4) Since HSR is a redundancy protocol, disconnect cable "Link CA",
to ensure frames from Station A reach Station C only through
Station B.
5) Run iperf3 Server on Station C and client on station A.
7) Check the CPU usage on Station B.
CPU usage report on Station B using mpstat when running UDP iperf3:
-------------------------------------------------------------------
AM57xx
------
1) Non-Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.00 0.00 0.00 0.00 0.00 10.41 0.00 0.00 89.59
0 0.00 0.00 0.00 0.00 0.00 20.88 0.00 0.00 79.12
1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.00 0.00 0.10 0.00 0.00 0.73 0.00 0.00 99.17
0 0.00 0.00 0.20 0.00 0.00 1.46 0.00 0.00 98.34
1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
AM437x
------
1) Non-Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.30 0.00 0.80 0.00 0.00 35.19 0.00 0.00 63.72
0 0.30 0.00 0.80 0.00 0.00 35.19 0.00 0.00 63.72
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.10 0.00 0.31 0.10 0.00 1.74 0.00 0.00 97.75
0 0.10 0.00 0.31 0.10 0.00 1.74 0.00 0.00 97.75
AM335x
------
1) Non Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.30 0.00 1.10 0.00 0.00 90.32 0.00 0.00 8.28
0 0.30 0.00 1.10 0.00 0.00 90.32 0.00 0.00 8.28
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 0.43 0.00 3.61 0.00 0.00 13.28 0.00 0.00 82.68
0 0.43 0.00 3.61 0.00 0.00 13.28 0.00 0.00 82.68
PRP Test Setup:
---------------
_________________ LAN-A __________________
| |eth1-----------eth1| |
| AM57/AM437/AM335| | AM57/AM437/AM335 |
| station A |eth2-----------eth2| station B |
|_________________| LAN-B |__________________|
Steps to switch to PRP offload mode:
------------------------------------
Example assuming eth1, eth2 ports of ICSSM on AM57x, AM437x and AM335x EVM's
1) Bring down both slave interfaces
ip link set eth1 down
ip link set eth2 down
2) Set matching MAC addresses on both slave interfaces
ip link set eth1 address <mac-addr>
ip link set eth2 address <mac-addr>
3) Enable PRP offload for both interfaces
ethtool -K eth1 hsr-dup-offload on
ethtool -K eth1 hsr-tag-rm-offload on
ethtool -K eth2 hsr-dup-offload on
ethtool -K eth2 hsr-tag-rm-offload on
4) Create PRP interface and add slave interfaces to it
ip link add name prp0 type hsr slave1 eth1 slave2 eth2 \
supervision 45 proto 1
5) Add IP address to the PRP interface
ip addr add <IP_ADDR>/24 dev prp0
6) Bring up the PRP interface
ip link set prp0 up
7) Bring up the both slave ports
ip link set eth1 up
ip link set eth2 up
Switching back to default mode:
--------------------------------
1) Delete PRP interface
ip link delete prp0
2) Disable PRP offloading mode
ethtool -K eth1 hsr-dup-offload off
ethtool -K eth1 hsr-tag-rm-offload off
ethtool -K eth2 hsr-dup-offload off
ethtool -K eth2 hsr-tag-rm-offload off
Testing the PRP offload feature:
--------------------------------
1) Connect eth1 of Station A to eth1 of Station B (LAN-A).
Connect eth2 of Station A to eth2 of Station B (LAN-B).
2) Configure Station A in PRP non-offload mode.
3) Configure Station B in PRP offload mode.
4) Run iperf3 Server on Station B and client on Station A.
5) Check the CPU usage on Station B.
6) Disconnect LAN-B cable to verify Station A frames still reach
Station B over LAN-A with no traffic interruption.
7) Reconnect LAN-B and disconnect LAN-A, verify the same.
CPU usage report on Station B using mpstat when running UDP iperf3:
-------------------------------------------------------------------
AM57x
-----
1) Non Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 2.04 0.00 18.85 0.00 0.00 27.83 0.00 0.00 51.27
0 1.80 0.00 21.56 0.00 0.00 54.89 0.00 0.00 21.76
1 2.29 0.00 16.14 0.00 0.00 0.80 0.00 0.00 80.78
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 2.79 0.00 18.36 0.00 0.00 18.16 0.00 0.00 60.68
0 3.89 0.00 22.16 0.00 0.00 36.13 0.00 0.00 37.82
1 1.69 0.00 14.56 0.00 0.00 0.20 0.00 0.00 83.55
AM437x
------
1) Non Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 5.68 0.00 43.27 0.00 0.00 43.57 0.00 0.00 7.48
0 5.68 0.00 43.27 0.00 0.00 43.57 0.00 0.00 7.48
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 6.39 0.00 42.86 0.00 0.00 32.57 0.00 0.00 18.18
0 6.39 0.00 42.86 0.00 0.00 32.57 0.00 0.00 18.18
AM335x
------
1) Non Offload case
-------------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 2.29 0.00 14.04 0.00 0.00 75.50 0.00 0.00 8.17
0 2.29 0.00 14.04 0.00 0.00 75.50 0.00 0.00 8.17
2) Offload case
---------------
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
all 5.70 0.00 48.50 0.00 0.00 29.00 0.00 0.00 16.80
0 5.70 0.00 48.50 0.00 0.00 29.00 0.00 0.00 16.80
Note:
hsr-tag-rm-offload and hsr-dup-offload are tightly coupled in the firmware implementation.
They both need to be enabled / disabled together and hsr-tag-ins-offload is unsupported.
Roger Quadros (3):
net: ti: icssm-prueth: Add HSR and PRP HW offload mode support for
AM57xx, AM437x and AM335x
net: ti: icssm-prueth: Add priority based RX IRQ handlers
net: ti: icssm-prueth: Support duplicate HW offload feature for HSR
and PRP
drivers/net/ethernet/ti/Makefile | 2 +-
.../ethernet/ti/icssm/icssm_lre_firmware.h | 141 ++++
drivers/net/ethernet/ti/icssm/icssm_prueth.c | 604 +++++++++++++++++-
drivers/net/ethernet/ti/icssm/icssm_prueth.h | 68 +-
.../ethernet/ti/icssm/icssm_prueth_common.c | 282 ++++++++
.../net/ethernet/ti/icssm/icssm_prueth_lre.c | 223 +++++++
.../net/ethernet/ti/icssm/icssm_prueth_lre.h | 19 +
.../ethernet/ti/icssm/icssm_prueth_switch.c | 310 ++++++++-
.../ethernet/ti/icssm/icssm_prueth_switch.h | 1 +
drivers/net/ethernet/ti/icssm/icssm_switch.h | 35 +-
10 files changed, 1639 insertions(+), 46 deletions(-)
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_lre_firmware.h
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_common.c
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_lre.c
create mode 100644 drivers/net/ethernet/ti/icssm/icssm_prueth_lre.h
--
2.43.0
^ permalink raw reply
* Re: [PATCH v11 0/6] gpio: siul2-s32g2: add initial GPIO driver
From: Linus Walleij @ 2026-06-11 12:35 UTC (permalink / raw)
To: Khristine Andreea Barbulescu
Cc: Bartosz Golaszewski, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chester Lin, Matthias Brugger, Ghennadi Procopciuc,
Larisa Grigore, Lee Jones, Shawn Guo, Sascha Hauer, Fabio Estevam,
Dong Aisheng, Jacky Bai, Greg Kroah-Hartman, Rafael J. Wysocki,
Srinivas Kandagatla, Alberto Ruiz, Christophe Lizzi, devicetree,
Enric Balletbo, Eric Chanudet, imx, linux-arm-kernel, linux-gpio,
linux-kernel, NXP S32 Linux Team, Pengutronix Kernel Team,
Vincent Guittot
In-Reply-To: <20260610132116.1998140-1-khristineandreea.barbulescu@oss.nxp.com>
On Wed, Jun 10, 2026 at 3:21 PM Khristine Andreea Barbulescu
<khristineandreea.barbulescu@oss.nxp.com> wrote:
> This patch series adds support for basic GPIO
> operations using gpio-regmap.
The series:
Reviewed-by: Linus Walleij <linusw@kernel.org>
This is definitely merge material, I would disregard Sashiko nitpicking at this
point and just merge it. Any additional fixes can be handled in-tree.
Yours,
Linus Walleij
^ permalink raw reply
* Re: [PATCH v2 0/6] phy: rockchip: samsung-hdptx: Clock fixes and API transition cleanups
From: Cristian Ciocaltea @ 2026-06-11 12:34 UTC (permalink / raw)
To: Vinod Koul
Cc: Neil Armstrong, Heiko Stuebner, Algea Cao, Dmitry Baryshkov,
kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <aiqbX8kon8HtiSEl@vaman>
On 6/11/26 2:26 PM, Vinod Koul wrote:
> On 03-06-26, 13:27, Cristian Ciocaltea wrote:
>> On 5/20/26 10:05 PM, Cristian Ciocaltea wrote:
>>> Hi Vinod,
>>>
>>> On 5/11/26 9:21 PM, Cristian Ciocaltea wrote:
>>>> This series provides a set of bug fixes and cleanups for the Rockchip
>>>> Samsung HDPTX PHY driver.
>>>>
>>>> The first part of the series (i.e. PATCH 1 & 2) addresses clock rate
>>>> calculation and synchronization issues. Specifically, it fixes edge
>>>> cases where the PHY PLL is pre-programmed by an external component (like
>>>> a bootloader) or when changing the color depth (bpc) while keeping the
>>>> modeline constant. Because the Common Clock Framework .set_rate()
>>>> callback might not be invoked if the pixel clock remains unchanged, this
>>>> previously led to out-of-sync states between CCF and the actual HDMI PHY
>>>> configuration.
>>>>
>>>> The second part focuses on code cleanups and modernizing the register
>>>> access. Now that dw_hdmi_qp driver has fully switched to using
>>>> phy_configure(), we can drop the deprecated TMDS rate setup workarounds
>>>> and the restrict_rate_change flag logic. Finally, it refactors the
>>>> driver to consistently use standard bitfield macros.
>>>>
>>>> Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
>>>> ---
>>>> Changes in v2:
>>>> - Collected Tested-by tags from Thomas and Simon
>>>> - Fixed a typo in commit description of patch 1
>>>> - Added a comment in patch 2 explaining why PLL config errors are
>>>> ignored for rk_hdptx_phy_consumer_get()
>>>> - Added a missed FIELD_GET conversion for lcpll_hw.pms_sdiv in patch 6
>>>> - Rebased onto latest phy/fixes
>>>> - Link to v1: https://lore.kernel.org/r/20260227-hdptx-clk-fixes-v1-0-f998f2762d0f@collabora.com
>>>
>>> In case you missed my comments from last week on the Sashiko AI review findings
>>> - in short, I don't think there is anything to worry about and the series should
>>> be fine to apply as-is. Please let me know if you would still prefer a new
>>> revision.
>> Kind reminder..
>
> Please post a new revision based on phy/next
Done (also addressed the confirmed Sashiko reported issues):
https://lore.kernel.org/all/20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com/
Thanks,
Cristian
^ permalink raw reply
* [PATCH v3 6/6] phy: rockchip: samsung-hdptx: Consistently use bitfield macros
From: Cristian Ciocaltea @ 2026-06-11 12:31 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com>
Make the code more robust and improve readability by using the available
bitfield macros (e.g. FIELD_PREP, FIELD_GET) whenever possible, instead
of open coding the related bit operations.
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 24 ++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 5ed110e2adc7..a8a8cd176897 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -53,6 +53,12 @@
/* CMN_REG(001e) */
#define LCPLL_PI_EN_MASK BIT(5)
#define LCPLL_100M_CLK_EN_MASK BIT(0)
+/* CMN_REG(0022) */
+#define ANA_LCPLL_PMS_PDIV_MASK GENMASK(7, 4)
+#define ANA_LCPLL_PMS_REFDIV_MASK GENMASK(3, 0)
+/* CMN_REG(0023) */
+#define LCPLL_PMS_SDIV_RBR_MASK GENMASK(7, 4)
+#define LCPLL_PMS_SDIV_HBR_MASK GENMASK(3, 0)
/* CMN_REG(0025) */
#define LCPLL_PMS_IQDIV_RSTN_MASK BIT(4)
/* CMN_REG(0028) */
@@ -1157,9 +1163,11 @@ static int rk_hdptx_frl_lcpll_cmn_config(struct rk_hdptx_phy *hdptx)
regmap_write(hdptx->regmap, CMN_REG(0020), cfg->pms_mdiv);
regmap_write(hdptx->regmap, CMN_REG(0021), cfg->pms_mdiv_afc);
regmap_write(hdptx->regmap, CMN_REG(0022),
- (cfg->pms_pdiv << 4) | cfg->pms_refdiv);
+ FIELD_PREP(ANA_LCPLL_PMS_PDIV_MASK, cfg->pms_pdiv) |
+ FIELD_PREP(ANA_LCPLL_PMS_REFDIV_MASK, cfg->pms_refdiv));
regmap_write(hdptx->regmap, CMN_REG(0023),
- (cfg->pms_sdiv << 4) | cfg->pms_sdiv);
+ FIELD_PREP(LCPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv) |
+ FIELD_PREP(LCPLL_PMS_SDIV_HBR_MASK, cfg->pms_sdiv));
regmap_write(hdptx->regmap, CMN_REG(002a), cfg->sdm_deno);
regmap_write(hdptx->regmap, CMN_REG(002b), cfg->sdm_num_sign);
regmap_write(hdptx->regmap, CMN_REG(002c), cfg->sdm_num);
@@ -1229,8 +1237,10 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
regmap_write(hdptx->regmap, CMN_REG(0051), cfg->pms_mdiv);
regmap_write(hdptx->regmap, CMN_REG(0055), cfg->pms_mdiv_afc);
regmap_write(hdptx->regmap, CMN_REG(0059),
- (cfg->pms_pdiv << 4) | cfg->pms_refdiv);
- regmap_write(hdptx->regmap, CMN_REG(005a), cfg->pms_sdiv << 4);
+ FIELD_PREP(ANA_ROPLL_PMS_PDIV_MASK, cfg->pms_pdiv) |
+ FIELD_PREP(ANA_ROPLL_PMS_REFDIV_MASK, cfg->pms_refdiv));
+ regmap_write(hdptx->regmap, CMN_REG(005a),
+ FIELD_PREP(ROPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv));
regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDM_EN_MASK,
FIELD_PREP(ROPLL_SDM_EN_MASK, cfg->sdm_en));
@@ -2177,7 +2187,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
ret = regmap_read(hdptx->regmap, CMN_REG(0023), &val);
if (ret)
return 0;
- lcpll_hw.pms_sdiv = val & 0xf;
+ lcpll_hw.pms_sdiv = FIELD_GET(LCPLL_PMS_SDIV_HBR_MASK, val);
ret = regmap_read(hdptx->regmap, CMN_REG(002B), &val);
if (ret)
@@ -2197,7 +2207,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
ret = regmap_read(hdptx->regmap, CMN_REG(002D), &val);
if (ret)
return 0;
- lcpll_hw.sdc_n = (val & LCPLL_SDC_N_MASK) >> 1;
+ lcpll_hw.sdc_n = FIELD_GET(LCPLL_SDC_N_MASK, val);
for (i = 0; i < ARRAY_SIZE(rk_hdptx_frl_lcpll_cfg); i++) {
const struct lcpll_config *cfg = &rk_hdptx_frl_lcpll_cfg[i];
@@ -2258,7 +2268,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
ret = regmap_read(hdptx->regmap, CMN_REG(0086), &val);
if (ret)
return 0;
- ropll_hw.pms_sdiv = ((val & PLL_PCG_POSTDIV_SEL_MASK) >> 4) + 1;
+ ropll_hw.pms_sdiv = FIELD_GET(PLL_PCG_POSTDIV_SEL_MASK, val) + 1;
bpc = (FIELD_GET(PLL_PCG_CLK_SEL_MASK, val) << 1) + 8;
fout = PLL_REF_CLK * ropll_hw.pms_mdiv;
--
2.54.0
^ permalink raw reply related
* [PATCH v3 5/6] phy: rockchip: samsung-hdptx: Simplify GRF access with FIELD_PREP_WM16()
From: Cristian Ciocaltea @ 2026-06-11 12:31 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com>
The 16 most significant bits of the general-purpose register (GRF) are
used as a write-enable mask for the remaining 16 bits.
Make use of the recently introduced FIELD_PREP_WM16() macro to avoid
open-coding the bit shift operations and improve code readability.
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 52 +++++++++++------------
1 file changed, 25 insertions(+), 27 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 529dd2d5d9bd..5ed110e2adc7 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd.
- * Copyright (c) 2024 Collabora Ltd.
+ * Copyright (c) 2024-2026 Collabora Ltd.
*
* Author: Algea Cao <algea.cao@rock-chips.com>
* Author: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
@@ -10,6 +10,7 @@
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
+#include <linux/hw_bitfield.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -949,7 +950,9 @@ static void rk_hdptx_pre_power_up(struct rk_hdptx_phy *hdptx)
reset_control_assert(hdptx->rsts[RST_CMN].rstc);
reset_control_assert(hdptx->rsts[RST_INIT].rstc);
- val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16;
+ val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0));
regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
}
@@ -960,8 +963,8 @@ static int rk_hdptx_post_enable_lane(struct rk_hdptx_phy *hdptx)
reset_control_deassert(hdptx->rsts[RST_LANE].rstc);
- val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 |
- HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN;
+ val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1));
regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
/* 3 lanes FRL mode */
@@ -990,16 +993,15 @@ static int rk_hdptx_post_enable_pll(struct rk_hdptx_phy *hdptx)
u32 val;
int ret;
- val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 |
- HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN;
+ val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1));
regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
usleep_range(10, 15);
reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
usleep_range(10, 15);
- val = HDPTX_I_PLL_EN << 16 | HDPTX_I_PLL_EN;
- regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1));
usleep_range(10, 15);
reset_control_deassert(hdptx->rsts[RST_CMN].rstc);
@@ -1037,7 +1039,9 @@ static void rk_hdptx_phy_disable(struct rk_hdptx_phy *hdptx)
reset_control_assert(hdptx->rsts[RST_CMN].rstc);
reset_control_assert(hdptx->rsts[RST_INIT].rstc);
- val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16;
+ val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0));
regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
}
@@ -1135,7 +1139,7 @@ static int rk_hdptx_frl_lcpll_cmn_config(struct rk_hdptx_phy *hdptx)
rk_hdptx_pre_power_up(hdptx);
- regmap_write(hdptx->grf, GRF_HDPTX_CON0, LC_REF_CLK_SEL << 16);
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0));
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq);
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_cmn_init_seq);
@@ -1178,8 +1182,7 @@ static int rk_hdptx_frl_lcpll_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
rk_hdptx_pre_power_up(hdptx);
/* ROPLL input reference clock from LCPLL (cascade mode) */
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- (LC_REF_CLK_SEL << 16) | LC_REF_CLK_SEL);
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 1));
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq);
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_ropll_cmn_init_seq);
@@ -1218,7 +1221,7 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
rk_hdptx_pre_power_up(hdptx);
- regmap_write(hdptx->grf, GRF_HDPTX_CON0, LC_REF_CLK_SEL << 16);
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0));
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq);
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_tmds_cmn_init_seq);
@@ -1336,11 +1339,9 @@ static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx)
FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0));
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x0));
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x0));
+ FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0));
}
static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx)
@@ -1616,9 +1617,8 @@ static int rk_hdptx_dp_aux_init(struct rk_hdptx_phy *hdptx)
FIELD_PREP(OVRD_SB_VREG_EN_MASK, 0x1));
regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x1));
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x1));
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1));
usleep_range(20, 25);
reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
@@ -1665,7 +1665,7 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
if (mode == PHY_MODE_DP) {
regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x1));
+ FIELD_PREP_WM16(HDPTX_MODE_SEL, 1));
for (lane = 0; lane < 4; lane++) {
regmap_update_bits(hdptx->regmap, LANE_REG(031e) + 0x400 * lane,
@@ -1693,7 +1693,7 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
if (!ret) {
regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0));
+ FIELD_PREP_WM16(HDPTX_MODE_SEL, 0));
if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
ret = rk_hdptx_frl_lcpll_mode_config(hdptx);
@@ -1828,8 +1828,7 @@ static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx,
u32 bw, status;
int ret;
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0));
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0));
switch (dp->link_rate) {
case 1620:
@@ -1885,8 +1884,7 @@ static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx,
regmap_update_bits(hdptx->regmap, CMN_REG(0095), DP_TX_LINK_BW_MASK,
FIELD_PREP(DP_TX_LINK_BW_MASK, bw));
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x1));
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1));
ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS,
status, FIELD_GET(HDPTX_O_PLL_LOCK_DONE, status),
--
2.54.0
^ permalink raw reply related
* [PATCH v3 4/6] phy: rockchip: samsung-hdptx: Drop restrict_rate_change handling
From: Cristian Ciocaltea @ 2026-06-11 12:31 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com>
Since commit 6efbd0f46dd8 ("phy: rockchip: samsung-hdptx: Restrict
altering TMDS char rate via CCF"), adjusting the rate via the Common
Clock Framework API has been disallowed.
To avoid breaking existing users until switching to the PHY config API,
it introduced a temporary exception to the rule, controlled via the
'restrict_rate_change' flag.
As the API transition completed, remove the now deprecated exception
logic.
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 42 +++++------------------
1 file changed, 8 insertions(+), 34 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 92c77e58518c..529dd2d5d9bd 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -414,7 +414,6 @@ struct rk_hdptx_phy {
/* clk provider */
struct clk_hw hw;
bool pll_config_dirty;
- bool restrict_rate_change;
atomic_t usage_count;
@@ -2074,7 +2073,6 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt
if (ret) {
dev_err(hdptx->dev, "invalid hdmi params for phy configure\n");
} else {
- hdptx->restrict_rate_change = true;
hdptx->pll_config_dirty = true;
dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__,
@@ -2301,41 +2299,17 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw,
struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
/*
- * Invalidate current clock rate to ensure rk_hdptx_phy_clk_set_rate()
- * will be invoked to commit PLL configuration.
+ * For uncommitted PLL configuration, invalidate the current clock rate
+ * to ensure rk_hdptx_phy_clk_set_rate() will be always invoked.
+ * Otherwise, restrict the rate according to the PHY link setup.
*/
- if (hdptx->pll_config_dirty) {
+ if (hdptx->pll_config_dirty)
req->rate = 0;
- return 0;
- }
-
- if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) {
+ else if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
req->rate = hdptx->hdmi_cfg.rate;
- return 0;
- }
-
- /*
- * FIXME: Temporarily allow altering TMDS char rate via CCF.
- * To be dropped as soon as the RK DW HDMI QP bridge driver
- * switches to make use of phy_configure().
- */
- if (!hdptx->restrict_rate_change && req->rate != hdptx->hdmi_cfg.rate) {
- struct phy_configure_opts_hdmi hdmi = {
- .tmds_char_rate = req->rate,
- };
-
- int ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &hdmi, &hdptx->hdmi_cfg);
-
- if (ret)
- return ret;
- }
-
- /*
- * The TMDS char rate shall be adjusted via phy_configure() only,
- * hence ensure rk_hdptx_phy_clk_set_rate() won't be invoked with
- * a different rate argument.
- */
- req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8, hdptx->hdmi_cfg.bpc);
+ else
+ req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8,
+ hdptx->hdmi_cfg.bpc);
return 0;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v3 3/6] phy: rockchip: samsung-hdptx: Drop TMDS rate setup workaround
From: Cristian Ciocaltea @ 2026-06-11 12:31 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com>
Since commit ba9c2fe18c17 ("drm/rockchip: dw_hdmi_qp: Switch to
phy_configure()") the TMDS rate setup doesn't rely anymore on the
unconventional usage of the bus width, instead it is managed exclusively
through the HDMI PHY configuration API.
Drop the now obsolete workaround to retrieve the TMDS character rate via
phy_get_bus_width() during power_on().
While at it, get rid of the extra call to rk_hdptx_phy_consumer_put() by
moving the statement at the end of the function.
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 27 +++++------------------
1 file changed, 6 insertions(+), 21 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 5295d5f6f287..92c77e58518c 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -1660,22 +1660,6 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
enum phy_mode mode = phy_get_mode(phy);
int ret, lane;
- if (mode != PHY_MODE_DP) {
- if (!hdptx->hdmi_cfg.rate && hdptx->hdmi_cfg.mode != PHY_HDMI_MODE_FRL) {
- /*
- * FIXME: Temporary workaround to setup TMDS char rate
- * from the RK DW HDMI QP bridge driver.
- * Will be removed as soon the switch to the HDMI PHY
- * configuration API has been completed on both ends.
- */
- hdptx->hdmi_cfg.rate = phy_get_bus_width(hdptx->phy) & 0xfffffff;
- hdptx->hdmi_cfg.rate *= 100;
- }
-
- dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__,
- hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
- }
-
ret = rk_hdptx_phy_consumer_get(hdptx);
if (ret)
return ret;
@@ -1701,9 +1685,10 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
rk_hdptx_dp_pll_init(hdptx);
ret = rk_hdptx_dp_aux_init(hdptx);
- if (ret)
- rk_hdptx_phy_consumer_put(hdptx, true);
} else {
+ dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__,
+ hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
+
if (hdptx->pll_config_dirty)
ret = rk_hdptx_pll_cmn_config(hdptx);
@@ -1716,11 +1701,11 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
else
ret = rk_hdptx_tmds_ropll_mode_config(hdptx);
}
-
- if (ret)
- rk_hdptx_phy_consumer_put(hdptx, true);
}
+ if (ret)
+ rk_hdptx_phy_consumer_put(hdptx, true);
+
return ret;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v3 2/6] phy: rockchip: samsung-hdptx: Handle uncommitted PHY config changes
From: Cristian Ciocaltea @ 2026-06-11 12:31 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com>
Any changes to the PHY link rate and/or color depth done via the HDMI
PHY configuration API are not immediately programmed into the hardware,
but are delayed until the PHY usage count gets incremented from 0 to 1,
that is when it is powered on or when the PLL clock exposed through
the CCF API is prepared, whichever comes first.
Since the clock might remain in prepared state after subsequent PHY
config changes, the programming can also be triggered via
clk_ops.set_rate(). However, from the clock consumer perspective (i.e.
VOP2 display controller), the (pixel) clock rate doesn't vary with bpc,
as that is handled internally by the PHY and reflected in the TDMS
character rate only.
As a consequence, changing the bpc while preserving the modeline may
lead to out-of-sync issues between CCF and HDMI PHY config state,
because the .set_rate() callback is not invoked when clock rate remains
constant. This may also happen when the PHY PLL has been pre-programmed
by an external entity, e.g. the bootloader, which is actually a
regression introduced by the recent FRL patches.
Introduce a pll_config_dirty flag to keep track of uncommitted PHY
config changes and use it in clk_ops.determine_rate() to invalidate the
current clock rate (as known by CCF) and, consequently, ensure those
changes are programmed into hardware via clk_ops.set_rate().
Moreover, proceed with a similar fix in phy_ops.power_on() callback, to
handle the scenario where the CCF API is not used due to operating in
FRL mode, while the clock is still in a prepared state and thus
preventing rk_hdptx_phy_consumer_get() to apply the updated PHY
configuration.
Fixes: de5dba833118 ("phy: rockchip: samsung-hdptx: Add HDMI 2.1 FRL support")
Fixes: 9d0ec51d7c22 ("phy: rockchip: samsung-hdptx: Add high color depth management")
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 84 +++++++++++++----------
1 file changed, 48 insertions(+), 36 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 710603afff86..5295d5f6f287 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -413,6 +413,7 @@ struct rk_hdptx_phy {
/* clk provider */
struct clk_hw hw;
+ bool pll_config_dirty;
bool restrict_rate_change;
atomic_t usage_count;
@@ -1260,13 +1261,19 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
static int rk_hdptx_pll_cmn_config(struct rk_hdptx_phy *hdptx)
{
+ int ret;
+
if (hdptx->hdmi_cfg.rate <= HDMI20_MAX_RATE)
- return rk_hdptx_tmds_ropll_cmn_config(hdptx);
+ ret = rk_hdptx_tmds_ropll_cmn_config(hdptx);
+ else if (hdptx->hdmi_cfg.rate == FRL_8G4L_RATE)
+ ret = rk_hdptx_frl_lcpll_ropll_cmn_config(hdptx);
+ else
+ ret = rk_hdptx_frl_lcpll_cmn_config(hdptx);
- if (hdptx->hdmi_cfg.rate == FRL_8G4L_RATE)
- return rk_hdptx_frl_lcpll_ropll_cmn_config(hdptx);
+ if (!ret)
+ hdptx->pll_config_dirty = false;
- return rk_hdptx_frl_lcpll_cmn_config(hdptx);
+ return ret;
}
static int rk_hdptx_frl_lcpll_mode_config(struct rk_hdptx_phy *hdptx)
@@ -1347,25 +1354,22 @@ static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx)
return 0;
ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &status);
- if (ret)
- goto dec_usage;
-
- if (status & HDPTX_O_PLL_LOCK_DONE)
- dev_warn(hdptx->dev, "PLL locked by unknown consumer!\n");
+ if (ret) {
+ atomic_dec(&hdptx->usage_count);
+ return ret;
+ }
if (mode == PHY_MODE_DP) {
rk_hdptx_dp_reset(hdptx);
} else {
- ret = rk_hdptx_pll_cmn_config(hdptx);
- if (ret)
- goto dec_usage;
+ /*
+ * Ignore PLL config errors at this point as pll_config_dirty
+ * was not reset and, therefore, operation will be retried.
+ */
+ rk_hdptx_pll_cmn_config(hdptx);
}
return 0;
-
-dec_usage:
- atomic_dec(&hdptx->usage_count);
- return ret;
}
static int rk_hdptx_phy_consumer_put(struct rk_hdptx_phy *hdptx, bool force)
@@ -1700,13 +1704,18 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
if (ret)
rk_hdptx_phy_consumer_put(hdptx, true);
} else {
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0));
+ if (hdptx->pll_config_dirty)
+ ret = rk_hdptx_pll_cmn_config(hdptx);
- if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
- ret = rk_hdptx_frl_lcpll_mode_config(hdptx);
- else
- ret = rk_hdptx_tmds_ropll_mode_config(hdptx);
+ if (!ret) {
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
+ HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0));
+
+ if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
+ ret = rk_hdptx_frl_lcpll_mode_config(hdptx);
+ else
+ ret = rk_hdptx_tmds_ropll_mode_config(hdptx);
+ }
if (ret)
rk_hdptx_phy_consumer_put(hdptx, true);
@@ -2081,7 +2090,10 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt
dev_err(hdptx->dev, "invalid hdmi params for phy configure\n");
} else {
hdptx->restrict_rate_change = true;
- dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__,
+ hdptx->pll_config_dirty = true;
+
+ dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__,
+ hdptx->hdmi_cfg.mode ? "FRL" : "TMDS",
hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
}
@@ -2303,8 +2315,19 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw,
{
struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
- if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
- return hdptx->hdmi_cfg.rate;
+ /*
+ * Invalidate current clock rate to ensure rk_hdptx_phy_clk_set_rate()
+ * will be invoked to commit PLL configuration.
+ */
+ if (hdptx->pll_config_dirty) {
+ req->rate = 0;
+ return 0;
+ }
+
+ if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) {
+ req->rate = hdptx->hdmi_cfg.rate;
+ return 0;
+ }
/*
* FIXME: Temporarily allow altering TMDS char rate via CCF.
@@ -2336,17 +2359,6 @@ static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
- unsigned long long link_rate = rate;
-
- if (hdptx->hdmi_cfg.mode != PHY_HDMI_MODE_FRL)
- link_rate = DIV_ROUND_CLOSEST_ULL(rate * hdptx->hdmi_cfg.bpc, 8);
-
- /* Revert any unlikely link rate change since determine_rate() */
- if (hdptx->hdmi_cfg.rate != link_rate) {
- dev_warn(hdptx->dev, "Reverting unexpected rate change from %llu to %llu\n",
- link_rate, hdptx->hdmi_cfg.rate);
- hdptx->hdmi_cfg.rate = link_rate;
- }
/*
* The link rate would be normally programmed in HW during
--
2.54.0
^ permalink raw reply related
* [PATCH v3 1/6] phy: rockchip: samsung-hdptx: Fix rate recalculation for high bpc
From: Cristian Ciocaltea @ 2026-06-11 12:31 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com>
The PHY PLL can be programmed by an external component, e.g. the
bootloader, just before the recalc_rate() callback is invoked during
devm_clk_hw_register() in the probe path.
Therefore rk_hdptx_phy_clk_recalc_rate() finds the PLL enabled and
attempts to compute the clock rate, while making use of the bpc value
from the HDMI PHY configuration, which always defaults to 8 because
phy_configure() was not run at that point. As a consequence, the
(re)calculated rate is incorrect when the actual bpc was higher than 8.
Do not rely on any of the hdmi_cfg members when computing the clock rate
and, instead, read the required input data (i.e. bpc), directly from the
hardware registers.
Fixes: 3481fc04d969 ("phy: rockchip: samsung-hdptx: Compute clk rate from PLL config")
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 2d973bc37f07..710603afff86 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -2168,7 +2168,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
struct lcpll_config lcpll_hw;
struct ropll_config ropll_hw;
u64 fout, sdm;
- u32 mode, val;
+ u32 mode, bpc, val;
int ret, i;
ret = regmap_read(hdptx->regmap, CMN_REG(0008), &mode);
@@ -2266,6 +2266,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
if (ret)
return 0;
ropll_hw.pms_sdiv = ((val & PLL_PCG_POSTDIV_SEL_MASK) >> 4) + 1;
+ bpc = (FIELD_GET(PLL_PCG_CLK_SEL_MASK, val) << 1) + 8;
fout = PLL_REF_CLK * ropll_hw.pms_mdiv;
if (ropll_hw.sdm_en) {
@@ -2280,7 +2281,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
fout = fout + sdm;
}
- return div_u64(fout * 2, ropll_hw.pms_sdiv * 10);
+ return DIV_ROUND_CLOSEST_ULL(fout * 2 * 8, ropll_hw.pms_sdiv * 10 * bpc);
}
static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw,
@@ -2288,19 +2289,13 @@ static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw,
{
struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
u32 status;
- u64 rate;
int ret;
ret = regmap_read(hdptx->grf, GRF_HDPTX_CON0, &status);
if (ret || !(status & HDPTX_I_PLL_EN))
return 0;
- rate = rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx);
-
- if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
- return rate;
-
- return DIV_ROUND_CLOSEST_ULL(rate * 8, hdptx->hdmi_cfg.bpc);
+ return rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx);
}
static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw,
--
2.54.0
^ permalink raw reply related
* [PATCH v3 0/6] phy: rockchip: samsung-hdptx: Clock fixes and API transition cleanups
From: Cristian Ciocaltea @ 2026-06-11 12:31 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
This series provides a set of bug fixes and cleanups for the Rockchip
Samsung HDPTX PHY driver.
The first part of the series (i.e. PATCH 1 & 2) addresses clock rate
calculation and synchronization issues. Specifically, it fixes edge
cases where the PHY PLL is pre-programmed by an external component (like
a bootloader) or when changing the color depth (bpc) while keeping the
modeline constant. Because the Common Clock Framework .set_rate()
callback might not be invoked if the pixel clock remains unchanged, this
previously led to out-of-sync states between CCF and the actual HDMI PHY
configuration.
The second part focuses on code cleanups and modernizing the register
access. Now that dw_hdmi_qp driver has fully switched to using
phy_configure(), we can drop the deprecated TMDS rate setup workarounds
and the restrict_rate_change flag logic. Finally, it refactors the
driver to consistently use standard bitfield macros.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
Changes in v3:
- Replaced div_u64() with DIV_ROUND_CLOSEST_ULL() in Patch 1 (Sashiko)
- Fixed theoretical usage_count unbalanced issue in Patch 2 (Sashiko)
- Rebased series onto latest phy/next
- Link to v2: https://patch.msgid.link/20260511-hdptx-clk-fixes-v2-0-664e41379cab@collabora.com
Changes in v2:
- Collected Tested-by tags from Thomas and Simon
- Fixed a typo in commit description of patch 1
- Added a comment in patch 2 explaining why PLL config errors are
ignored for rk_hdptx_phy_consumer_get()
- Added a missed FIELD_GET conversion for lcpll_hw.pms_sdiv in patch 6
- Rebased onto latest phy/fixes
- Link to v1: https://lore.kernel.org/r/20260227-hdptx-clk-fixes-v1-0-f998f2762d0f@collabora.com
---
Cristian Ciocaltea (6):
phy: rockchip: samsung-hdptx: Fix rate recalculation for high bpc
phy: rockchip: samsung-hdptx: Handle uncommitted PHY config changes
phy: rockchip: samsung-hdptx: Drop TMDS rate setup workaround
phy: rockchip: samsung-hdptx: Drop restrict_rate_change handling
phy: rockchip: samsung-hdptx: Simplify GRF access with FIELD_PREP_WM16()
phy: rockchip: samsung-hdptx: Consistently use bitfield macros
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 216 ++++++++++------------
1 file changed, 95 insertions(+), 121 deletions(-)
---
base-commit: 293e19f416fa3f233a2fb013258f7abcb39ad6ed
change-id: 20260227-hdptx-clk-fixes-47426632f862
^ permalink raw reply
* Re: [PATCH v2 2/2] clk: amlogic: Add A9 AO clock controller driver
From: Jian Hu @ 2026-06-11 12:24 UTC (permalink / raw)
To: Jerome Brunet
Cc: Jian Hu via B4 Relay, Neil Armstrong, Michael Turquette,
Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Xianwei Zhao, Kevin Hilman, Martin Blumenstingl, linux-amlogic,
linux-clk, devicetree, linux-kernel, linux-arm-kernel
In-Reply-To: <1jpl1yfunu.fsf@starbuckisacylon.baylibre.com>
On 6/10/2026 8:26 PM, Jerome Brunet wrote:
> [ EXTERNAL EMAIL ]
>
> On mer. 10 juin 2026 at 12:18, Jian Hu <jian.hu@amlogic.com> wrote:
>
>> Hi Jerome,
>>
>> Thanks for your review
>>
>> On 6/3/2026 10:29 PM, Jerome Brunet wrote:
>>> [ EXTERNAL EMAIL ]
>>>
>>> On Wed 03 Jun 2026 at 20:17, Jian Hu via B4 Relay <devnull+jian.hu.amlogic.com@kernel.org> wrote:
>>>
>>>> From: Jian Hu <jian.hu@amlogic.com>
>>>>
>>>> Add the Always-on clock controller driver for the Amlogic A9 SoC family.
>>>>
>>>> Signed-off-by: Jian Hu <jian.hu@amlogic.com>
>>>> ---
>>>> drivers/clk/meson/Kconfig | 13 ++
>>>> drivers/clk/meson/Makefile | 1 +
>>>> drivers/clk/meson/a9-aoclk.c | 419 +++++++++++++++++++++++++++++++++++++++++++
>>>> 3 files changed, 433 insertions(+)
>>>>
>>>> diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig
>>>> index cf8cf3f9e4ee..625e6788b940 100644
>>>> --- a/drivers/clk/meson/Kconfig
>>>> +++ b/drivers/clk/meson/Kconfig
>>>> @@ -132,6 +132,19 @@ config COMMON_CLK_A1_PERIPHERALS
>>>> device, A1 SoC Family. Say Y if you want A1 Peripherals clock
>>>> controller to work.
>>>>
>>>> +config COMMON_CLK_A9_AO
>>>> + tristate "Amlogic A9 SoC AO clock controller support"
>>>> + depends on ARM64
>>>> + default ARCH_MESON || COMPILE_TEST
>>>> + select COMMON_CLK_MESON_REGMAP
>>>> + select COMMON_CLK_MESON_CLKC_UTILS
>>>> + select COMMON_CLK_MESON_DUALDIV
>>>> + imply COMMON_CLK_SCMI
>>>> + help
>>>> + Support for the AO clock controller on Amlogic A311Y3 based
>>>> + device, AKA A9.
>>>> + Say Y if you want A9 AO clock controller to work.
>>>> +
>>>> config COMMON_CLK_C3_PLL
>>>> tristate "Amlogic C3 PLL clock controller"
>>>> depends on ARM64
>>>> diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
>>>> index c6719694a242..f89d027c282c 100644
>>>> --- a/drivers/clk/meson/Makefile
>>>> +++ b/drivers/clk/meson/Makefile
>>>> @@ -19,6 +19,7 @@ obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o
>>>> obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o
>>>> obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o
>>>> obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o
>>>> +obj-$(CONFIG_COMMON_CLK_A9_AO) += a9-aoclk.o
>>>> obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o
>>>> obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o
>>>> obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o
>>>> diff --git a/drivers/clk/meson/a9-aoclk.c b/drivers/clk/meson/a9-aoclk.c
>>>> new file mode 100644
>>>> index 000000000000..b7b3ca231a42
>>>> --- /dev/null
>>>> +++ b/drivers/clk/meson/a9-aoclk.c
>>>> @@ -0,0 +1,419 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2026 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <dt-bindings/clock/amlogic,a9-aoclkc.h>
>>>> +#include <linux/clk-provider.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include "clk-regmap.h"
>>>> +#include "clk-dualdiv.h"
>>>> +#include "meson-clkc-utils.h"
>>>> +
>>>> +#define AO_OSCIN_CTRL 0x00
>>>> +#define AO_SYS_CLK0 0x04
>>>> +#define AO_PWM_CLK_A_CTRL 0x1c
>>>> +#define AO_PWM_CLK_B_CTRL 0x20
>>>> +#define AO_PWM_CLK_C_CTRL 0x24
>>>> +#define AO_PWM_CLK_D_CTRL 0x28
>>>> +#define AO_PWM_CLK_E_CTRL 0x2c
>>>> +#define AO_PWM_CLK_F_CTRL 0x30
>>>> +#define AO_PWM_CLK_G_CTRL 0x34
>>>> +#define AO_CEC_CTRL0 0x38
>>>> +#define AO_CEC_CTRL1 0x3c
>>>> +#define AO_RTC_BY_OSCIN_CTRL0 0x50
>>>> +#define AO_RTC_BY_OSCIN_CTRL1 0x54
>>>> +
>>>> +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \
>>>> + MESON_COMP_SEL(a9_ao_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0)
>>>> +
>>>> +#define A9_COMP_DIV(_name, _reg, _shift, _width) \
>>>> + MESON_COMP_DIV(a9_ao_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT)
>>>> +
>>>> +#define A9_COMP_GATE(_name, _reg, _bit) \
>>>> + MESON_COMP_GATE(a9_ao_, _name, _reg, _bit, CLK_SET_RATE_PARENT)
>>>> +
>>>> +static struct clk_regmap a9_ao_xtal_in = {
>>>> + .data = &(struct clk_regmap_gate_data){
>>>> + .offset = AO_OSCIN_CTRL,
>>>> + .bit_idx = 3,
>>>> + },
>>>> + /*
>>>> + * It may be ao_sys's parent clock, its child clocks mark
>>>> + * CLK_IS_CRITICAL, So mark CLK_IS_CRITICAL for it.
>>>> + */
>>> I don't really get what you mean ... Could you rephrase ?
>>
>> The AO sys gate clock chain may be:
>>
>> ao_xtal_in->ao_xtal->ao_sys-> AO sys gate clocks
>>
>> "ao_xtal_in" is part of the parent chain of the AO sys gate clocks.
>>
>> Some of its downstream clocks are marked with CLK_IS_CRITICAL. To ensure
>> those clocks remain functional, ao_xtal_in must not be disabled and is
>> therefore marked as CLK_IS_CRITICAL as well.
> If any of the downstream clocks are critical and marked as such, there is not
> need to mark this one as well.
>
> You should only mark the clocks that are actually critical with the flag
> and let CCF figure out the dependencies.
Thanks for the clarification.
Understood. CCF already keeps the parent clocks of critical clocks enabled
during __clk_core_init(), so the CLK_IS_CRITICAL flag is not needed here.
I'll drop it in the next revision.
>>
>> I will rephrase it like this in the next version:
>>
>> /*
>> * ao_sys can select different clock sources. One possible clock
>> path is:
>> * ao_xtal_in->ao_xtal->ao_sys-> ao sys gate clocks
>> *
>> * ao_xtal_in is in the parent chain of AO sys gate clocks.
>> * Since some downstream clocks are marked CLK_IS_CRITICAL,
>> * ao_xtal_in must remain enabled and is therefore marked
>> * CLK_IS_CRITICAL as well.
>> */
>>
>>>> + .hw.init = CLK_HW_INIT_FW_NAME("ao_xtal_in", "xtal",
>>>> + &clk_regmap_gate_ops, CLK_IS_CRITICAL),
>>> I'm honestly not sure about this. It is correct, sure and the macro exist to be
>>> used but ... It does not really help readability here, does it ?
>>>
>>> (I know that was a feedback you've got on v1)
>>>
>>> Other than that, this looks good to me.
>>>
>> Ok, I will use the original clk_init_data for this one.
> Well my comment applies to whole thing really.
>
> There are surely ways in which the macro but the way we statically
> declare things, it adds a level of indirection that makes things harder
> to review IMO.
Understood. The same reasoning applies to the PLL and peripheral clock
controllers too.
I'll switch back to the explicit clk_init_data initialization and drop
CLK_HW_INIT_FW_NAME in the next revision.
>>
>> [ ... ]
>>
>>> --
>>> Jerome
> --
> Jerome
--
Jian
^ permalink raw reply
* Re: [PATCH 1/3] xfrm: extend ESP offload infrastructure for packet engines
From: Jihong Min @ 2026-06-11 12:23 UTC (permalink / raw)
To: Leon Romanovsky
Cc: Christian Marangi, Antoine Tenart, Herbert Xu, David S . Miller,
Lorenzo Bianconi, Andrew Lunn, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Steffen Klassert, linux-kernel,
linux-crypto, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260611115646.GN327369@unreal>
On 6/11/26 20:56, Leon Romanovsky wrote:
> On Sat, May 23, 2026 at 09:15:20PM +0900, Jihong Min wrote:
>> Some ESP offload engines operate on whole ESP packets rather than the
>> generic software trailer layout. They can generate outbound ESP padding,
>> next-header and ICV bytes in hardware, and inbound decapsulation can
>> return an already-trimmed packet with the recovered next-header value.
>
> How does this differ from the existing IPsec packet‑offload support in the
> Linux kernel?
>
> Thanks
Hi Leon,
The short answer is that the series did not explain the relationship
with the existing XFRM packet-offload model clearly enough.
Existing XFRM_DEV_OFFLOAD_PACKET already represents the high-level model
where hardware handles ESP packet processing instead of only crypto
transforms. What I was trying to handle in this series was a narrower
case: EIP93 is a look-aside crypto/IPsec engine, not the netdev itself,
so the Airoha netdev had to attach that engine into its TX/RX path and
let it generate or consume the ESP packet framing. The extra hooks in
this series were meant for that look-aside integration, but looking
back, the split between the existing packet-offload model and the new
plumbing was not clean enough.
At this point, though, I think the right thing is to withdraw this
EIP93/Airoha series.
The reason is related to the SOE work I mentioned in the other patch
thread. Many Airoha SoCs also have a higher-performance IP block called
SOE (Secure Offload Engine). I recently wrote and tested a driver for
that block, and I am currently carrying it here: [kernel: add bonding
LAG XFRM offload infrastructure and Airoha
support](https://github.com/hurryman2212/OpenW1700k-test/commit/fbfe8f919f836bb62b3849f803865a4d9b8dc76f).
With the EIP93 path I could get around 1 Gbps, while the SOE path can
reach about 5 Gbps in my current setup. Because of that, integrating
this EIP93 ESP packet path directly into `airoha_eth` is no longer the
most useful direction for Airoha Ethernet.
That said, SOE exists only on some Airoha SoCs. EIP93 can still be
useful on other platforms as a look-aside ESP packet offloader, but I
think that needs a cleaner infrastructure than this series had. The
look-aside offloader should be able to live as a separate module, not be
tied directly to one specific netdev driver, while still allowing
compatible netdevs to attach it into the XFRM path. I think that needs a
more general infrastructure extension, so I would rather revisit the
EIP93 work later on top of that kind of model.
Sincerely,
Jihong Min
^ permalink raw reply
* Re: [RFC PATCH v2 1/3] mm/huge_memory: make persistent huge zero folio read-only
From: David Hildenbrand (Arm) @ 2026-06-11 12:21 UTC (permalink / raw)
To: Lance Yang
Cc: akpm, xueyuan.chen21, linux-mm, linux-kernel, linux-arm-kernel,
x86, catalin.marinas, will, tglx, mingo, bp, dave.hansen, luto,
peterz, hpa, ljs, liam, vbabka, rppt, surenb, mhocko, ziy,
baolin.wang, npache, ryan.roberts, dev.jain, baohua, yang, jannh,
dave.hansen
In-Reply-To: <20260611115817.59353-1-lance.yang@linux.dev>
On 6/11/26 13:58, Lance Yang wrote:
>
> On Thu, Jun 11, 2026 at 01:28:58PM +0200, David Hildenbrand (Arm) wrote:
>> On 6/10/26 04:15, Lance Yang wrote:
>>>
>>>
>>> Right, this came from the RFC v1 discussion[1]. David preferred a page-
>>> range helper for possible future non-folio callers, not something folio-
>>> only.
>>>
>>> Of course, we could also add a folio wrapper on top of that if needed :)
>>
>> Best to document that as part of the patch description: we don't really expect
>> to have a lot of read-only folios in the near future (zero page is rather
>> special; maybe it won't even be a folio in the future).
>
> Ah, good to know, thanks. Will spell that out in RFC v3.
>
> Maybe something like this?
>
> The huge zero page is pretty special case, and maybe it won't even be a
> folio in the future. Since read-only folios are unlikely to become a
> common thing, a page-range helper is the cleaner fit.
Right. And if read-only folios in FSes become a real thing, we can always add
infrastructure for that.
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH 2/3] crypto: inside-secure: add EIP93 ESP packet backend
From: Jihong Min @ 2026-06-11 12:17 UTC (permalink / raw)
To: Simon Horman
Cc: Christian Marangi, Antoine Tenart, Herbert Xu, David S . Miller,
Lorenzo Bianconi, Andrew Lunn, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Steffen Klassert, linux-kernel, linux-crypto,
linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260527100824.GJ2256768@horms.kernel.org>
On 5/27/26 19:08, Simon Horman wrote:
> On Sat, May 23, 2026 at 09:15:21PM +0900, Jihong Min wrote:
>> Expose an EIP93 packet-mode IPsec backend for netdev drivers that need
>> ESP encapsulation and decapsulation offload without advertising EIP93
>> itself as a netdev.
>>
>> Add provider selection, capability reporting, SA lifecycle management,
>> IPsec request completion, and provider fault notification around the
>> existing EIP93 descriptor path.
>>
>> Assisted-by: Codex:gpt-5.5
>> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
>
> ...
>
>> diff --git a/drivers/crypto/inside-secure/eip93/eip93-ipsec.c b/drivers/crypto/inside-secure/eip93/eip93-ipsec.c
>
> ...
>
>> +static void eip93_ipsec_abort_requests(struct eip93_ipsec *ipsec, int err)
>> +{
>> + struct eip93_ipsec_sa *sa;
>> +
>> + while (true) {
>> + bool found = false;
>> +
>> + spin_lock_bh(&ipsec->lock);
>> + list_for_each_entry(sa, &ipsec->sa_list, node) {
>> + spin_lock(&sa->lock);
>> + if (sa->aborting) {
>> + spin_unlock(&sa->lock);
>> + continue;
>> + }
>> +
>> + sa->aborting = true;
>> + found = refcount_inc_not_zero(&sa->refcnt);
>> + spin_unlock(&sa->lock);
>> + if (found)
>> + break;
>> + }
>> + spin_unlock_bh(&ipsec->lock);
>> + if (!found)
>> + return;
>> +
>> + eip93_ipsec_abort_sa(sa, err);
>> + eip93_ipsec_sa_put(sa);
>
> sa is the iterator for the list_for_each_entry loop.
> However, here it is used outside of that context.
>
> "If list_for_each_entry, etc complete a traversal of the list, the
> iterator variable ends up pointing to an address at an offset from
> the list head, and not a meaningful structure. Thus this value
> should not be used after the end of the iterator.
>
> https://www.spinics.net/lists/linux-kernel-janitors/msg11994.html
>
> Flagged by Coccinelle.
>
Hi Simon,
Thanks for the feedback, and sorry for noticing this mail so late.
Your point is correct. The `list_for_each_entry()` iterator should not
be used outside the loop like that. If I continued with this series, I
would fix it by keeping a separate selected SA pointer before dropping
the lock.
At this point, though, I think the right thing is to withdraw this
EIP93/Airoha series.
The reason is that many Airoha SoCs also have a higher-performance IP
block called SOE (Secure Offload Engine). I recently wrote and tested a
driver for that block, and I am currently carrying it here: [kernel: add
bonding LAG XFRM offload infrastructure and Airoha
support](https://github.com/hurryman2212/OpenW1700k-test/commit/fbfe8f919f836bb62b3849f803865a4d9b8dc76f).
With the EIP93 path I could get around 1 Gbps, while the SOE path can
reach about 5 Gbps in my current setup. Because of that, integrating
this EIP93 ESP packet path directly into `airoha_eth` is no longer the
most useful direction for Airoha Ethernet.
That said, SOE exists only on some Airoha SoCs. EIP93 can still be
useful on other platforms as a look-aside ESP packet offloader, but I
think that needs a cleaner infrastructure than this series had. The
look-aside offloader should be able to live as a separate module, not be
tied directly to one specific netdev driver, while still allowing
compatible netdevs to attach it into the XFRM path. I think that needs a
more general infrastructure extension, so I would rather revisit the
EIP93 work later on top of that kind of model.
Sincerely,
Jihong Min
>> + }
>> +}
>
> ...
^ permalink raw reply
* Re: [PATCH v3 00/11] kdump: reduce vmcore size and capture time
From: Baoquan He @ 2026-06-11 12:03 UTC (permalink / raw)
To: Wandun
Cc: linux-arm-kernel, linux-kernel, loongarch, linux-riscv,
devicetree, kexec, iommu, zhaomeijing, Rob Herring, saravanak,
bhe, rppt, pjw, palmer, aou, chenhuacai, kernel, catalin.marinas,
will, alex, akpm, pasha.tatashin, pratyush, ruirui.yang,
m.szyprowski, robin.murphy
In-Reply-To: <a3993db0-6975-455d-9674-4fd7cfcf80fc@gmail.com>
On 06/11/26 at 11:09am, Wandun wrote:
>
>
> On 6/11/26 10:09, Wandun wrote:
> >
> >
> > On 5/27/26 11:29, Wandun Chen wrote:
> >> From: Wandun Chen <chenwandun@lixiang.com>
> >>
> >> On SoCs that carve out large firmware-owned reserved memory (GPU
> >> firmware, DSP, modem, camera ISP, NPU, ...), kdump currently dumps
> >> those carveouts as part of system RAM even though their contents are
> >> firmware state that is not useful for kernel crash analysis.
> >>
> >> This series introduces an opt-in 'dumpable' flag [1] on struct
> >> reserved_mem and uses it to filter the elfcorehdr PT_LOAD ranges on
> >> DT-based architectures (arm64, riscv, loongarch). By default reserved
> >> regions are treated as non-dumpable; CMA regions are explicitly opted
> >> in because their pages are returned to the buddy allocator and may
> >> carry key crash-analysis data.
> >>
> >> The series is organized as follows:
> >> Patches 1-3: Pre-existing fixes and a small prep change.
> >> Patches 4-5: Restructure to allow appending /memreserve/ entries.
> >> Patches 6-7: Add a dumpable flag and append /memreserve/ entries.
> >> Patch 8: Add generic kdump helpers.
> >> Patches 9-11: Wire the helpers into arm64, riscv and loongarch kdump
> >> elfcorehdr preparation.
> > Hi,
> >
> > Gentle ping on this series.
> >
> > Status summary:
> > -patch 03: respun separately per Rob's suggestion, picked up for 7.2
> > -patch 06: Acked-by: Marek Szyprowski -patch 09: Acked-by: Will Deacon
> > The remaining patches (01, 02, 04, 05, 07, 08, 10, 11) are still
> > awaiting review. your feedback would be greately appreciated. I know we
> > are at the end of 7.1 -rc cycle, I don't want to rush this series, just
> > collecting more feedback, and will send next version based on 7.2-rc1.
> > If spliting the series into smaller logical group would make review
> > easier, please let me know. Best regards, Wandun
>
> Apologies for the formatting issue in my previous email.
> Here is the properly formatted version.
>
> Gentle ping on this series.
Thanks for the effort, the overral looks good to me at 1st glance. I will
check if there's concern on generic part. And meanwhile, I am wondering
if there's any chance x86 or other ARCH-es w/o OF/FDT can also choose to
not dump some areas, e.g GPU stolen memory. Surely, that's another story.
>
> Status summary:
> - patch 03: respun separately per Rob's suggestion, picked up for 7.2
> - patch 06: Acked-by: Marek Szyprowski
> - patch 09: Acked-by: Will Deacon
>
> The remaining patches (01, 02, 04, 05, 07, 08, 10, 11) are still
> awaiting review. Your feedback would be greatly appreciated.
>
> I know we are at the end of 7.1-rc cycle, I don't want to rush this
> series, just collecting more feedback, and will send next version based
> on 7.2-rc1.
>
> If splitting the series into smaller logical groups would make review
> easier, please let me know.
>
> Best regards,
> Wandun
>
>
> >>
> >> v2 --> v3:
> >> 1. Fix out-of-bounds issue if device tree lacks /reserved-memory node.[2]
> >> 2. Fix UAF issue when alloc_reserved_mem_array() fails.
> >> 3. Add some prepare patches.
> >>
> >> v1 --> v2:
> >> 1. v1 added an opt-out DT property ('linux,no-dump'). Per Rob's
> >> feedback [1], v2 drop that property and exclude reserve memory
> >> by default.
> >> 2. Split some prepared patches from the original patches.
> >> 3. Address coding-style comments on patch 5 from Rob.
> >>
> >> [1] https://lore.kernel.org/lkml/20260506144542.GA2072596-
> >> robh@kernel.org/
> >> [2] https://sashiko.dev/#/patchset/20260520091844.592753-1-
> >> chenwandun%40lixiang.com?part=4
> >>
> >> Wandun Chen (11):
> >> of: reserved_mem: handle NULL name in of_reserved_mem_lookup()
> >> kexec/crash: provide crash_exclude_mem_range() stub when
> >> CONFIG_CRASH_DUMP=n
> >> of: reserved_mem: avoid post-init UAF when alloc_reserved_mem_array()
> >> fails
> >> of: reserved_mem: zero total_reserved_mem_cnt if no valid
> >> /reserved-memory entry
> >> of: reserved_mem: split alloc_reserved_mem_array() from
> >> fdt_scan_reserved_mem_late()
> >> of: reserved_mem: add dumpable flag to opt-in vmcore
> >> of: reserved_mem: save /memreserve/ entries into the reserved_mem
> >> array
> >> of: reserved_mem: add kdump helpers to exclude non-dumpable regions
> >> arm64: kdump: exclude non-dumpable reserved memory regions from vmcore
> >> riscv: kdump: exclude non-dumpable reserved memory regions from vmcore
> >> loongarch: kdump: exclude non-dumpable reserved memory regions from
> >> vmcore
> >>
> >> arch/arm64/kernel/machine_kexec_file.c | 6 ++
> >> arch/loongarch/kernel/machine_kexec_file.c | 6 ++
> >> arch/riscv/kernel/machine_kexec_file.c | 4 +
> >> drivers/of/fdt.c | 11 +-
> >> drivers/of/of_private.h | 3 +
> >> drivers/of/of_reserved_mem.c | 117 +++++++++++++++++++--
> >> include/linux/crash_core.h | 6 ++
> >> include/linux/of_reserved_mem.h | 15 +++
> >> kernel/dma/contiguous.c | 1 +
> >> 9 files changed, 157 insertions(+), 12 deletions(-)
> >>
> >
>
^ permalink raw reply
* Re: [RFC PATCH v2 1/3] mm/huge_memory: make persistent huge zero folio read-only
From: Lance Yang @ 2026-06-11 11:58 UTC (permalink / raw)
To: david
Cc: lance.yang, akpm, xueyuan.chen21, linux-mm, linux-kernel,
linux-arm-kernel, x86, catalin.marinas, will, tglx, mingo, bp,
dave.hansen, luto, peterz, hpa, ljs, liam, vbabka, rppt, surenb,
mhocko, ziy, baolin.wang, npache, ryan.roberts, dev.jain, baohua,
yang, jannh, dave.hansen
In-Reply-To: <8c36a91d-288f-4ffb-b2ec-41e3ef789d72@kernel.org>
On Thu, Jun 11, 2026 at 01:28:58PM +0200, David Hildenbrand (Arm) wrote:
>On 6/10/26 04:15, Lance Yang wrote:
>>
>> On Tue, Jun 09, 2026 at 12:45:49PM -0700, Andrew Morton wrote:
>>> On Tue, 9 Jun 2026 22:37:59 +0800 Xueyuan Chen <xueyuan.chen21@gmail.com> wrote:
>>>
>>>> The huge zero folio is shared globally, and its contents should never
>>>> change after initialization. As Jann Horn pointed out[1], the kernel has
>>>> had bugs, including security bugs, where read-only pages were later written
>>>> to. If the persistent huge zero folio is read-only in the direct map, such
>>>> writes fault instead of silently corrupting the shared zero contents.
>>>>
>>>> Add arch_make_pages_readonly() so mm code can request read-only direct-map
>>>> protection for a page range. Direct-map protection is
>>>> architecture-specific, so the generic weak implementation does nothing.
>>>>
>>>> This was inspired by Jann Horn's read-only zero page work[1] and follow-up
>>>> discussion[2] with Yang Shi.
>>>>
>>>> [1] https://lore.kernel.org/linux-mm/20260508-ro-zeropage-v1-1-9808abc20b49@google.com/
>>>> [2] https://lore.kernel.org/linux-mm/CAHbLzkrXXe7r3n3jXgDKtwZhRqj=jDx9E6dLOULohnhBguvi9A@mail.gmail.com/
>>>>
>>>> ...
>>>>
>>>> --- a/mm/huge_memory.c
>>>> +++ b/mm/huge_memory.c
>>>> @@ -308,6 +308,11 @@ static unsigned long shrink_huge_zero_folio_scan(struct shrinker *shrink,
>>>> return 0;
>>>> }
>>>>
>>>> +bool __weak arch_make_pages_readonly(struct page *page, int nr_pages)
>>>> +{
>>>> + return false;
>>>> +}
>>>> +
>>>> static struct shrinker *huge_zero_folio_shrinker;
>>>>
>>>> #ifdef CONFIG_SYSFS
>>>> @@ -982,8 +987,14 @@ static int __init thp_shrinker_init(void)
>>>> * that get_huge_zero_folio() will most likely not fail as
>>>> * thp_shrinker_init() is invoked early on during boot.
>>>> */
>>>> - if (!get_huge_zero_folio())
>>>> + if (!get_huge_zero_folio()) {
>>>> pr_warn("Allocating persistent huge zero folio failed\n");
>>>> + return 0;
>>>> + }
>>>> +
>>>> + arch_make_pages_readonly(folio_page(huge_zero_folio, 0),
>>>> + HPAGE_PMD_NR);
>>>
>>> Can it simply pass the folio?
>>
>> Right, this came from the RFC v1 discussion[1]. David preferred a page-
>> range helper for possible future non-folio callers, not something folio-
>> only.
>>
>> Of course, we could also add a folio wrapper on top of that if needed :)
>
>Best to document that as part of the patch description: we don't really expect
>to have a lot of read-only folios in the near future (zero page is rather
>special; maybe it won't even be a folio in the future).
Ah, good to know, thanks. Will spell that out in RFC v3.
Maybe something like this?
The huge zero page is pretty special case, and maybe it won't even be a
folio in the future. Since read-only folios are unlikely to become a
common thing, a page-range helper is the cleaner fit.
^ permalink raw reply
* Re: [PATCH 00/18] pinctrl: airoha: split driver on shared code and SoC specific drivers, add supporf of en7523
From: Linus Walleij @ 2026-06-11 11:58 UTC (permalink / raw)
To: Mikhail Kshevetskiy
Cc: Sean Wang, Lorenzo Bianconi, Matthias Brugger,
AngeloGioacchino Del Regno, Christian Marangi,
Bartosz Golaszewski, Benjamin Larsson, linux-kernel, linux-gpio,
linux-mediatek, linux-arm-kernel, Matheus Sampaio Queiroga,
Markus Gothe
In-Reply-To: <20260607001654.1439480-1-mikhail.kshevetskiy@iopsys.eu>
On Sun, Jun 7, 2026 at 2:17 AM Mikhail Kshevetskiy
<mikhail.kshevetskiy@iopsys.eu> wrote:
> This patchset
> * fixes a series of issues
> * split combined driver on common code and several SoC specific drivers
> * adds support of en7523 SoC
This seems to be a collection of previously posted, reviewed and now
also applied patches. I don't know what to apply and what not to apply
now.
Please rebase the remaining patches on to of my pinctrl "devel" branch
https://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-pinctrl.git/log/?h=devel
and repost as v2.
Yours,
Linus Walleij
^ permalink raw reply
* RE: [PATCH v3 3/5] dt-bindings: clock: cix,sky1-audss-clock: add audss clock controller
From: Joakim Zhang @ 2026-06-11 11:57 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: mturquette@baylibre.com, sboyd@kernel.org, bmasney@redhat.com,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
p.zabel@pengutronix.de, Gary Yang, cix-kernel-upstream,
linux-clk@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org
In-Reply-To: <20260611-numbat-of-unmistakable-excitement-f6cfed@quoll>
Hi,
[...]
> -----Original Message-----
> From: Krzysztof Kozlowski <krzk@kernel.org>
> Sent: Thursday, June 11, 2026 3:42 PM
> To: Joakim Zhang <joakim.zhang@cixtech.com>
> Cc: mturquette@baylibre.com; sboyd@kernel.org; bmasney@redhat.com;
> robh@kernel.org; krzk+dt@kernel.org; conor+dt@kernel.org;
> p.zabel@pengutronix.de; Gary Yang <gary.yang@cixtech.com>; cix-kernel-
> upstream <cix-kernel-upstream@cixtech.com>; linux-clk@vger.kernel.org;
> devicetree@vger.kernel.org; linux-kernel@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org
> Subject: Re: [PATCH v3 3/5] dt-bindings: clock: cix,sky1-audss-clock: add audss
> clock controller
>
> EXTERNAL EMAIL
>
> > diff --git a/include/dt-bindings/clock/cix,sky1-audss.h b/include/dt-
> bindings/clock/cix,sky1-audss.h
> > new file mode 100644
> > index 000000000000..033046407dee
> > --- /dev/null
> > +++ b/include/dt-bindings/clock/cix,sky1-audss.h
>
> Filename must match the compatible.
Will rename to include/dt-bindings/clock/cix,sky1-audss-clock.h to match the compatible.
Thanks,
Joakim
^ permalink raw reply
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