#include <linux/device.h>
#include <linux/of.h>
#include <linux/pse-pd/pse.h>
static DEFINE_MUTEX(pse_list_mutex);
static LIST_HEAD(pse_controller_list);
struct pse_control {
struct pse_controller_dev *pcdev;
struct list_head list;
unsigned int id;
struct kref refcnt;
};
static int of_pse_zero_xlate(struct pse_controller_dev *pcdev,
const struct of_phandle_args *pse_spec)
{
return 0;
}
static int of_pse_simple_xlate(struct pse_controller_dev *pcdev,
const struct of_phandle_args *pse_spec)
{
if (pse_spec->args[0] >= pcdev->nr_lines)
return -EINVAL;
return pse_spec->args[0];
}
int pse_controller_register(struct pse_controller_dev *pcdev)
{
if (!pcdev->of_xlate) {
if (pcdev->of_pse_n_cells == 0)
pcdev->of_xlate = of_pse_zero_xlate;
else if (pcdev->of_pse_n_cells == 1)
pcdev->of_xlate = of_pse_simple_xlate;
}
mutex_init(&pcdev->lock);
INIT_LIST_HEAD(&pcdev->pse_control_head);
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)
{
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);
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);
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 pse_control *psec;
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)) {
kfree(psec);
return ERR_PTR(-ENODEV);
}
psec->pcdev = pcdev;
list_add(&psec->list, &pcdev->pse_control_head);
psec->id = index;
kref_init(&psec->refcnt);
return psec;
}
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 (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;
}
psec_id = pcdev->of_xlate(pcdev, &args);
if (psec_id < 0) {
psec = ERR_PTR(psec_id);
goto out;
}
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);
int pse_ethtool_get_status(struct pse_control *psec,
struct netlink_ext_ack *extack,
struct pse_control_status *status)
{
const struct pse_controller_ops *ops;
int err;
ops = psec->pcdev->ops;
if (!ops->ethtool_get_status) {
NL_SET_ERR_MSG(extack,
"PSE driver does not support status report");
return -EOPNOTSUPP;
}
mutex_lock(&psec->pcdev->lock);
err = ops->ethtool_get_status(psec->pcdev, psec->id, extack, status);
mutex_unlock(&psec->pcdev->lock);
return err;
}
EXPORT_SYMBOL_GPL(pse_ethtool_get_status);
int pse_ethtool_set_config(struct pse_control *psec,
struct netlink_ext_ack *extack,
const struct pse_control_config *config)
{
const struct pse_controller_ops *ops;
int err;
ops = psec->pcdev->ops;
if (!ops->ethtool_set_config) {
NL_SET_ERR_MSG(extack,
"PSE driver does not configuration");
return -EOPNOTSUPP;
}
mutex_lock(&psec->pcdev->lock);
err = ops->ethtool_set_config(psec->pcdev, psec->id, extack, config);
mutex_unlock(&psec->pcdev->lock);
return err;
}
EXPORT_SYMBOL_GPL