mirror of
https://github.com/torvalds/linux.git
synced 2024-11-21 19:46:16 +00:00
d83e13761d
Integrate the regulator framework to the PSE framework for enhanced access to features such as voltage, power measurement, and limits, which are akin to regulators. Additionally, PSE features like port priorities could potentially enhance the regulator framework. Note that this integration introduces some implementation complexity, including wrapper callbacks, but the potential benefits make it worthwhile. Regulator are using enable counter with specific behavior. Two calls to regulator_disable will trigger kernel warnings. If the counter exceeds one, regulator_disable call won't disable the PSE PI. These behavior isn't suitable for PSE control. Added a boolean 'enabled' state to prevent multiple calls to regulator_enable/disable. These calls will only be called from PSE framework as it won't have any regulator children, therefore no mutex are needed to safeguards this boolean. regulator_get needs the consumer device pointer. Use PSE as regulator provider and consumer device until we have RJ45 ports represented in the Kernel. Signed-off-by: Kory Maincent <kory.maincent@bootlin.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20240417-feature_poe-v9-10-242293fd1900@bootlin.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
155 lines
3.6 KiB
C
155 lines
3.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
//
|
|
// Driver for the regulator based Ethernet Power Sourcing Equipment, without
|
|
// auto classification support.
|
|
//
|
|
// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
|
|
//
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pse-pd/pse.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
struct pse_reg_priv {
|
|
struct pse_controller_dev pcdev;
|
|
struct regulator *ps; /*power source */
|
|
enum ethtool_podl_pse_admin_state admin_state;
|
|
};
|
|
|
|
static struct pse_reg_priv *to_pse_reg(struct pse_controller_dev *pcdev)
|
|
{
|
|
return container_of(pcdev, struct pse_reg_priv, pcdev);
|
|
}
|
|
|
|
static int
|
|
pse_reg_pi_enable(struct pse_controller_dev *pcdev, int id)
|
|
{
|
|
struct pse_reg_priv *priv = to_pse_reg(pcdev);
|
|
int ret;
|
|
|
|
ret = regulator_enable(priv->ps);
|
|
if (ret)
|
|
return ret;
|
|
|
|
priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
pse_reg_pi_disable(struct pse_controller_dev *pcdev, int id)
|
|
{
|
|
struct pse_reg_priv *priv = to_pse_reg(pcdev);
|
|
int ret;
|
|
|
|
ret = regulator_disable(priv->ps);
|
|
if (ret)
|
|
return ret;
|
|
|
|
priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
pse_reg_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
|
|
{
|
|
struct pse_reg_priv *priv = to_pse_reg(pcdev);
|
|
|
|
return regulator_is_enabled(priv->ps);
|
|
}
|
|
|
|
static int
|
|
pse_reg_ethtool_get_status(struct pse_controller_dev *pcdev, unsigned long id,
|
|
struct netlink_ext_ack *extack,
|
|
struct pse_control_status *status)
|
|
{
|
|
struct pse_reg_priv *priv = to_pse_reg(pcdev);
|
|
int ret;
|
|
|
|
ret = regulator_is_enabled(priv->ps);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (!ret)
|
|
status->podl_pw_status = ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED;
|
|
else
|
|
status->podl_pw_status =
|
|
ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING;
|
|
|
|
status->podl_admin_state = priv->admin_state;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pse_controller_ops pse_reg_ops = {
|
|
.ethtool_get_status = pse_reg_ethtool_get_status,
|
|
.pi_enable = pse_reg_pi_enable,
|
|
.pi_is_enabled = pse_reg_pi_is_enabled,
|
|
.pi_disable = pse_reg_pi_disable,
|
|
};
|
|
|
|
static int
|
|
pse_reg_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct pse_reg_priv *priv;
|
|
int ret;
|
|
|
|
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
if (!pdev->dev.of_node)
|
|
return -ENOENT;
|
|
|
|
priv->ps = devm_regulator_get_exclusive(dev, "pse");
|
|
if (IS_ERR(priv->ps))
|
|
return dev_err_probe(dev, PTR_ERR(priv->ps),
|
|
"failed to get PSE regulator.\n");
|
|
|
|
platform_set_drvdata(pdev, priv);
|
|
|
|
ret = regulator_is_enabled(priv->ps);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret)
|
|
priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
|
|
else
|
|
priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED;
|
|
|
|
priv->pcdev.owner = THIS_MODULE;
|
|
priv->pcdev.ops = &pse_reg_ops;
|
|
priv->pcdev.dev = dev;
|
|
priv->pcdev.types = ETHTOOL_PSE_PODL;
|
|
ret = devm_pse_controller_register(dev, &priv->pcdev);
|
|
if (ret) {
|
|
dev_err(dev, "failed to register PSE controller (%pe)\n",
|
|
ERR_PTR(ret));
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const __maybe_unused struct of_device_id pse_reg_of_match[] = {
|
|
{ .compatible = "podl-pse-regulator", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, pse_reg_of_match);
|
|
|
|
static struct platform_driver pse_reg_driver = {
|
|
.probe = pse_reg_probe,
|
|
.driver = {
|
|
.name = "PSE regulator",
|
|
.of_match_table = of_match_ptr(pse_reg_of_match),
|
|
},
|
|
};
|
|
module_platform_driver(pse_reg_driver);
|
|
|
|
MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>");
|
|
MODULE_DESCRIPTION("regulator based Ethernet Power Sourcing Equipment");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:pse-regulator");
|