| // SPDX-License-Identifier: GPL-2.0-only | |
| // | |
| // Framework for Ethernet Power Sourcing Equipment | |
| // | |
| // Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> | |
| // | |
| #include <linux/device.h> | |
| #include <linux/ethtool.h> | |
| #include <linux/of.h> | |
| #include <linux/pse-pd/pse.h> | |
| #include <linux/regulator/driver.h> | |
| #include <linux/regulator/machine.h> | |
| static DEFINE_MUTEX(pse_list_mutex); | |
| static LIST_HEAD(pse_controller_list); | |
| /** | |
| * 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 | |
| */ | |
| struct pse_control { | |
| struct pse_controller_dev *pcdev; | |
| struct regulator *ps; | |
| struct list_head list; | |
| unsigned int id; | |
| struct kref refcnt; | |
| }; | |
| 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; | |
| } | |
| /** | |
| * 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 | |
| */ | |
| 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 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; | |
| } | |
| 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); | |
| } | |
| /** | |
| * 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 | |
| */ | |
| 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 = 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; | |
| } 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 int pse_pi_is_enabled(struct regulator_dev *rdev) | |
| { | |
| struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); | |
| struct pse_admin_state admin_state = {0}; | |
| 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); | |
| ret = ops->pi_get_admin_state(pcdev, id, &admin_state); | |
| if (ret) | |
| goto out; | |
| if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED || | |
| admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED) | |
| ret = 1; | |
| out: | |
| mutex_unlock(&pcdev->lock); | |
| return ret; | |
| } | |
| 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; | |
| ops = pcdev->ops; | |
| if (!ops->pi_enable) | |
| return -EOPNOTSUPP; | |
| id = rdev_get_id(rdev); | |
| mutex_lock(&pcdev->lock); | |
| 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); | |
| const struct pse_controller_ops *ops; | |
| int id, ret; | |
| ops = pcdev->ops; | |
| if (!ops->pi_disable) | |
| return -EOPNOTSUPP; | |
| id = rdev_get_id(rdev); | |
| mutex_lock(&pcdev->lock); | |
| ret = ops->pi_disable(pcdev, id); | |
| if (!ret) | |
| pcdev->pi[id].admin_state_enabled = 0; | |
| mutex_unlock(&pcdev->lock); | |
| return ret; | |
| } | |
| 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; | |
| /* uA = mW * 1000000000 / uV */ | |
| 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 = uA * uV / 1000000000 */ | |
| 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; | |
| /* 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; | |
| 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; | |
| } | |
| /** | |
| * 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); | |
| 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; | |
| 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; | |
| } | |
| 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); | |
| /** | |
| * pse_controller_unregister - unregister a PSE controller device | |
| * @pcdev: a pointer to the PSE controller device | |
| */ | |
| void pse_controller_unregister(struct pse_controller_dev *pcdev) | |
| { | |
| pse_release_pis(pcdev); | |
| 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); | |
| } | |
| /** | |
| * 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; | |
| } | |
| *pcdevp = pcdev; | |
| devres_add(dev, pcdevp); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(devm_pse_controller_register); | |
| /* PSE control section */ | |
| 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); | |
| } | |
| /** | |
| * 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; | |
| 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 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(sizeof(*psec), GFP_KERNEL); | |
| if (!psec) | |
| return ERR_PTR(-ENOMEM); | |
| if (!try_module_get(pcdev->owner)) { | |
| ret = -ENODEV; | |
| goto free_psec; | |
| } | |
| 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; | |
| } | |
| ret = regulator_is_enabled(psec->ps); | |
| if (ret < 0) | |
| goto regulator_put; | |
| pcdev->pi[index].admin_state_enabled = ret; | |
| psec->pcdev = pcdev; | |
| list_add(&psec->list, &pcdev->pse_control_head); | |
| psec->id = index; | |
| kref_init(&psec->refcnt); | |
| return psec; | |
| regulator_put: | |
| devm_regulator_put(psec->ps); | |
| put_module: | |
| module_put(pcdev->owner); | |
| free_psec: | |
| kfree(psec); | |
| return ERR_PTR(ret); | |
| } | |
| /** | |
| * 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 | |
| */ | |
| 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; | |
| } | |
| /** | |
| * 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. | |
| */ | |
| 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 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; | |
| } | |
| } | |
| /* pse_list_mutex also protects the pcdev's pse_control list */ | |
| psec = pse_control_get_internal(pcdev, psec_id); | |
| out: | |
| mutex_unlock(&pse_list_mutex); | |
| of_node_put(args.np); | |
| return psec; | |
| } | |
| EXPORT_SYMBOL_GPL(of_pse_control_get); | |
| /** | |
| * 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}; | |
| const struct pse_controller_ops *ops; | |
| struct pse_controller_dev *pcdev; | |
| int ret; | |
| pcdev = psec->pcdev; | |
| ops = pcdev->ops; | |
| mutex_lock(&pcdev->lock); | |
| 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; | |
| 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; | |
| /* 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; | |
| } | |
| return err; | |
| } | |
| static int pse_ethtool_podl_set_config(struct pse_control *psec, | |
| const struct pse_control_config *config) | |
| { | |
| int err = 0; | |
| /* 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, | |
| 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); | |
| /** | |
| * 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, | |
| const unsigned int pw_limit) | |
| { | |
| int uV, uA, ret; | |
| 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); | |
| return regulator_set_current_limit(psec->ps, 0, uA); | |
| } | |
| EXPORT_SYMBOL_GPL(pse_ethtool_set_pw_limit); | |
| 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); |