/** * struct pse_control - a PSE control * @pcdev: a pointer to the PSE controller device * this PSE control belongs to * @ps: PSE PI supply of the PSE control * @list: list entry for the pcdev's PSE controller list * @id: ID of the PSE line in the PSE controller device * @refcnt: Number of gets of this pse_control * @attached_phydev: PHY device pointer attached by the PSE control
*/ struct pse_control { struct pse_controller_dev *pcdev; struct regulator *ps; struct list_head list; unsignedint id; struct kref refcnt; struct phy_device *attached_phydev;
};
/** * struct pse_power_domain - a PSE power domain * @id: ID of the power domain * @supply: Power supply the Power Domain * @refcnt: Number of gets of this pse_power_domain * @budget_eval_strategy: Current power budget evaluation strategy of the * power domain
*/ struct pse_power_domain { int id; struct regulator *supply; struct kref refcnt;
u32 budget_eval_strategy;
};
staticint of_load_single_pse_pi_pairset(struct device_node *node, struct pse_pi *pi, int pairset_num)
{ struct device_node *pairset_np; constchar *name; int ret;
ret = of_property_read_string_index(node, "pairset-names",
pairset_num, &name); if (ret) return ret;
pairset_np = of_parse_phandle(node, "pairsets", pairset_num); if (!pairset_np) return -ENODEV;
pi->pairset[pairset_num].np = pairset_np;
return 0;
}
/** * of_load_pse_pi_pairsets - load PSE PI pairsets pinout and polarity * @node: a pointer of the device node * @pi: a pointer of the PSE PI to fill * @npairsets: the number of pairsets (1 or 2) used by the PI * * Return: 0 on success and failure value on error
*/ staticint 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 an error appears, release all the pairset device node kref */ 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;
}
staticvoid 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);
}
/** * of_load_pse_pis - load all the PSE PIs * @pcdev: a pointer to the PSE controller device * * Return: 0 on success and failure value on error
*/ staticint 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 = kcalloc(pcdev->nr_lines, sizeof(*pcdev->pi), GFP_KERNEL); if (!pcdev->pi) return -ENOMEM;
pis = of_get_child_by_name(np, "pse-pis"); if (!pis) { /* no description of PSE 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); /* npairsets is limited to value one or two */ if (ret == 1 || ret == 2) {
ret = of_load_pse_pi_pairsets(node, &pi, ret); if (ret) goto out;
} elseif (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;
}
/** * pse_control_find_net_by_id - Find net attached to the pse control id * @pcdev: a pointer to the PSE * @id: index of the PSE control * * Return: pse_control pointer or NULL. The device returned has had a * reference added and the pointer is safe until the user calls * pse_control_put() to indicate they have finished with it.
*/ staticstruct pse_control *
pse_control_find_by_id(struct pse_controller_dev *pcdev, int id)
{ struct pse_control *psec;
/** * pse_control_get_netdev - Return netdev associated to a PSE control * @psec: PSE control pointer * * Return: netdev pointer or NULL
*/ staticstruct 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;
}
/** * pse_pi_is_hw_enabled - Is PI enabled at the hardware level * @pcdev: a pointer to the PSE controller device * @id: Index of the PI * * Return: 1 if the PI is enabled at the hardware level, 0 if not, and * a failure value on error
*/ staticint 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;
/* PI is well enabled at the hardware level */ 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;
}
/** * pse_pi_is_admin_enable_pending - Check if PI is in admin enable pending state * which mean the power is not yet being * delivered * @pcdev: a pointer to the PSE controller device * @id: Index of the PI * * Detects if a PI is enabled in software with a PD detected, but the hardware * admin state hasn't been applied yet. * * This function is used in the power delivery and retry mechanisms to determine * which PIs need to have power delivery attempted again. * * Return: true if the PI has admin enable flag set in software but not yet * reflected in the hardware admin state, false otherwise.
*/ staticbool
pse_pi_is_admin_enable_pending(struct pse_controller_dev *pcdev, int id)
{ int ret;
/* PI not enabled or nothing is plugged */ if (!pcdev->pi[id].admin_state_enabled ||
!pcdev->pi[id].isr_pd_detected) returnfalse;
ret = pse_pi_is_hw_enabled(pcdev, id); /* PSE PI is already enabled at hardware level */ if (ret == 1) returnfalse;
returntrue;
}
staticint _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev, int id, struct netlink_ext_ack *extack);
/** * pse_pw_d_retry_power_delivery - Retry power delivery for pending ports in a * PSE power domain * @pcdev: a pointer to the PSE controller device * @pw_d: a pointer to the PSE power domain * * Scans all ports in the specified power domain and attempts to enable power * delivery to any ports that have admin enable state set but don't yet have * hardware power enabled. Used when there are changes in connection status, * admin state, or priority that might allow previously unpowered ports to * receive power, especially in over-budget conditions.
*/ staticvoid 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;
/* Do not try to enable PI with a lower prio (higher value) * than one which already can't be enabled.
*/ 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;
}
}
/** * pse_pw_d_is_sw_pw_control - Determine if power control is software managed * @pcdev: a pointer to the PSE controller device * @pw_d: a pointer to the PSE power domain * * This function determines whether the power control for a specific power * domain is managed by software in the interrupt handler rather than directly * by hardware. * * Software power control is active in the following cases: * - When the budget evaluation strategy is set to static * - When the budget evaluation strategy is disabled but the PSE controller * has an interrupt handler that can report if a Powered Device is connected * * Return: true if the power control of the power domain is managed by software, * false otherwise
*/ staticbool pse_pw_d_is_sw_pw_control(struct pse_controller_dev *pcdev, struct pse_power_domain *pw_d)
{ if (!pw_d) returnfalse;
if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC) returntrue; if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_DISABLED &&
pcdev->ops->pi_enable && pcdev->irq) returntrue;
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;
}
/** * pse_pi_deallocate_pw_budget - Deallocate power budget of the PI * @pi: a pointer to the PSE PI
*/ staticvoid pse_pi_deallocate_pw_budget(struct pse_pi *pi)
{ if (!pi->pw_d || !pi->pw_allocated_mW) return;
/** * _pse_pi_disable - Call disable operation. Assumes the PSE lock has been * acquired. * @pcdev: a pointer to the PSE * @id: index of the PSE control * * Return: 0 on success and failure value on error
*/ staticint _pse_pi_disable(struct pse_controller_dev *pcdev, int id)
{ conststruct 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;
}
/** * pse_disable_pi_pol - Disable a PI on a power budget policy * @pcdev: a pointer to the PSE * @id: index of the PSE PI * * Return: 0 on success and failure value on error
*/ staticint pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id)
{ unsignedlong 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;
/** * pse_disable_pi_prio - Disable all PIs of a given priority inside a PSE * power domain * @pcdev: a pointer to the PSE * @pw_d: a pointer to the PSE power domain * @prio: priority * * Return: 0 on success and failure value on error
*/ staticint pse_disable_pi_prio(struct pse_controller_dev *pcdev, struct pse_power_domain *pw_d, int prio)
{ int i;
ret = pse_disable_pi_pol(pcdev, i); if (ret) return ret;
}
return 0;
}
/** * pse_pi_allocate_pw_budget_static_prio - Allocate power budget for the PI * when the budget eval strategy is * static * @pcdev: a pointer to the PSE * @id: index of the PSE control * @pw_req: power requested in mW * @extack: extack for error reporting * * Allocates power using static budget evaluation strategy, where allocation * is based on PD classification. When insufficient budget is available, * lower-priority ports (higher priority numbers) are turned off first. * * Return: 0 on success and failure value on error
*/ staticint
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;
}
/** * pse_pi_allocate_pw_budget - Allocate power budget for the PI * @pcdev: a pointer to the PSE * @id: index of the PSE control * @pw_req: power requested in mW * @extack: extack for error reporting * * Return: 0 on success and failure value on error
*/ staticint 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];
/** * _pse_pi_delivery_power_sw_pw_ctrl - Enable PSE PI in case of software power * control. Assumes the PSE lock has been * acquired. * @pcdev: a pointer to the PSE * @id: index of the PSE control * @extack: extack for error reporting * * Return: 0 on success and failure value on error
*/ staticint _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev, int id, struct netlink_ext_ack *extack)
{ conststruct pse_controller_ops *ops = pcdev->ops; struct pse_pi *pi = &pcdev->pi[id]; int ret, pw_req;
if (!ops->pi_get_pw_req) { /* No power allocation management */
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;
/* Compare requested power with port power limit and use the lowest * one.
*/ 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;
}
staticint pse_pi_enable(struct regulator_dev *rdev)
{ struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); conststruct 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)) { /* Manage enabled status by software. * Real enable process will happen if a port is connected.
*/ 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);
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; /* uA = mW * 1000000000 / uV */
ret = DIV_ROUND_CLOSEST_ULL(tmp_64, uV);
out:
mutex_unlock(&pcdev->lock); return ret;
}
staticint pse_pi_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA)
{ struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); conststruct 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;
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;
/* Regulator descriptor id have to be the same as its associated * PSE PI id for the well functioning of the PSE controls.
*/
rdesc->id = id;
rdesc->name = name;
rdesc->type = REGULATOR_VOLTAGE;
rdesc->ops = &pse_pi_ops;
rdesc->owner = pcdev->owner;
/** * pse_flush_pw_ds - flush all PSE power domains of a PSE * @pcdev: a pointer to the initialized PSE controller device
*/ staticvoid 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;
/** * devm_pse_alloc_pw_d - allocate a new PSE power domain for a device * @dev: device that is registering this PSE power domain * * Return: Pointer to the newly allocated PSE power domain or error pointers
*/ staticstruct 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);
/** * pse_register_pw_ds - register the PSE power domains for a PSE * @pcdev: a pointer to the PSE controller device * * Return: 0 on success and failure value on error
*/ staticint 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; unsignedlong index;
/* No regulator or regulator parent supply registered. * We need a regulator parent to register a PSE power domain
*/ if (!rdev || !rdev->supply) continue;
xa_for_each(&pse_pw_d_map, index, pw_d) { /* Power supply already registered as a PSE power * domain.
*/ 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;
}
/** * pse_send_ntf_worker - Worker to send PSE notifications * @work: work object * * Manage and send PSE netlink notifications using a workqueue to avoid * deadlock between pcdev_lock and pse_list_mutex.
*/ staticvoid pse_send_ntf_worker(struct work_struct *work)
{ struct pse_controller_dev *pcdev; struct pse_ntf ntf;
/** * pse_controller_register - register a PSE controller device * @pcdev: a pointer to the initialized PSE controller device * * Return: 0 on success and failure value on error
*/ 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;
}
/* Each regulator name len is pcdev dev name + 7 char + * int max digit number (10) + 1
*/
reg_name_len = strlen(dev_name(pcdev->dev)) + 18;
/* Register PI regulators */ for (i = 0; i < pcdev->nr_lines; i++) { char *reg_name;
/* Do not register regulator for PIs not described */ 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;
/** * devm_pse_controller_register - resource managed pse_controller_register() * @dev: device that is registering this PSE controller * @pcdev: a pointer to the initialized PSE controller device * * Managed pse_controller_register(). For PSE controllers registered by * this function, pse_controller_unregister() is automatically called on * driver detach. See pse_controller_register() for more information. * * Return: 0 on success and failure value on error
*/ 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;
}
/** * pse_to_regulator_notifs - Convert PSE notifications to Regulator * notifications * @notifs: PSE notifications * * Return: Regulator notifications
*/ staticunsignedlong pse_to_regulator_notifs(unsignedlong notifs)
{ unsignedlong 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;
}
/** * pse_set_config_isr - Set PSE control config according to the PSE * notifications * @pcdev: a pointer to the PSE * @id: index of the PSE control * @notifs: PSE event notifications * * Return: 0 on success and failure value on error
*/ staticint pse_set_config_isr(struct pse_controller_dev *pcdev, int id, unsignedlong notifs)
{ int ret = 0;
if (notifs & PSE_BUDGET_EVAL_STRAT_DYNAMIC) return 0;
/* Do nothing PI not described */ 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);
/** * devm_pse_irq_helper - Register IRQ based PSE event notifier * @pcdev: a pointer to the PSE * @irq: the irq value to be passed to request_irq * @irq_flags: the flags to be passed to request_irq * @d: PSE interrupt description * * Return: 0 on success and errno on failure
*/ int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq, int irq_flags, conststruct 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 is pcdev dev name + 5 char + irq desc name + 1 */
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;
/** * pse_control_put - free the PSE control * @psec: PSE control pointer
*/ void pse_control_put(struct pse_control *psec)
{ if (IS_ERR_OR_NULL(psec)) return;
psec = kzalloc(sizeof(*psec), GFP_KERNEL); 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;
}
/* Initialize admin_state_enabled before the regulator_get. This * aims to have the right value reported in the first is_enabled * call in case of control managed by software.
*/
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;
}
/** * of_pse_match_pi - Find the PSE PI id matching the device node phandle * @pcdev: a pointer to the PSE controller device * @np: a pointer to the device node * * Return: id of the PSE PI, -EINVAL if not found
*/ staticint 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;
}
/** * psec_id_xlate - translate pse_spec to the PSE line number according * to the number of pse-cells in case of no pse_pi node * @pcdev: a pointer to the PSE controller device * @pse_spec: PSE line specifier as found in the device tree * * Return: 0 if #pse-cells = <0>. Return PSE line number otherwise.
*/ staticint psec_id_xlate(struct pse_controller_dev *pcdev, conststruct 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;
/** * pse_get_sw_admin_state - Convert the software admin state to c33 or podl * admin state value used in the standard * @psec: PSE control pointer * @admin_state: a pointer to the admin_state structure
*/ staticvoid 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;
}
}
/** * pse_ethtool_get_status - get status of PSE control * @psec: PSE control pointer * @extack: extack for reporting useful error messages * @status: struct to store PSE status * * Return: 0 on success and failure value on error
*/ 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}; conststruct 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;
/* Look at admin_state_enabled status to not call regulator_enable * or regulator_disable twice creating a regulator counter mismatch
*/ switch (config->c33_admin_control) { case ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED: /* We could have mismatch between admin_state_enabled and * state reported by regulator_is_enabled. This can occur when * the PI is forcibly turn off by the controller. Call * regulator_disable on that case to fix the counters state.
*/ 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;
}
/* Look at admin_state_enabled status to not call regulator_enable * or regulator_disable twice creating a regulator counter mismatch
*/ 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;
}
/** * pse_ethtool_set_config - set PSE control configuration * @psec: PSE control pointer * @extack: extack for reporting useful error messages * @config: Configuration of the test to run * * Return: 0 on success and failure value on error
*/ int pse_ethtool_set_config(struct pse_control *psec, struct netlink_ext_ack *extack, conststruct 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);
/** * pse_pi_update_pw_budget - Update PSE power budget allocated with new * power in mW * @pcdev: a pointer to the PSE controller device * @id: index of the PSE PI * @pw_req: power requested * @extack: extack for reporting useful error messages * * Return: Previous power allocated on success and failure value on error
*/ staticint pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id, constunsignedint pw_req, struct netlink_ext_ack *extack)
{ struct pse_pi *pi = &pcdev->pi[id]; int previous_pw_allocated; int pw_diff, ret = 0;
/* We don't want pw_allocated_mW value change in the middle of an * power budget update
*/
mutex_lock(&pcdev->lock);
previous_pw_allocated = pi->pw_allocated_mW;
pw_diff = pw_req - previous_pw_allocated; if (!pw_diff) { goto out;
} elseif (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;
}
/** * pse_ethtool_set_pw_limit - set PSE control power limit * @psec: PSE control pointer * @extack: extack for reporting useful error messages * @pw_limit: power limit value in mW * * Return: 0 on success and failure value on error
*/ int pse_ethtool_set_pw_limit(struct pse_control *psec, struct netlink_ext_ack *extack, constunsignedint 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 = mW * 1000000000 / uV */
uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV);
/* Update power budget only in software power control case and * if a Power Device is powered.
*/ 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);
}
/** * pse_ethtool_set_prio - Set PSE PI priority according to the budget * evaluation strategy * @psec: PSE control pointer * @extack: extack for reporting useful error messages * @prio: priovity value * * Return: 0 on success and failure value on error
*/ int pse_ethtool_set_prio(struct pse_control *psec, struct netlink_ext_ack *extack, unsignedint prio)
{ struct pse_controller_dev *pcdev = psec->pcdev; conststruct 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;
}
/* We don't want priority change in the middle of an * enable/disable call or a priority mode change
*/
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;
}
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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.