167 lines
3.6 KiB
C
167 lines
3.6 KiB
C
/*
|
|
* pinctrl.c - barebox pinctrl support
|
|
*
|
|
* Copyright (c) 2013 Sascha Hauer <s.hauer@pengutronix.de>
|
|
*
|
|
* See file CREDITS for list of people who contributed to this
|
|
* project.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
#include <common.h>
|
|
#include <malloc.h>
|
|
#include <pinctrl.h>
|
|
#include <errno.h>
|
|
#include <of.h>
|
|
|
|
static LIST_HEAD(pinctrl_list);
|
|
|
|
static struct pinctrl_device *find_pinctrl(struct device_node *node)
|
|
{
|
|
struct pinctrl_device *pdev;
|
|
|
|
list_for_each_entry(pdev, &pinctrl_list, list)
|
|
if (pdev->node == node)
|
|
return pdev;
|
|
return NULL;
|
|
}
|
|
|
|
static int pinctrl_config_one(struct device_node *np)
|
|
{
|
|
struct pinctrl_device *pdev;
|
|
struct device_node *pinctrl_node = np;
|
|
|
|
while (1) {
|
|
pinctrl_node = pinctrl_node->parent;
|
|
if (!pinctrl_node)
|
|
return -ENODEV;
|
|
pdev = find_pinctrl(pinctrl_node);
|
|
if (pdev)
|
|
break;
|
|
}
|
|
|
|
return pdev->ops->set_state(pdev, np);
|
|
}
|
|
|
|
int of_pinctrl_select_state(struct device_node *np, const char *name)
|
|
{
|
|
int state, ret;
|
|
char *propname;
|
|
struct property *prop;
|
|
const __be32 *list;
|
|
int size, config;
|
|
phandle phandle;
|
|
struct device_node *np_config;
|
|
const char *statename;
|
|
|
|
if (!of_find_property(np, "pinctrl-0", NULL))
|
|
return 0;
|
|
|
|
/* For each defined state ID */
|
|
for (state = 0; ; state++) {
|
|
/* Retrieve the pinctrl-* property */
|
|
propname = asprintf("pinctrl-%d", state);
|
|
prop = of_find_property(np, propname, NULL);
|
|
free(propname);
|
|
|
|
if (!prop) {
|
|
ret = -ENODEV;
|
|
break;
|
|
}
|
|
|
|
size = prop->length;
|
|
|
|
list = prop->value;
|
|
size /= sizeof(*list);
|
|
|
|
/* Determine whether pinctrl-names property names the state */
|
|
ret = of_property_read_string_index(np, "pinctrl-names",
|
|
state, &statename);
|
|
/*
|
|
* If not, statename is just the integer state ID. But rather
|
|
* than dynamically allocate it and have to free it later,
|
|
* just point part way into the property name for the string.
|
|
*/
|
|
if (ret < 0) {
|
|
/* strlen("pinctrl-") == 8 */
|
|
statename = prop->name + 8;
|
|
}
|
|
|
|
if (strcmp(name, statename))
|
|
continue;
|
|
|
|
/* For every referenced pin configuration node in it */
|
|
for (config = 0; config < size; config++) {
|
|
phandle = be32_to_cpup(list++);
|
|
|
|
/* Look up the pin configuration node */
|
|
np_config = of_find_node_by_phandle(phandle);
|
|
if (!np_config) {
|
|
pr_err("prop %s %s index %i invalid phandle\n",
|
|
np->full_name, prop->name, config);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
/* Parse the node */
|
|
ret = pinctrl_config_one(np_config);
|
|
if (ret < 0)
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
int of_pinctrl_select_state_default(struct device_node *np)
|
|
{
|
|
return of_pinctrl_select_state(np, "default");
|
|
}
|
|
|
|
int pinctrl_select_state(struct device_d *dev, const char *name)
|
|
{
|
|
struct device_node *np;
|
|
|
|
np = dev->device_node;
|
|
if (!np)
|
|
return 0;
|
|
|
|
return of_pinctrl_select_state(np, name);
|
|
}
|
|
|
|
int pinctrl_select_state_default(struct device_d *dev)
|
|
{
|
|
return pinctrl_select_state(dev, "default");
|
|
}
|
|
|
|
int pinctrl_register(struct pinctrl_device *pdev)
|
|
{
|
|
if (!IS_ENABLED(CONFIG_PINCTRL))
|
|
return -ENOSYS;
|
|
|
|
BUG_ON(!pdev->dev->device_node);
|
|
|
|
list_add_tail(&pdev->list, &pinctrl_list);
|
|
|
|
pdev->node = pdev->dev->device_node;
|
|
|
|
pinctrl_select_state_default(pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void pinctrl_unregister(struct pinctrl_device *pdev)
|
|
{
|
|
list_del(&pdev->list);
|
|
}
|