#include <linux/device.h>
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/pse-pd/pse.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/rtnetlink.h>
#include <net/net_trackers.h>
#define PSE_PW_D_LIMIT INT_MAX
static DEFINE_MUTEX(pse_list_mutex);
static LIST_HEAD(pse_controller_list);
static DEFINE_XARRAY_ALLOC(pse_pw_d_map);
static DEFINE_MUTEX(pse_pw_d_mutex);
struct pse_control {
struct pse_controller_dev *pcdev;
struct regulator *ps;
struct list_head list;
unsigned int id;
struct kref refcnt;
struct phy_device *attached_phydev;
};
struct pse_power_domain {
int id;
struct regulator *supply;
struct kref refcnt;
u32 budget_eval_strategy;
};
static int of_load_single_pse_pi_pairset(struct device_node *node,
struct pse_pi *pi,
int pairset_num)
{
struct device_node *pairset_np;
const char *name;
int ret;
ret = of_property_read_string_index(node, "pairset-names",
pairset_num, &name);
if (ret)
return ret;
if (!strcmp(name, "alternative-a")) {
pi->pairset[pairset_num].pinout = ALTERNATIVE_A;
} else if (!strcmp(name, "alternative-b")) {
pi->pairset[pairset_num].pinout = ALTERNATIVE_B;
} else {
pr_err("pse: wrong pairset-names value %s (%pOF)\n",
name, node);
return -EINVAL;
}
pairset_np = of_parse_phandle(node, "pairsets", pairset_num);
if (!pairset_np)
return -ENODEV;
pi->pairset[pairset_num].np = pairset_np;
return 0;
}
static int of_load_pse_pi_pairsets(struct device_node *node,
struct pse_pi *pi,
int npairsets)
{
int i, ret;
ret = of_property_count_strings(node, "pairset-names");
if (ret != npairsets) {
pr_err("pse: amount of pairsets and pairset-names is not equal %d != %d (%pOF)\n",
npairsets, ret, node);
return -EINVAL;
}
for (i = 0; i < npairsets; i++) {
ret = of_load_single_pse_pi_pairset(node, pi, i);
if (ret)
goto out;
}
if (npairsets == 2 &&
pi->pairset[0].pinout == pi->pairset[1].pinout) {
pr_err("pse: two PI pairsets can not have identical pinout (%pOF)",
node);
ret = -EINVAL;
}
out:
if (ret) {
of_node_put(pi->pairset[0].np);
pi->pairset[0].np = NULL;
of_node_put(pi->pairset[1].np);
pi->pairset[1].np = NULL;
}
return ret;
}
static void pse_release_pis(struct pse_controller_dev *pcdev)
{
int i;
for (i = 0; i < pcdev->nr_lines; i++) {
of_node_put(pcdev->pi[i].pairset[0].np);
of_node_put(pcdev->pi[i].pairset[1].np);
of_node_put(pcdev->pi[i].np);
}
kfree(pcdev->pi);
}
static int of_load_pse_pis(struct pse_controller_dev *pcdev)
{
struct device_node *np = pcdev->dev->of_node;
struct device_node *node, *pis;
int ret;
if (!np)
return -ENODEV;
pcdev->pi = kzalloc_objs(*pcdev->pi, pcdev->nr_lines);
if (!pcdev->pi)
return -ENOMEM;
pis = of_get_child_by_name(np, "pse-pis");
if (!pis) {
pcdev->no_of_pse_pi = true;
return 0;
}
for_each_child_of_node(pis, node) {
struct pse_pi pi = {0};
u32 id;
if (!of_node_name_eq(node, "pse-pi"))
continue;
ret = of_property_read_u32(node, "reg", &id);
if (ret) {
dev_err(pcdev->dev,
"can't get reg property for node '%pOF'",
node);
goto out;
}
if (id >= pcdev->nr_lines) {
dev_err(pcdev->dev,
"reg value (%u) is out of range (%u) (%pOF)\n",
id, pcdev->nr_lines, node);
ret = -EINVAL;
goto out;
}
if (pcdev->pi[id].np) {
dev_err(pcdev->dev,
"other node with same reg value was already registered. %pOF : %pOF\n",
pcdev->pi[id].np, node);
ret = -EINVAL;
goto out;
}
ret = of_count_phandle_with_args(node, "pairsets", NULL);
if (ret == 1 || ret == 2) {
ret = of_load_pse_pi_pairsets(node, &pi, ret);
if (ret)
goto out;
} else if (ret != ENOENT) {
dev_err(pcdev->dev,
"error: wrong number of pairsets. Should be 1 or 2, got %d (%pOF)\n",
ret, node);
ret = -EINVAL;
goto out;
}
of_node_get(node);
pi.np = node;
memcpy(&pcdev->pi[id], &pi, sizeof(pi));
}
of_node_put(pis);
return 0;
out:
pse_release_pis(pcdev);
of_node_put(node);
of_node_put(pis);
return ret;
}
static struct pse_control *
pse_control_find_by_id(struct pse_controller_dev *pcdev, int id)
{
struct pse_control *psec;
mutex_lock(&pse_list_mutex);
list_for_each_entry(psec, &pcdev->pse_control_head, list) {
if (psec->id == id) {
kref_get(&psec->refcnt);
mutex_unlock(&pse_list_mutex);
return psec;
}
}
mutex_unlock(&pse_list_mutex);
return NULL;
}
static struct net_device *pse_control_get_netdev(struct pse_control *psec)
{
ASSERT_RTNL();
if (!psec || !psec->attached_phydev)
return NULL;
return psec->attached_phydev->attached_dev;
}
static int pse_pi_is_hw_enabled(struct pse_controller_dev *pcdev, int id)
{
struct pse_admin_state admin_state = {0};
int ret;
ret = pcdev->ops->pi_get_admin_state(pcdev, id, &admin_state);
if (ret < 0)
return ret;
if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED ||
admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
return 1;
return 0;
}
static bool
pse_pi_is_admin_enable_pending(struct pse_controller_dev *pcdev, int id)
{
int ret;
if (!pcdev->pi[id].admin_state_enabled ||
!pcdev->pi[id].isr_pd_detected)
return false;
ret = pse_pi_is_hw_enabled(pcdev, id);
if (ret == 1)
return false;
return true;
}
static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev,
int id,
struct netlink_ext_ack *extack);
static void pse_pw_d_retry_power_delivery(struct pse_controller_dev *pcdev,
struct pse_power_domain *pw_d)
{
int i, ret = 0;
for (i = 0; i < pcdev->nr_lines; i++) {
int prio_max = pcdev->nr_lines;
struct netlink_ext_ack extack;
if (pcdev->pi[i].pw_d != pw_d)
continue;
if (!pse_pi_is_admin_enable_pending(pcdev, i))
continue;
if (pcdev->pi[i].prio > prio_max)
continue;
ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, i, &extack);
if (ret == -ERANGE)
prio_max = pcdev->pi[i].prio;
}
}
static bool pse_pw_d_is_sw_pw_control(struct pse_controller_dev *pcdev,
struct pse_power_domain *pw_d)
{
if (!pw_d)
return false;
if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC)
return true;
if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_DISABLED &&
pcdev->ops->pi_enable && pcdev->irq)
return true;
return false;
}
static int pse_pi_is_enabled(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
const struct pse_controller_ops *ops;
int id, ret;
ops = pcdev->ops;
if (!ops->pi_get_admin_state)
return -EOPNOTSUPP;
id = rdev_get_id(rdev);
mutex_lock(&pcdev->lock);
if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
ret = pcdev->pi[id].admin_state_enabled;
goto out;
}
ret = pse_pi_is_hw_enabled(pcdev, id);
out:
mutex_unlock(&pcdev->lock);
return ret;
}
static void pse_pi_deallocate_pw_budget(struct pse_pi *pi)
{
if (!pi->pw_d || !pi->pw_allocated_mW)
return;
regulator_free_power_budget(pi->pw_d->supply, pi->pw_allocated_mW);
pi->pw_allocated_mW = 0;
}
static int _pse_pi_disable(struct pse_controller_dev *pcdev, int id)
{
const struct pse_controller_ops *ops = pcdev->ops;
int ret;
if (!ops->pi_disable)
return -EOPNOTSUPP;
ret = ops->pi_disable(pcdev, id);
if (ret)
return ret;
pse_pi_deallocate_pw_budget(&pcdev->pi[id]);
if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d))
pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[id].pw_d);
return 0;
}
static int pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id)
{
unsigned long notifs = ETHTOOL_PSE_EVENT_OVER_BUDGET;
struct pse_ntf ntf = {};
int ret;
dev_dbg(pcdev->dev, "Disabling PI %d to free power budget\n", id);
ret = _pse_pi_disable(pcdev, id);
if (ret)
notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
ntf.notifs = notifs;
ntf.id = id;
kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, &pcdev->ntf_fifo_lock);
schedule_work(&pcdev->ntf_work);
return ret;
}
static int pse_disable_pi_prio(struct pse_controller_dev *pcdev,
struct pse_power_domain *pw_d,
int prio)
{
int i;
for (i = 0; i < pcdev->nr_lines; i++) {
int ret;
if (pcdev->pi[i].prio != prio ||
pcdev->pi[i].pw_d != pw_d ||
pse_pi_is_hw_enabled(pcdev, i) <= 0)
continue;
ret = pse_disable_pi_pol(pcdev, i);
if (ret)
return ret;
}
return 0;
}
static int
pse_pi_allocate_pw_budget_static_prio(struct pse_controller_dev *pcdev, int id,
int pw_req, struct netlink_ext_ack *extack)
{
struct pse_pi *pi = &pcdev->pi[id];
int ret, _prio;
_prio = pcdev->nr_lines;
while (regulator_request_power_budget(pi->pw_d->supply, pw_req) == -ERANGE) {
if (_prio <= pi->prio) {
NL_SET_ERR_MSG_FMT(extack,
"PI %d: not enough power budget available",
id);
return -ERANGE;
}
ret = pse_disable_pi_prio(pcdev, pi->pw_d, _prio);
if (ret < 0)
return ret;
_prio--;
}
pi->pw_allocated_mW = pw_req;
return 0;
}
static int pse_pi_allocate_pw_budget(struct pse_controller_dev *pcdev, int id,
int pw_req, struct netlink_ext_ack *extack)
{
struct pse_pi *pi = &pcdev->pi[id];
if (!pi->pw_d)
return 0;
if (pi->pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC)
return pse_pi_allocate_pw_budget_static_prio(pcdev, id, pw_req,
extack);
return 0;
}
static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev,
int id,
struct netlink_ext_ack *extack)
{
const struct pse_controller_ops *ops = pcdev->ops;
struct pse_pi *pi = &pcdev->pi[id];
int ret, pw_req;
if (!ops->pi_get_pw_req) {
ret = ops->pi_enable(pcdev, id);
if (ret)
NL_SET_ERR_MSG_FMT(extack,
"PI %d: enable error %d",
id, ret);
return ret;
}
ret = ops->pi_get_pw_req(pcdev, id);
if (ret < 0)
return ret;
pw_req = ret;
if (ops->pi_get_pw_limit) {
ret = ops->pi_get_pw_limit(pcdev, id);
if (ret < 0)
return ret;
if (ret < pw_req)
pw_req = ret;
}
ret = pse_pi_allocate_pw_budget(pcdev, id, pw_req, extack);
if (ret)
return ret;
ret = ops->pi_enable(pcdev, id);
if (ret) {
pse_pi_deallocate_pw_budget(pi);
NL_SET_ERR_MSG_FMT(extack,
"PI %d: enable error %d",
id, ret);
return ret;
}
return 0;
}
static int pse_pi_enable(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
const struct pse_controller_ops *ops;
int id, ret = 0;
ops = pcdev->ops;
if (!ops->pi_enable)
return -EOPNOTSUPP;
id = rdev_get_id(rdev);
mutex_lock(&pcdev->lock);
if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
if (pcdev->pi[id].isr_pd_detected) {
struct netlink_ext_ack extack;
ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, &extack);
}
if (!ret || ret == -ERANGE) {
pcdev->pi[id].admin_state_enabled = 1;
ret = 0;
}
mutex_unlock(&pcdev->lock);
return ret;
}
ret = ops->pi_enable(pcdev, id);
if (!ret)
pcdev->pi[id].admin_state_enabled = 1;
mutex_unlock(&pcdev->lock);
return ret;
}
static int pse_pi_disable(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
struct pse_pi *pi;
int id, ret;
id = rdev_get_id(rdev);
pi = &pcdev->pi[id];
mutex_lock(&pcdev->lock);
ret = _pse_pi_disable(pcdev, id);
if (!ret)
pi->admin_state_enabled = 0;
mutex_unlock(&pcdev->lock);
return 0;
}
static int _pse_pi_get_voltage(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
const struct pse_controller_ops *ops;
int id;
ops = pcdev->ops;
if (!ops->pi_get_voltage)
return -EOPNOTSUPP;
id = rdev_get_id(rdev);
return ops->pi_get_voltage(pcdev, id);
}
static int pse_pi_get_voltage(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
int ret;
mutex_lock(&pcdev->lock);
ret = _pse_pi_get_voltage(rdev);
mutex_unlock(&pcdev->lock);
return ret;
}
static int pse_pi_get_current_limit(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
const struct pse_controller_ops *ops;
int id, uV, mW, ret;
s64 tmp_64;
ops = pcdev->ops;
id = rdev_get_id(rdev);
if (!ops->pi_get_pw_limit || !ops->pi_get_voltage)
return -EOPNOTSUPP;
mutex_lock(&pcdev->lock);
ret = ops->pi_get_pw_limit(pcdev, id);
if (ret < 0)
goto out;
mW = ret;
ret = _pse_pi_get_voltage(rdev);
if (!ret) {
dev_err(pcdev->dev, "Voltage null\n");
ret = -ERANGE;
goto out;
}
if (ret < 0)
goto out;
uV = ret;
tmp_64 = mW;
tmp_64 *= 1000000000ull;
ret = DIV_ROUND_CLOSEST_ULL(tmp_64, uV);
out:
mutex_unlock(&pcdev->lock);
return ret;
}
static int pse_pi_set_current_limit(struct regulator_dev *rdev, int min_uA,
int max_uA)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
const struct pse_controller_ops *ops;
int id, mW, ret;
s64 tmp_64;
ops = pcdev->ops;
if (!ops->pi_set_pw_limit || !ops->pi_get_voltage)
return -EOPNOTSUPP;
if (max_uA > MAX_PI_CURRENT)
return -ERANGE;
id = rdev_get_id(rdev);
mutex_lock(&pcdev->lock);
ret = _pse_pi_get_voltage(rdev);
if (!ret) {
dev_err(pcdev->dev, "Voltage null\n");
ret = -ERANGE;
goto out;
}
if (ret < 0)
goto out;
tmp_64 = ret;
tmp_64 *= max_uA;
mW = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
ret = ops->pi_set_pw_limit(pcdev, id, mW);
out:
mutex_unlock(&pcdev->lock);
return ret;
}
static const struct regulator_ops pse_pi_ops = {
.is_enabled = pse_pi_is_enabled,
.enable = pse_pi_enable,
.disable = pse_pi_disable,
.get_voltage = pse_pi_get_voltage,
.get_current_limit = pse_pi_get_current_limit,
.set_current_limit = pse_pi_set_current_limit,
};
static int
devm_pse_pi_regulator_register(struct pse_controller_dev *pcdev,
char *name, int id)
{
struct regulator_init_data *rinit_data;
struct regulator_config rconfig = {0};
struct regulator_desc *rdesc;
struct regulator_dev *rdev;
rinit_data = devm_kzalloc(pcdev->dev, sizeof(*rinit_data),
GFP_KERNEL);
if (!rinit_data)
return -ENOMEM;
rdesc = devm_kzalloc(pcdev->dev, sizeof(*rdesc), GFP_KERNEL);
if (!rdesc)
return -ENOMEM;
rdesc->id = id;
rdesc->name = name;
rdesc->type = REGULATOR_VOLTAGE;
rdesc->ops = &pse_pi_ops;
rdesc->owner = pcdev->owner;
rinit_data->constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS;
if (pcdev->ops->pi_set_pw_limit)
rinit_data->constraints.valid_ops_mask |=
REGULATOR_CHANGE_CURRENT;
rinit_data->supply_regulator = "vpwr";
rconfig.dev = pcdev->dev;
rconfig.driver_data = pcdev;
rconfig.init_data = rinit_data;
rconfig.of_node = pcdev->pi[id].np;
rdev = devm_regulator_register(pcdev->dev, rdesc, &rconfig);
if (IS_ERR(rdev)) {
dev_err_probe(pcdev->dev, PTR_ERR(rdev),
"Failed to register regulator\n");
return PTR_ERR(rdev);
}
pcdev->pi[id].rdev = rdev;
return 0;
}
static void __pse_pw_d_release(struct kref *kref)
{
struct pse_power_domain *pw_d = container_of(kref,
struct pse_power_domain,
refcnt);
regulator_put(pw_d->supply);
xa_erase(&pse_pw_d_map, pw_d->id);
mutex_unlock(&pse_pw_d_mutex);
}
static void pse_flush_pw_ds(struct pse_controller_dev *pcdev)
{
struct pse_power_domain *pw_d;
int i;
for (i = 0; i < pcdev->nr_lines; i++) {
if (!pcdev->pi[i].pw_d)
continue;
pw_d = xa_load(&pse_pw_d_map, pcdev->pi[i].pw_d->id);
if (!pw_d)
continue;
kref_put_mutex(&pw_d->refcnt, __pse_pw_d_release,
&pse_pw_d_mutex);
}
}
static struct pse_power_domain *devm_pse_alloc_pw_d(struct device *dev)
{
struct pse_power_domain *pw_d;
int index, ret;
pw_d = devm_kzalloc(dev, sizeof(*pw_d), GFP_KERNEL);
if (!pw_d)
return ERR_PTR(-ENOMEM);
ret = xa_alloc(&pse_pw_d_map, &index, pw_d, XA_LIMIT(1, PSE_PW_D_LIMIT),
GFP_KERNEL);
if (ret)
return ERR_PTR(ret);
kref_init(&pw_d->refcnt);
pw_d->id = index;
return pw_d;
}
static int pse_register_pw_ds(struct pse_controller_dev *pcdev)
{
int i, ret = 0;
mutex_lock(&pse_pw_d_mutex);
for (i = 0; i < pcdev->nr_lines; i++) {
struct regulator_dev *rdev = pcdev->pi[i].rdev;
struct pse_power_domain *pw_d;
struct regulator *supply;
bool present = false;
unsigned long index;
if (!rdev || !rdev->supply)
continue;
xa_for_each(&pse_pw_d_map, index, pw_d) {
if (regulator_is_equal(pw_d->supply, rdev->supply)) {
present = true;
pcdev->pi[i].pw_d = pw_d;
break;
}
}
if (present) {
kref_get(&pw_d->refcnt);
continue;
}
pw_d = devm_pse_alloc_pw_d(pcdev->dev);
if (IS_ERR(pw_d)) {
ret = PTR_ERR(pw_d);
goto out;
}
supply = regulator_get(&rdev->dev, rdev->supply_name);
if (IS_ERR(supply)) {
xa_erase(&pse_pw_d_map, pw_d->id);
ret = PTR_ERR(supply);
goto out;
}
pw_d->supply = supply;
if (pcdev->supp_budget_eval_strategies)
pw_d->budget_eval_strategy = pcdev->supp_budget_eval_strategies;
else
pw_d->budget_eval_strategy = PSE_BUDGET_EVAL_STRAT_DISABLED;
kref_init(&pw_d->refcnt);
pcdev->pi[i].pw_d = pw_d;
}
out:
mutex_unlock(&pse_pw_d_mutex);
return ret;
}
static void pse_send_ntf_worker(struct work_struct *work)
{
struct pse_controller_dev *pcdev;
struct pse_ntf ntf;
pcdev = container_of(work, struct pse_controller_dev, ntf_work);
while (kfifo_out(&pcdev->ntf_fifo, &ntf, 1)) {
struct net_device *netdev;
struct pse_control *psec;
psec = pse_control_find_by_id(pcdev, ntf.id);
rtnl_lock();
netdev = pse_control_get_netdev(psec);
if (netdev)
ethnl_pse_send_ntf(netdev, ntf.notifs);
rtnl_unlock();
pse_control_put(psec);
}
}
int pse_controller_register(struct pse_controller_dev *pcdev)
{
size_t reg_name_len;
int ret, i;
mutex_init(&pcdev->lock);
INIT_LIST_HEAD(&pcdev->pse_control_head);
spin_lock_init(&pcdev->ntf_fifo_lock);
ret = kfifo_alloc(&pcdev->ntf_fifo, pcdev->nr_lines, GFP_KERNEL);
if (ret) {
dev_err(pcdev->dev, "failed to allocate kfifo notifications\n");
return ret;
}
INIT_WORK(&pcdev->ntf_work, pse_send_ntf_worker);
if (!pcdev->nr_lines)
pcdev->nr_lines = 1;
if (!pcdev->ops->pi_get_admin_state ||
!pcdev->ops->pi_get_pw_status) {
dev_err(pcdev->dev,
"Mandatory status report callbacks are missing");
return -EINVAL;
}
ret = of_load_pse_pis(pcdev);
if (ret)
return ret;
if (pcdev->ops->setup_pi_matrix) {
ret = pcdev->ops->setup_pi_matrix(pcdev);
if (ret)
return ret;
}
reg_name_len = strlen(dev_name(pcdev->dev)) + 18;
for (i = 0; i < pcdev->nr_lines; i++) {
char *reg_name;
if (!pcdev->no_of_pse_pi && !pcdev->pi[i].np)
continue;
reg_name = devm_kzalloc(pcdev->dev, reg_name_len, GFP_KERNEL);
if (!reg_name)
return -ENOMEM;
snprintf(reg_name, reg_name_len, "pse-%s_pi%d",
dev_name(pcdev->dev), i);
ret = devm_pse_pi_regulator_register(pcdev, reg_name, i);
if (ret)
return ret;
}
ret = pse_register_pw_ds(pcdev);
if (ret)
return ret;
mutex_lock(&pse_list_mutex);
list_add(&pcdev->list, &pse_controller_list);
mutex_unlock(&pse_list_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(pse_controller_register);
void pse_controller_unregister(struct pse_controller_dev *pcdev)
{
pse_flush_pw_ds(pcdev);
pse_release_pis(pcdev);
if (pcdev->irq)
disable_irq(pcdev->irq);
cancel_work_sync(&pcdev->ntf_work);
kfifo_free(&pcdev->ntf_fifo);
mutex_lock(&pse_list_mutex);
list_del(&pcdev->list);
mutex_unlock(&pse_list_mutex);
}
EXPORT_SYMBOL_GPL(pse_controller_unregister);
static void devm_pse_controller_release(struct device *dev, void *res)
{
pse_controller_unregister(*(struct pse_controller_dev **)res);
}
int devm_pse_controller_register(struct device *dev,
struct pse_controller_dev *pcdev)
{
struct pse_controller_dev **pcdevp;
int ret;
pcdevp = devres_alloc(devm_pse_controller_release, sizeof(*pcdevp),
GFP_KERNEL);
if (!pcdevp)
return -ENOMEM;
ret = pse_controller_register(pcdev);
if (ret) {
devres_free(pcdevp);
return ret;
}
*pcdevp = pcdev;
devres_add(dev, pcdevp);
return 0;
}
EXPORT_SYMBOL_GPL(devm_pse_controller_register);
struct pse_irq {
struct pse_controller_dev *pcdev;
struct pse_irq_desc desc;
unsigned long *notifs;
};
static unsigned long pse_to_regulator_notifs(unsigned long notifs)
{
unsigned long rnotifs = 0;
if (notifs & ETHTOOL_PSE_EVENT_OVER_CURRENT)
rnotifs |= REGULATOR_EVENT_OVER_CURRENT;
if (notifs & ETHTOOL_PSE_EVENT_OVER_TEMP)
rnotifs |= REGULATOR_EVENT_OVER_TEMP;
return rnotifs;
}
static int pse_set_config_isr(struct pse_controller_dev *pcdev, int id,
unsigned long notifs)
{
int ret = 0;
if (notifs & PSE_BUDGET_EVAL_STRAT_DYNAMIC)
return 0;
if ((notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) &&
((notifs & ETHTOOL_C33_PSE_EVENT_DETECTION) ||
(notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION))) {
dev_dbg(pcdev->dev,
"PI %d: error, connection and disconnection reported simultaneously",
id);
return -EINVAL;
}
if (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION) {
struct netlink_ext_ack extack;
pcdev->pi[id].isr_pd_detected = true;
if (pcdev->pi[id].admin_state_enabled) {
ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id,
&extack);
if (ret == -ERANGE)
ret = 0;
}
} else if (notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) {
if (pcdev->pi[id].admin_state_enabled &&
pcdev->pi[id].isr_pd_detected)
ret = _pse_pi_disable(pcdev, id);
pcdev->pi[id].isr_pd_detected = false;
}
return ret;
}
static irqreturn_t pse_isr(int irq, void *data)
{
struct pse_controller_dev *pcdev;
unsigned long notifs_mask = 0;
struct pse_irq_desc *desc;
struct pse_irq *h = data;
int ret, i;
desc = &h->desc;
pcdev = h->pcdev;
memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs));
mutex_lock(&pcdev->lock);
ret = desc->map_event(irq, pcdev, h->notifs, ¬ifs_mask);
if (ret || !notifs_mask) {
mutex_unlock(&pcdev->lock);
return IRQ_NONE;
}
for_each_set_bit(i, ¬ifs_mask, pcdev->nr_lines) {
unsigned long notifs, rnotifs;
struct pse_ntf ntf = {};
if (!pcdev->pi[i].rdev)
continue;
notifs = h->notifs[i];
if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[i].pw_d)) {
ret = pse_set_config_isr(pcdev, i, notifs);
if (ret)
notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
}
dev_dbg(h->pcdev->dev,
"Sending PSE notification EVT 0x%lx\n", notifs);
ntf.notifs = notifs;
ntf.id = i;
kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1,
&pcdev->ntf_fifo_lock);
schedule_work(&pcdev->ntf_work);
rnotifs = pse_to_regulator_notifs(notifs);
regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs,
NULL);
}
mutex_unlock(&pcdev->lock);
return IRQ_HANDLED;
}
int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
int irq_flags, const struct pse_irq_desc *d)
{
struct device *dev = pcdev->dev;
size_t irq_name_len;
struct pse_irq *h;
char *irq_name;
int ret;
if (!d || !d->map_event || !d->name)
return -EINVAL;
h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
if (!h)
return -ENOMEM;
h->pcdev = pcdev;
h->desc = *d;
irq_name_len = strlen(dev_name(pcdev->dev)) + 5 + strlen(d->name) + 1;
irq_name = devm_kzalloc(dev, irq_name_len, GFP_KERNEL);
if (!irq_name)
return -ENOMEM;
snprintf(irq_name, irq_name_len, "pse-%s:%s", dev_name(pcdev->dev),
d->name);
h->notifs = devm_kcalloc(dev, pcdev->nr_lines,
sizeof(*h->notifs), GFP_KERNEL);
if (!h->notifs)
return -ENOMEM;
ret = devm_request_threaded_irq(dev, irq, NULL, pse_isr,
IRQF_ONESHOT | irq_flags,
irq_name, h);
if (ret)
dev_err(pcdev->dev, "Failed to request IRQ %d\n", irq);
pcdev->irq = irq;
return ret;
}
EXPORT_SYMBOL_GPL(devm_pse_irq_helper);
static void __pse_control_release(struct kref *kref)
{
struct pse_control *psec = container_of(kref, struct pse_control,
refcnt);
lockdep_assert_held(&pse_list_mutex);
if (psec->pcdev->pi[psec->id].admin_state_enabled)
regulator_disable(psec->ps);
devm_regulator_put(psec->ps);
module_put(psec->pcdev->owner);
list_del(&psec->list);
kfree(psec);
}
static void __pse_control_put_internal(struct pse_control *psec)
{
lockdep_assert_held(&pse_list_mutex);
kref_put(&psec->refcnt, __pse_control_release);
}
void pse_control_put(struct pse_control *psec)
{
if (IS_ERR_OR_NULL(psec))
return;
mutex_lock(&pse_list_mutex);
__pse_control_put_internal(psec);
mutex_unlock(&pse_list_mutex);
}
EXPORT_SYMBOL_GPL(pse_control_put);
static struct pse_control *
pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index,
struct phy_device *phydev)
{
struct pse_control *psec;
int ret;
lockdep_assert_held(&pse_list_mutex);
list_for_each_entry(psec, &pcdev->pse_control_head, list) {
if (psec->id == index) {
kref_get(&psec->refcnt);
return psec;
}
}
psec = kzalloc_obj(*psec);
if (!psec)
return ERR_PTR(-ENOMEM);
if (!try_module_get(pcdev->owner)) {
ret = -ENODEV;
goto free_psec;
}
if (!pcdev->ops->pi_get_admin_state) {
ret = -EOPNOTSUPP;
goto free_psec;
}
ret = pse_pi_is_hw_enabled(pcdev, index);
if (ret < 0)
goto free_psec;
pcdev->pi[index].admin_state_enabled = ret;
psec->ps = devm_regulator_get_exclusive(pcdev->dev,
rdev_get_name(pcdev->pi[index].rdev));
if (IS_ERR(psec->ps)) {
ret = PTR_ERR(psec->ps);
goto put_module;
}
psec->pcdev = pcdev;
list_add(&psec->list, &pcdev->pse_control_head);
psec->id = index;
psec->attached_phydev = phydev;
kref_init(&psec->refcnt);
return psec;
put_module:
module_put(pcdev->owner);
free_psec:
kfree(psec);
return ERR_PTR(ret);
}
static int of_pse_match_pi(struct pse_controller_dev *pcdev,
struct device_node *np)
{
int i;
for (i = 0; i < pcdev->nr_lines; i++) {
if (pcdev->pi[i].np == np)
return i;
}
return -EINVAL;
}
static int psec_id_xlate(struct pse_controller_dev *pcdev,
const struct of_phandle_args *pse_spec)
{
if (!pcdev->of_pse_n_cells)
return 0;
if (pcdev->of_pse_n_cells > 1 ||
pse_spec->args[0] >= pcdev->nr_lines)
return -EINVAL;
return pse_spec->args[0];
}
struct pse_control *of_pse_control_get(struct device_node *node,
struct phy_device *phydev)
{
struct pse_controller_dev *r, *pcdev;
struct of_phandle_args args;
struct pse_control *psec;
int psec_id;
int ret;
if (!node)
return ERR_PTR(-EINVAL);
ret = of_parse_phandle_with_args(node, "pses", "#pse-cells", 0, &args);
if (ret)
return ERR_PTR(ret);
mutex_lock(&pse_list_mutex);
pcdev = NULL;
list_for_each_entry(r, &pse_controller_list, list) {
if (!r->no_of_pse_pi) {
ret = of_pse_match_pi(r, args.np);
if (ret >= 0) {
pcdev = r;
psec_id = ret;
break;
}
} else if (args.np == r->dev->of_node) {
pcdev = r;
break;
}
}
if (!pcdev) {
psec = ERR_PTR(-EPROBE_DEFER);
goto out;
}
if (WARN_ON(args.args_count != pcdev->of_pse_n_cells)) {
psec = ERR_PTR(-EINVAL);
goto out;
}
if (pcdev->no_of_pse_pi) {
psec_id = psec_id_xlate(pcdev, &args);
if (psec_id < 0) {
psec = ERR_PTR(psec_id);
goto out;
}
}
psec = pse_control_get_internal(pcdev, psec_id, phydev);
out:
mutex_unlock(&pse_list_mutex);
of_node_put(args.np);
return psec;
}
EXPORT_SYMBOL_GPL(of_pse_control_get);
static void pse_get_sw_admin_state(struct pse_control *psec,
struct pse_admin_state *admin_state)
{
struct pse_pi *pi = &psec->pcdev->pi[psec->id];
if (pse_has_podl(psec)) {
if (pi->admin_state_enabled)
admin_state->podl_admin_state =
ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
else
admin_state->podl_admin_state =
ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED;
}
if (pse_has_c33(psec)) {
if (pi->admin_state_enabled)
admin_state->c33_admin_state =
ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
else
admin_state->c33_admin_state =
ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
}
}
int pse_ethtool_get_status(struct pse_control *psec,
struct netlink_ext_ack *extack,
struct ethtool_pse_control_status *status)
{
struct pse_admin_state admin_state = {0};
struct pse_pw_status pw_status = {0};
const struct pse_controller_ops *ops;
struct pse_controller_dev *pcdev;
struct pse_pi *pi;
int ret;
pcdev = psec->pcdev;
ops = pcdev->ops;
pi = &pcdev->pi[psec->id];
mutex_lock(&pcdev->lock);
if (pi->pw_d) {
status->pw_d_id = pi->pw_d->id;
if (pse_pw_d_is_sw_pw_control(pcdev, pi->pw_d)) {
pse_get_sw_admin_state(psec, &admin_state);
} else {
ret = ops->pi_get_admin_state(pcdev, psec->id,
&admin_state);
if (ret)
goto out;
}
status->podl_admin_state = admin_state.podl_admin_state;
status->c33_admin_state = admin_state.c33_admin_state;
switch (pi->pw_d->budget_eval_strategy) {
case PSE_BUDGET_EVAL_STRAT_STATIC:
status->prio_max = pcdev->nr_lines - 1;
status->prio = pi->prio;
break;
case PSE_BUDGET_EVAL_STRAT_DYNAMIC:
status->prio_max = pcdev->pis_prio_max;
if (ops->pi_get_prio) {
ret = ops->pi_get_prio(pcdev, psec->id);
if (ret < 0)
goto out;
status->prio = ret;
}
break;
default:
break;
}
}
ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status);
if (ret)
goto out;
status->podl_pw_status = pw_status.podl_pw_status;
status->c33_pw_status = pw_status.c33_pw_status;
if (ops->pi_get_ext_state) {
struct pse_ext_state_info ext_state_info = {0};
ret = ops->pi_get_ext_state(pcdev, psec->id,
&ext_state_info);
if (ret)
goto out;
memcpy(&status->c33_ext_state_info,
&ext_state_info.c33_ext_state_info,
sizeof(status->c33_ext_state_info));
}
if (ops->pi_get_pw_class) {
ret = ops->pi_get_pw_class(pcdev, psec->id);
if (ret < 0)
goto out;
status->c33_pw_class = ret;
}
if (ops->pi_get_actual_pw) {
ret = ops->pi_get_actual_pw(pcdev, psec->id);
if (ret < 0)
goto out;
status->c33_actual_pw = ret;
}
if (ops->pi_get_pw_limit) {
ret = ops->pi_get_pw_limit(pcdev, psec->id);
if (ret < 0)
goto out;
status->c33_avail_pw_limit = ret;
}
if (ops->pi_get_pw_limit_ranges) {
struct pse_pw_limit_ranges pw_limit_ranges = {0};
ret = ops->pi_get_pw_limit_ranges(pcdev, psec->id,
&pw_limit_ranges);
if (ret < 0)
goto out;
status->c33_pw_limit_ranges =
pw_limit_ranges.c33_pw_limit_ranges;
status->c33_pw_limit_nb_ranges = ret;
}
out:
mutex_unlock(&psec->pcdev->lock);
return ret;
}
EXPORT_SYMBOL_GPL(pse_ethtool_get_status);
static int pse_ethtool_c33_set_config(struct pse_control *psec,
const struct pse_control_config *config)
{
int err = 0;
switch (config->c33_admin_control) {
case ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED:
if (psec->pcdev->pi[psec->id].admin_state_enabled &&
!regulator_is_enabled(psec->ps)) {
err = regulator_disable(psec->ps);
if (err)
break;
}
if (!psec->pcdev->pi[psec->id].admin_state_enabled)
err = regulator_enable(psec->ps);
break;
case ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED:
if (psec->pcdev->pi[psec->id].admin_state_enabled)
err = regulator_disable(psec->ps);
break;
default:
err = -EOPNOTSUPP;
}
return err;
}
static int pse_ethtool_podl_set_config(struct pse_control *psec,
const struct pse_control_config *config)
{
int err = 0;
switch (config->podl_admin_control) {
case ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED:
if (!psec->pcdev->pi[psec->id].admin_state_enabled)
err = regulator_enable(psec->ps);
break;
case ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED:
if (psec->pcdev->pi[psec->id].admin_state_enabled)
err = regulator_disable(psec->ps);
break;
default:
err = -EOPNOTSUPP;
}
return err;
}
int pse_ethtool_set_config(struct pse_control *psec,
struct netlink_ext_ack *extack,
const struct pse_control_config *config)
{
int err = 0;
if (pse_has_c33(psec) && config->c33_admin_control) {
err = pse_ethtool_c33_set_config(psec, config);
if (err)
return err;
}
if (pse_has_podl(psec) && config->podl_admin_control)
err = pse_ethtool_podl_set_config(psec, config);
return err;
}
EXPORT_SYMBOL_GPL(pse_ethtool_set_config);
static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id,
const unsigned int pw_req,
struct netlink_ext_ack *extack)
{
struct pse_pi *pi = &pcdev->pi[id];
int previous_pw_allocated;
int pw_diff, ret = 0;
mutex_lock(&pcdev->lock);
previous_pw_allocated = pi->pw_allocated_mW;
pw_diff = pw_req - previous_pw_allocated;
if (!pw_diff) {
goto out;
} else if (pw_diff > 0) {
ret = regulator_request_power_budget(pi->pw_d->supply, pw_diff);
if (ret) {
NL_SET_ERR_MSG_FMT(extack,
"PI %d: not enough power budget available",
id);
goto out;
}
} else {
regulator_free_power_budget(pi->pw_d->supply, -pw_diff);
}
pi->pw_allocated_mW = pw_req;
ret = previous_pw_allocated;
out:
mutex_unlock(&pcdev->lock);
return ret;
}
int pse_ethtool_set_pw_limit(struct pse_control *psec,
struct netlink_ext_ack *extack,
const unsigned int pw_limit)
{
int uV, uA, ret, previous_pw_allocated = 0;
s64 tmp_64;
if (pw_limit > MAX_PI_PW)
return -ERANGE;
ret = regulator_get_voltage(psec->ps);
if (!ret) {
NL_SET_ERR_MSG(extack,
"Can't calculate the current, PSE voltage read is 0");
return -ERANGE;
}
if (ret < 0) {
NL_SET_ERR_MSG(extack,
"Error reading PSE voltage");
return ret;
}
uV = ret;
tmp_64 = pw_limit;
tmp_64 *= 1000000000ull;
uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV);
if (pse_pw_d_is_sw_pw_control(psec->pcdev,
psec->pcdev->pi[psec->id].pw_d) &&
psec->pcdev->pi[psec->id].admin_state_enabled &&
psec->pcdev->pi[psec->id].isr_pd_detected) {
ret = pse_pi_update_pw_budget(psec->pcdev, psec->id,
pw_limit, extack);
if (ret < 0)
return ret;
previous_pw_allocated = ret;
}
ret = regulator_set_current_limit(psec->ps, 0, uA);
if (ret < 0 && previous_pw_allocated) {
pse_pi_update_pw_budget(psec->pcdev, psec->id,
previous_pw_allocated, extack);
}
return ret;
}
EXPORT_SYMBOL_GPL(pse_ethtool_set_pw_limit);
int pse_ethtool_set_prio(struct pse_control *psec,
struct netlink_ext_ack *extack,
unsigned int prio)
{
struct pse_controller_dev *pcdev = psec->pcdev;
const struct pse_controller_ops *ops;
int ret = 0;
if (!pcdev->pi[psec->id].pw_d) {
NL_SET_ERR_MSG(extack, "no power domain attached");
return -EOPNOTSUPP;
}
mutex_lock(&pcdev->lock);
switch (pcdev->pi[psec->id].pw_d->budget_eval_strategy) {
case PSE_BUDGET_EVAL_STRAT_STATIC:
if (prio >= pcdev->nr_lines) {
NL_SET_ERR_MSG_FMT(extack,
"priority %d exceed priority max %d",
prio, pcdev->nr_lines);
ret = -ERANGE;
goto out;
}
pcdev->pi[psec->id].prio = prio;
pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[psec->id].pw_d);
break;
case PSE_BUDGET_EVAL_STRAT_DYNAMIC:
ops = psec->pcdev->ops;
if (!ops->pi_set_prio) {
NL_SET_ERR_MSG(extack,
"pse driver does not support setting port priority");
ret = -EOPNOTSUPP;
goto out;
}
if (prio > pcdev->pis_prio_max) {
NL_SET_ERR_MSG_FMT(extack,
"priority %d exceed priority max %d",
prio, pcdev->pis_prio_max);
ret = -ERANGE;
goto out;
}
ret = ops->pi_set_prio(pcdev, psec->id, prio);
break;
default:
ret = -EOPNOTSUPP;
}
out:
mutex_unlock(&pcdev->lock);
return ret;
}
EXPORT_SYMBOL_GPL(pse_ethtool_set_prio);
bool pse_has_podl(struct pse_control *psec)
{
return psec->pcdev->types & ETHTOOL_PSE_PODL;
}
EXPORT_SYMBOL_GPL(pse_has_podl);
bool pse_has_c33(struct pse_control *psec)
{
return psec->pcdev->types & ETHTOOL_PSE_C33;
}
EXPORT_SYMBOL_GPL(pse_has_c33);