// 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/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) (%pO