mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 14:04:27 -04:00
net: pse-pd: realtek-pse-mcu: add I2C transport
Add the I2C/SMBus transport for the Realtek PSE MCU core. It registers the MCU on an I2C bus and provides the send/recv callbacks the core uses to exchange the 12-byte frames. The MCU firmware expects one of two framings on the I2C bus, and which one is part of the compatible: '-smbus' (reads carry a leading command byte and a repeated start) or raw '-i2c' (bare block writes and reads). The match data flags the raw-I2C case; SMBus is the default because that's what the majority of devices uses. Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com> Reviewed-by: Kory Maincent <kory.maincent@bootlin.com> Link: https://patch.msgid.link/20260813222036.873930-4-jelonek.jonas@gmail.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
committed by
Paolo Abeni
parent
44566e614d
commit
4c088d9eb3
@@ -19,6 +19,17 @@ config PSE_REALTEK_MCU
|
||||
Shared core for the Realtek PSE MCU driver. This is selected
|
||||
automatically by the transport options below.
|
||||
|
||||
config PSE_REALTEK_MCU_I2C
|
||||
tristate "Realtek PSE MCU driver (I2C transport)"
|
||||
depends on I2C
|
||||
select PSE_REALTEK_MCU
|
||||
help
|
||||
Driver for the microcontroller (MCU) that fronts the PSE
|
||||
hardware on various Realtek-based managed switches, attached
|
||||
via I2C/SMBus. 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-i2c.
|
||||
|
||||
config PSE_REGULATOR
|
||||
tristate "Regulator based PSE controller"
|
||||
help
|
||||
|
||||
@@ -4,6 +4,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_REGULATOR) += pse_regulator.o
|
||||
obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
|
||||
obj-$(CONFIG_PSE_SI3474) += si3474.o
|
||||
|
||||
148
drivers/net/pse-pd/realtek-pse-mcu-i2c.c
Normal file
148
drivers/net/pse-pd/realtek-pse-mcu-i2c.c
Normal file
@@ -0,0 +1,148 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pse-pd/pse.h>
|
||||
|
||||
#include "realtek-pse-mcu.h"
|
||||
|
||||
/*
|
||||
* The core has already waited RTPSE_MCU_RESPONSE_MS before calling us, so
|
||||
* the response is normally ready on the very first read. For commands the
|
||||
* MCU produces more slowly, keep polling at the typical response cadence
|
||||
* up to the worst-case ceiling.
|
||||
*/
|
||||
#define RTPSE_MCU_I2C_RETRY_MS RTPSE_MCU_RESPONSE_MS
|
||||
#define RTPSE_MCU_I2C_MAX_TRIES (RTPSE_MCU_RESPONSE_MAX_MS / RTPSE_MCU_I2C_RETRY_MS)
|
||||
|
||||
static int rtpse_mcu_i2c_smbus_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(pse->dev);
|
||||
|
||||
/* Send opcode as SMBus command byte; remaining 11 bytes as block data */
|
||||
return i2c_smbus_write_i2c_block_data(client, req->opcode, RTPSE_MCU_MSG_SIZE - 1,
|
||||
(const u8 *)req + 1);
|
||||
}
|
||||
|
||||
static int rtpse_mcu_i2c_smbus_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
|
||||
struct rtpse_mcu_msg *resp)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(pse->dev);
|
||||
int tries, ret;
|
||||
|
||||
for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
|
||||
if (tries > 0)
|
||||
msleep(RTPSE_MCU_I2C_RETRY_MS);
|
||||
|
||||
/* MCU needs 0x00 as command byte for read */
|
||||
ret = i2c_smbus_read_i2c_block_data(client, 0x00,
|
||||
RTPSE_MCU_MSG_SIZE,
|
||||
(u8 *)resp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_smbus_ops = {
|
||||
.send = rtpse_mcu_i2c_smbus_send,
|
||||
.recv = rtpse_mcu_i2c_smbus_recv,
|
||||
};
|
||||
|
||||
static int rtpse_mcu_i2c_native_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(pse->dev);
|
||||
int ret;
|
||||
|
||||
ret = i2c_master_send(client, (const u8 *)req, RTPSE_MCU_MSG_SIZE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return ret == RTPSE_MCU_MSG_SIZE ? 0 : -EIO;
|
||||
}
|
||||
|
||||
static int rtpse_mcu_i2c_native_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
|
||||
struct rtpse_mcu_msg *resp)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(pse->dev);
|
||||
int tries, ret;
|
||||
|
||||
for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
|
||||
if (tries > 0)
|
||||
msleep(RTPSE_MCU_I2C_RETRY_MS);
|
||||
|
||||
ret = i2c_master_recv(client, (u8 *)resp, RTPSE_MCU_MSG_SIZE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_native_ops = {
|
||||
.send = rtpse_mcu_i2c_native_send,
|
||||
.recv = rtpse_mcu_i2c_native_recv,
|
||||
};
|
||||
|
||||
static int rtpse_mcu_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
const struct rtpse_mcu_match_data *match;
|
||||
struct rtpse_mcu_ctrl *pse;
|
||||
bool use_native;
|
||||
|
||||
match = device_get_match_data(dev);
|
||||
if (!match)
|
||||
return dev_err_probe(dev, -ENODEV, "missing match data\n");
|
||||
|
||||
/* The framing (raw I2C vs SMBus) is carried by the match data. */
|
||||
use_native = match->native_i2c;
|
||||
if (use_native) {
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
|
||||
return dev_err_probe(dev, -EOPNOTSUPP,
|
||||
"plain-I2C MCU protocol requires I2C-capable adapter\n");
|
||||
} else {
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK |
|
||||
I2C_FUNC_SMBUS_READ_I2C_BLOCK))
|
||||
return dev_err_probe(dev, -EOPNOTSUPP,
|
||||
"SMBus MCU protocol requires SMBus I2C-block support\n");
|
||||
}
|
||||
|
||||
pse = devm_kzalloc(dev, sizeof(*pse), GFP_KERNEL);
|
||||
if (!pse)
|
||||
return -ENOMEM;
|
||||
|
||||
pse->dev = dev;
|
||||
pse->pcdev.owner = THIS_MODULE;
|
||||
pse->transport = use_native ? &rtpse_mcu_i2c_native_ops : &rtpse_mcu_i2c_smbus_ops;
|
||||
|
||||
return rtpse_mcu_register(pse);
|
||||
}
|
||||
|
||||
static const struct of_device_id rtpse_mcu_i2c_of_match[] = {
|
||||
{ .compatible = "realtek,pse-mcu-gen1-smbus", .data = &rtpse_mcu_gen1_data },
|
||||
{ .compatible = "realtek,pse-mcu-gen2-smbus", .data = &rtpse_mcu_gen2_data },
|
||||
{ .compatible = "realtek,pse-mcu-gen2-i2c", .data = &rtpse_mcu_gen2_i2c_data },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rtpse_mcu_i2c_of_match);
|
||||
|
||||
static struct i2c_driver rtpse_mcu_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "realtek-pse-mcu-i2c",
|
||||
.of_match_table = rtpse_mcu_i2c_of_match,
|
||||
},
|
||||
.probe = rtpse_mcu_i2c_probe,
|
||||
};
|
||||
module_i2c_driver(rtpse_mcu_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
|
||||
MODULE_DESCRIPTION("Realtek PSE MCU driver (I2C transport)");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user