#include <linux/export.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "pinctrl-sunxi.h"
#define INVALID_MUX 0xff
static u8 sunxi_pinctrl_dt_read_pinmux(const struct device_node *node,
int index)
{
int ret, num_elems;
u32 value;
num_elems = of_property_count_u32_elems(node, "allwinner,pinmux");
if (num_elems <= 0)
return INVALID_MUX;
if (index >= num_elems)
index = num_elems - 1;
ret = of_property_read_u32_index(node, "allwinner,pinmux", index,
&value);
if (ret)
return INVALID_MUX;
return value;
}
static struct sunxi_desc_pin *init_pins_table(struct device *dev,
const u8 *pins_per_bank,
struct sunxi_pinctrl_desc *desc)
{
struct sunxi_desc_pin *pins, *cur_pin;
int name_size = 0;
int port_base = desc->pin_base / PINS_PER_BANK;
char *pin_names, *cur_name;
int i, j;
for (i = 0; i < SUNXI_PINCTRL_MAX_BANKS; i++) {
desc->npins += pins_per_bank[i];
if (pins_per_bank[i] < 10) {
name_size += pins_per_bank[i] * 4;
} else {
name_size += 10 * 4;
name_size += (pins_per_bank[i] - 10) * 5;
}
}
if (desc->npins == 0) {
dev_err(dev, "no ports defined\n");
return ERR_PTR(-EINVAL);
}
pins = devm_kcalloc(dev, desc->npins, sizeof(*pins), GFP_KERNEL);
if (!pins)
return ERR_PTR(-ENOMEM);
pin_names = devm_kmalloc(dev, name_size, GFP_KERNEL);
if (!pin_names)
return ERR_PTR(-ENOMEM);
cur_name = pin_names;
cur_pin = pins;
for (i = 0; i < SUNXI_PINCTRL_MAX_BANKS; i++) {
for (j = 0; j < pins_per_bank[i]; j++, cur_pin++) {
int nchars = sprintf(cur_name, "P%c%d",
port_base + 'A' + i, j);
cur_pin->pin.number = (port_base + i) * PINS_PER_BANK + j;
cur_pin->pin.name = cur_name;
cur_name += nchars + 1;
}
}
return pins;
}
static int prepare_function_table(struct device *dev, struct device_node *pnode,
struct sunxi_desc_pin *pins, int npins,
unsigned pin_base, const u8 *irq_bank_muxes)
{
struct device_node *node;
struct property *prop;
struct sunxi_desc_function *func;
int num_funcs, irq_bank, last_bank, i;
num_funcs = 3 * npins;
for (i = 0; i < npins; i++) {
struct sunxi_desc_pin *pin = &pins[i];
int bank = (pin->pin.number - pin_base) / PINS_PER_BANK;
if (irq_bank_muxes[bank]) {
pin->variant++;
num_funcs++;
}
}
for_each_child_of_node(pnode, node) {
const char *name;
of_property_for_each_string(node, "pins", prop, name) {
for (i = 0; i < npins; i++) {
if (strcmp(pins[i].pin.name, name))
continue;
pins[i].variant++;
num_funcs++;
break;
}
}
}
func = devm_kcalloc(dev, num_funcs, sizeof(*func), GFP_KERNEL);
if (!func)
return -ENOMEM;
irq_bank = 0;
last_bank = 0;
for (i = 0; i < npins; i++) {
struct sunxi_desc_pin *pin = &pins[i];
int bank = (pin->pin.number - pin_base) / PINS_PER_BANK;
int lastfunc = pin->variant + 1;
int irq_mux = irq_bank_muxes[bank];
func[0].name = "gpio_in";
func[0].muxval = 0;
func[1].name = "gpio_out";
func[1].muxval = 1;
if (irq_mux) {
if (bank > last_bank)
irq_bank++;
func[lastfunc].muxval = irq_mux;
func[lastfunc].irqbank = irq_bank;
func[lastfunc].irqnum = pin->pin.number % PINS_PER_BANK;
func[lastfunc].name = "irq";
}
if (bank > last_bank)
last_bank = bank;
pin->functions = func;
func += pin->variant + 3;
pin->variant = 2;
}
return 0;
}
static void fill_pin_function(struct device *dev, struct device_node *node,
struct sunxi_desc_pin *pins, int npins)
{
const char *name, *funcname;
struct sunxi_desc_function *func;
struct property *prop;
int pin, i, index;
u8 muxval;
if (of_property_read_string(node, "function", &funcname)) {
dev_warn(dev, "missing \"function\" property\n");
return;
}
index = 0;
of_property_for_each_string(node, "pins", prop, name) {
for (pin = 0; pin < npins; pin++)
if (!strcmp(pins[pin].pin.name, name))
break;
if (pin == npins) {
dev_warn(dev, "%pOF: cannot find pin %s\n", node, name);
index++;
continue;
}
muxval = sunxi_pinctrl_dt_read_pinmux(node, index);
if (muxval == INVALID_MUX) {
dev_warn(dev, "%pOF: invalid mux value for pin %s\n",
node, name);
index++;
continue;
}
for (i = 2; i < pins[pin].variant; i++) {
func = &pins[pin].functions[i];
if (strcmp(func->name, funcname) &&
func->muxval != muxval)
continue;
if (!strcmp(func->name, funcname) &&
muxval == func->muxval)
break;
dev_warn(dev,
"pin %s: function %s redefined to mux %d\n",
name, funcname, muxval);
break;
}
if (i < pins[pin].variant) {
index++;
continue;
}
func = &pins[pin].functions[pins[pin].variant];
func->muxval = muxval;
func->name = funcname;
pins[pin].variant++;
index++;
}
}
int sunxi_pinctrl_dt_table_init(struct platform_device *pdev,
const u8 *pins_per_bank,
const u8 *irq_bank_muxes,
struct sunxi_pinctrl_desc *desc,
unsigned long flags)
{
struct device_node *pnode = pdev->dev.of_node, *node;
struct sunxi_desc_pin *pins;
int ret, i;
pins = init_pins_table(&pdev->dev, pins_per_bank, desc);
if (IS_ERR(pins))
return PTR_ERR(pins);
ret = prepare_function_table(&pdev->dev, pnode, pins, desc->npins,
desc->pin_base, irq_bank_muxes);
if (ret)
return ret;
for_each_child_of_node(pnode, node)
fill_pin_function(&pdev->dev, node, pins, desc->npins);
for (i = 0; i < desc->npins; i++)
pins[i].variant = 0;
desc->pins = pins;
return sunxi_pinctrl_init_with_flags(pdev, desc, flags);
}