mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 05:14:41 -04:00
net: pse-pd: realtek-pse-mcu: add UART transport
Add the serdev (UART) transport for the Realtek PSE MCU core. It registers the MCU as a serdev device and provides the send/recv callbacks the core uses to exchange the 12-byte frames, receiving asynchronously via the serdev receive_buf callback. The baud rate defaults to 19200 and can be overridden per board with the "current-speed" property. Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com> Reviewed-by: Kory Maincent <kory.maincent@bootlin.com> Link: https://patch.msgid.link/20260813222036.873930-5-jelonek.jonas@gmail.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
committed by
Paolo Abeni
parent
4c088d9eb3
commit
5cc93d9897
@@ -30,6 +30,17 @@ config PSE_REALTEK_MCU_I2C
|
||||
PSE silicon is not accessed directly. To compile this driver as a
|
||||
module, choose M here: the module will be called realtek-pse-mcu-i2c.
|
||||
|
||||
config PSE_REALTEK_MCU_UART
|
||||
tristate "Realtek PSE MCU driver (UART transport)"
|
||||
depends on SERIAL_DEV_BUS
|
||||
select PSE_REALTEK_MCU
|
||||
help
|
||||
Driver for the microcontroller (MCU) that fronts the PSE
|
||||
hardware on various Realtek-based managed switches, attached
|
||||
via UART. The MCU exposes a message-based protocol; the actual PSE
|
||||
silicon is not accessed directly. To compile this driver as a
|
||||
module, choose M here: the module will be called realtek-pse-mcu-uart.
|
||||
|
||||
config PSE_REGULATOR
|
||||
tristate "Regulator based PSE controller"
|
||||
help
|
||||
|
||||
@@ -5,6 +5,7 @@ obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
|
||||
|
||||
obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o
|
||||
obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o
|
||||
obj-$(CONFIG_PSE_REALTEK_MCU_UART) += realtek-pse-mcu-uart.o
|
||||
obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
|
||||
obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
|
||||
obj-$(CONFIG_PSE_SI3474) += si3474.o
|
||||
|
||||
164
drivers/net/pse-pd/realtek-pse-mcu-uart.c
Normal file
164
drivers/net/pse-pd/realtek-pse-mcu-uart.c
Normal file
@@ -0,0 +1,164 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pse-pd/pse.h>
|
||||
#include <linux/serdev.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include "realtek-pse-mcu.h"
|
||||
|
||||
#define RTPSE_MCU_UART_BAUD_DEFAULT 19200
|
||||
#define RTPSE_MCU_UART_TX_TIMEOUT msecs_to_jiffies(100)
|
||||
#define RTPSE_MCU_UART_RX_TIMEOUT msecs_to_jiffies(RTPSE_MCU_RESPONSE_MAX_MS)
|
||||
|
||||
struct rtpse_mcu_uart {
|
||||
struct rtpse_mcu_ctrl pse;
|
||||
struct serdev_device *serdev;
|
||||
struct completion rx_done;
|
||||
spinlock_t rx_lock; /* protects rx_buf and rx_len */
|
||||
size_t rx_len;
|
||||
u8 rx_buf[RTPSE_MCU_MSG_SIZE];
|
||||
};
|
||||
|
||||
#define to_rtpse_mcu_uart(p) container_of(p, struct rtpse_mcu_uart, pse)
|
||||
|
||||
/*
|
||||
* No framing is done here: a glitched frame costs one transaction, then
|
||||
* the next _send re-frames from rx_len 0. Resync works by returning count
|
||||
* (not take), dropping any overflow so serdev keeps no leftover to bleed
|
||||
* into the next frame.
|
||||
*/
|
||||
static size_t rtpse_mcu_uart_receive(struct serdev_device *serdev,
|
||||
const u8 *buf, size_t count)
|
||||
{
|
||||
struct rtpse_mcu_uart *ctx = serdev_device_get_drvdata(serdev);
|
||||
size_t take;
|
||||
|
||||
scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
|
||||
take = min(count, sizeof(ctx->rx_buf) - ctx->rx_len);
|
||||
if (take) {
|
||||
memcpy(ctx->rx_buf + ctx->rx_len, buf, take);
|
||||
ctx->rx_len += take;
|
||||
if (ctx->rx_len == sizeof(ctx->rx_buf))
|
||||
complete(&ctx->rx_done);
|
||||
}
|
||||
}
|
||||
|
||||
/* consume all to avoid desync/misalignment */
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct serdev_device_ops rtpse_mcu_uart_serdev_ops = {
|
||||
.receive_buf = rtpse_mcu_uart_receive,
|
||||
.write_wakeup = serdev_device_write_wakeup,
|
||||
};
|
||||
|
||||
static int rtpse_mcu_uart_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
|
||||
{
|
||||
struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);
|
||||
int written;
|
||||
|
||||
/* clear any leftover rx state before transmitting */
|
||||
scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
|
||||
reinit_completion(&ctx->rx_done);
|
||||
ctx->rx_len = 0;
|
||||
}
|
||||
|
||||
written = serdev_device_write(ctx->serdev, (const u8 *)req, sizeof(*req),
|
||||
RTPSE_MCU_UART_TX_TIMEOUT);
|
||||
if (written < 0)
|
||||
return written;
|
||||
if (written != sizeof(*req))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtpse_mcu_uart_recv(struct rtpse_mcu_ctrl *pse,
|
||||
const struct rtpse_mcu_msg *req,
|
||||
struct rtpse_mcu_msg *resp)
|
||||
{
|
||||
struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);
|
||||
|
||||
if (!wait_for_completion_timeout(&ctx->rx_done, RTPSE_MCU_UART_RX_TIMEOUT))
|
||||
return -ETIMEDOUT;
|
||||
|
||||
scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
|
||||
if (ctx->rx_len != sizeof(*resp))
|
||||
return -EIO;
|
||||
|
||||
memcpy(resp, ctx->rx_buf, sizeof(*resp));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtpse_mcu_transport_ops rtpse_mcu_uart_transport_ops = {
|
||||
.send = rtpse_mcu_uart_send,
|
||||
.recv = rtpse_mcu_uart_recv,
|
||||
};
|
||||
|
||||
static int rtpse_mcu_uart_probe(struct serdev_device *serdev)
|
||||
{
|
||||
u32 speed = RTPSE_MCU_UART_BAUD_DEFAULT;
|
||||
struct device *dev = &serdev->dev;
|
||||
struct rtpse_mcu_uart *ctx;
|
||||
unsigned int baud;
|
||||
int ret;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ctx->serdev = serdev;
|
||||
ctx->pse.dev = dev;
|
||||
ctx->pse.pcdev.owner = THIS_MODULE;
|
||||
ctx->pse.transport = &rtpse_mcu_uart_transport_ops;
|
||||
init_completion(&ctx->rx_done);
|
||||
spin_lock_init(&ctx->rx_lock);
|
||||
|
||||
serdev_device_set_drvdata(serdev, ctx);
|
||||
serdev_device_set_client_ops(serdev, &rtpse_mcu_uart_serdev_ops);
|
||||
|
||||
ret = devm_serdev_device_open(dev, serdev);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to open serdev\n");
|
||||
|
||||
fwnode_property_read_u32(dev_fwnode(dev), "current-speed", &speed);
|
||||
|
||||
baud = serdev_device_set_baudrate(serdev, speed);
|
||||
if (baud != speed)
|
||||
dev_warn(dev, "could not set baudrate %u, controller uses %u\n",
|
||||
speed, baud);
|
||||
|
||||
serdev_device_set_flow_control(serdev, false);
|
||||
|
||||
ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
|
||||
if (ret)
|
||||
dev_warn(dev, "could not set parity to none: %d\n", ret);
|
||||
|
||||
return rtpse_mcu_register(&ctx->pse);
|
||||
}
|
||||
|
||||
static const struct of_device_id rtpse_mcu_uart_of_match[] = {
|
||||
{ .compatible = "realtek,pse-mcu-gen1", .data = &rtpse_mcu_gen1_data },
|
||||
{ .compatible = "realtek,pse-mcu-gen2", .data = &rtpse_mcu_gen2_data },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rtpse_mcu_uart_of_match);
|
||||
|
||||
static struct serdev_device_driver rtpse_mcu_uart_driver = {
|
||||
.driver = {
|
||||
.name = "realtek-pse-mcu-uart",
|
||||
.of_match_table = rtpse_mcu_uart_of_match,
|
||||
},
|
||||
.probe = rtpse_mcu_uart_probe,
|
||||
};
|
||||
module_serdev_device_driver(rtpse_mcu_uart_driver);
|
||||
|
||||
MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
|
||||
MODULE_DESCRIPTION("Realtek PSE MCU driver (UART transport)");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user