[PATCH 02/13] pinctrl-jz4740: add a pinctrl driver for the Ingenic jz4740 SoC

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




From: Paul Burton <paul.burton@xxxxxxxxxx>

This driver handles pin configuration, pin muxing, and GPIOs of the
jz4740 SoC from Ingenic.

It is separated into two files:
- pinctrl-ingenic.c, which contains the core functions that can be
  shared across all Ingenic SoCs,
- pinctrl-jz4740.c, which contains the jz4740-pinctrl driver.

The reason behind separating some functions out of the jz4740-pinctrl
driver, is that the pin/GPIO controllers of the Ingenic SoCs are
extremely similar across SoC versions, except that some have the
registers shuffled around. Making a distinct separation will permit the
reuse of large parts of the driver to support the other SoCs from
Ingenic.

Signed-off-by: Paul Cercueil <paul@xxxxxxxxxxxxxxx>
---
 drivers/pinctrl/Kconfig                   |   1 +
 drivers/pinctrl/Makefile                  |   1 +
 drivers/pinctrl/ingenic/Kconfig           |  14 +
 drivers/pinctrl/ingenic/Makefile          |   2 +
 drivers/pinctrl/ingenic/pinctrl-ingenic.c | 847 ++++++++++++++++++++++++++++++
 drivers/pinctrl/ingenic/pinctrl-ingenic.h |  42 ++
 drivers/pinctrl/ingenic/pinctrl-jz4740.c  | 190 +++++++
 include/dt-bindings/pinctrl/ingenic.h     |  11 +
 8 files changed, 1108 insertions(+)
 create mode 100644 drivers/pinctrl/ingenic/Kconfig
 create mode 100644 drivers/pinctrl/ingenic/Makefile
 create mode 100644 drivers/pinctrl/ingenic/pinctrl-ingenic.c
 create mode 100644 drivers/pinctrl/ingenic/pinctrl-ingenic.h
 create mode 100644 drivers/pinctrl/ingenic/pinctrl-jz4740.c
 create mode 100644 include/dt-bindings/pinctrl/ingenic.h

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 54044a8ecbd7..e13ca8cc1cde 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -282,6 +282,7 @@ source "drivers/pinctrl/aspeed/Kconfig"
 source "drivers/pinctrl/bcm/Kconfig"
 source "drivers/pinctrl/berlin/Kconfig"
 source "drivers/pinctrl/freescale/Kconfig"
+source "drivers/pinctrl/ingenic/Kconfig"
 source "drivers/pinctrl/intel/Kconfig"
 source "drivers/pinctrl/mvebu/Kconfig"
 source "drivers/pinctrl/nomadik/Kconfig"
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 25d50a86981d..93b6837af7bb 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_ARCH_ASPEED)	+= aspeed/
 obj-y				+= bcm/
 obj-$(CONFIG_PINCTRL_BERLIN)	+= berlin/
 obj-y				+= freescale/
+obj-$(CONFIG_PINCTRL_INGENIC)	+= ingenic/
 obj-$(CONFIG_X86)		+= intel/
 obj-$(CONFIG_PINCTRL_MVEBU)	+= mvebu/
 obj-y				+= nomadik/
diff --git a/drivers/pinctrl/ingenic/Kconfig b/drivers/pinctrl/ingenic/Kconfig
new file mode 100644
index 000000000000..9923ce127183
--- /dev/null
+++ b/drivers/pinctrl/ingenic/Kconfig
@@ -0,0 +1,14 @@
+#
+# Ingenic SoCs pin control drivers
+#
+config PINCTRL_INGENIC
+	bool
+	select PINMUX
+	select GPIOLIB_IRQCHIP
+	select GENERIC_PINCONF
+
+config PINCTRL_JZ4740
+	bool "Pinctrl driver for the Ingenic JZ4740 SoC"
+	default y
+	depends on MACH_JZ4740 || COMPILE_TEST
+	select PINCTRL_INGENIC
diff --git a/drivers/pinctrl/ingenic/Makefile b/drivers/pinctrl/ingenic/Makefile
new file mode 100644
index 000000000000..8b2c8b789dc9
--- /dev/null
+++ b/drivers/pinctrl/ingenic/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_PINCTRL_INGENIC)	+= pinctrl-ingenic.o
+obj-$(CONFIG_PINCTRL_JZ4740)	+= pinctrl-jz4740.o
diff --git a/drivers/pinctrl/ingenic/pinctrl-ingenic.c b/drivers/pinctrl/ingenic/pinctrl-ingenic.c
new file mode 100644
index 000000000000..22a2be6d72f1
--- /dev/null
+++ b/drivers/pinctrl/ingenic/pinctrl-ingenic.c
@@ -0,0 +1,847 @@
+/*
+ * Ingenic SoCs pinctrl driver
+ *
+ * Copyright (c) 2013 Imagination Technologies
+ * Copyright (c) 2017 Paul Cercueil <paul@xxxxxxxxxxxxxxx>
+ *
+ * Authors: Paul Burton <paul.burton@xxxxxxxxxx>,
+ *          Paul Cercueil <paul@xxxxxxxxxxxxxxx>
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/compiler.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/pinctrl/ingenic.h>
+
+#include "../core.h"
+#include "../pinconf.h"
+#include "pinctrl-ingenic.h"
+
+struct ingenic_pinctrl;
+
+struct ingenic_pinctrl_pin {
+	struct ingenic_gpio_chip *gpio_chip;
+	unsigned int idx;
+	unsigned int func;
+	unsigned long *configs;
+	unsigned int num_configs;
+};
+
+struct ingenic_pinctrl_group {
+	const char *name;
+	struct device_node *of_node;
+
+	unsigned int num_pins;
+	struct ingenic_pinctrl_pin *pins;
+	unsigned int *pin_indices;
+};
+
+struct ingenic_pinctrl_func {
+	const char *name;
+	struct device_node *of_node;
+
+	unsigned int num_groups;
+	struct ingenic_pinctrl_group **groups;
+	const char **group_names;
+};
+
+struct ingenic_gpio_chip {
+	char name[3];
+	unsigned int idx;
+	void __iomem *base;
+	struct gpio_chip gc;
+	struct irq_chip irq_chip;
+	struct ingenic_pinctrl *pinctrl;
+	const struct ingenic_pinctrl_ops *ops;
+	uint32_t pull_ups;
+	uint32_t pull_downs;
+	unsigned int irq;
+	struct pinctrl_gpio_range grange;
+};
+
+struct ingenic_pinctrl {
+	struct device *dev;
+	uint32_t base;
+	struct pinctrl_dev *pctl;
+	struct pinctrl_pin_desc *pdesc;
+
+	unsigned int num_gpio_chips;
+	struct ingenic_gpio_chip *gpio_chips;
+
+	unsigned int num_groups;
+	struct ingenic_pinctrl_group *groups;
+
+	unsigned int num_funcs;
+	struct ingenic_pinctrl_func *funcs;
+};
+
+#define gc_to_jzgc(gpiochip) \
+	container_of(gpiochip, struct ingenic_gpio_chip, gc)
+
+#define PINS_PER_GPIO_PORT 32
+
+static struct ingenic_pinctrl_group *find_group_by_of_node(
+		struct ingenic_pinctrl *jzpc, struct device_node *np)
+{
+	int i;
+
+	for (i = 0; i < jzpc->num_groups; i++)
+		if (jzpc->groups[i].of_node == np)
+			return &jzpc->groups[i];
+
+	return NULL;
+}
+
+static struct ingenic_pinctrl_func *find_func_by_of_node(
+		struct ingenic_pinctrl *jzpc, struct device_node *np)
+{
+	int i;
+
+	for (i = 0; i < jzpc->num_funcs; i++)
+		if (jzpc->funcs[i].of_node == np)
+			return &jzpc->funcs[i];
+
+	return NULL;
+}
+
+static void ingenic_gpio_set(struct gpio_chip *gc,
+		unsigned int offset, int value)
+{
+	struct ingenic_gpio_chip *jzgc = gc_to_jzgc(gc);
+
+	jzgc->ops->gpio_set_value(jzgc->base, offset, value);
+}
+
+static int ingenic_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+	struct ingenic_gpio_chip *jzgc = gc_to_jzgc(gc);
+
+	return jzgc->ops->gpio_get_value(jzgc->base, offset);
+}
+
+static int ingenic_gpio_direction_input(struct gpio_chip *gc,
+		unsigned int offset)
+{
+	return pinctrl_gpio_direction_input(gc->base + offset);
+}
+
+static int ingenic_gpio_direction_output(struct gpio_chip *gc,
+		unsigned int offset, int value)
+{
+	ingenic_gpio_set(gc, offset, value);
+	return pinctrl_gpio_direction_output(gc->base + offset);
+}
+
+static void ingenic_gpio_irq_mask(struct irq_data *irqd)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+	struct ingenic_gpio_chip *jzgc = gc_to_jzgc(gc);
+
+	jzgc->ops->irq_mask(jzgc->base, irqd->hwirq, true);
+}
+
+static void ingenic_gpio_irq_unmask(struct irq_data *irqd)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+	struct ingenic_gpio_chip *jzgc = gc_to_jzgc(gc);
+
+	jzgc->ops->irq_mask(jzgc->base, irqd->hwirq, false);
+}
+
+static void ingenic_gpio_irq_ack(struct irq_data *irqd)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+	struct ingenic_gpio_chip *jzgc = gc_to_jzgc(gc);
+	unsigned int high;
+	int irq = irqd->hwirq;
+
+	if (irqd_get_trigger_type(irqd) == IRQ_TYPE_EDGE_BOTH) {
+		/*
+		 * Switch to an interrupt for the opposite edge to the one that
+		 * triggered the interrupt being ACKed.
+		 */
+		high = jzgc->ops->gpio_get_value(jzgc->base, irq);
+		if (high)
+			jzgc->ops->irq_set_type(jzgc->base, irq,
+					IRQ_TYPE_EDGE_FALLING);
+		else
+			jzgc->ops->irq_set_type(jzgc->base, irq,
+					IRQ_TYPE_EDGE_RISING);
+	}
+
+	jzgc->ops->irq_ack(jzgc->base, irq);
+}
+
+static int ingenic_gpio_irq_set_type(struct irq_data *irqd, unsigned int type)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+	struct ingenic_gpio_chip *jzgc = gc_to_jzgc(gc);
+
+	switch (type) {
+	case IRQ_TYPE_EDGE_BOTH:
+	case IRQ_TYPE_EDGE_RISING:
+	case IRQ_TYPE_EDGE_FALLING:
+	case IRQ_TYPE_LEVEL_HIGH:
+	case IRQ_TYPE_LEVEL_LOW:
+		break;
+	default:
+		pr_err("unsupported external interrupt type\n");
+		return -EINVAL;
+	}
+
+	if (type & IRQ_TYPE_EDGE_BOTH)
+		irq_set_handler_locked(irqd, handle_edge_irq);
+	else
+		irq_set_handler_locked(irqd, handle_level_irq);
+
+	if (type == IRQ_TYPE_EDGE_BOTH) {
+		/*
+		 * The hardware does not support interrupts on both edges. The
+		 * best we can do is to set up a single-edge interrupt and then
+		 * switch to the opposing edge when ACKing the interrupt.
+		 */
+		int value = jzgc->ops->gpio_get_value(jzgc->base, irqd->hwirq);
+
+		type = value ? IRQ_TYPE_EDGE_FALLING : IRQ_TYPE_EDGE_RISING;
+	}
+
+	jzgc->ops->irq_set_type(jzgc->base, irqd->hwirq, type);
+	return 0;
+}
+
+static int ingenic_gpio_irq_set_wake(struct irq_data *irqd, unsigned int on)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+	struct ingenic_gpio_chip *jzgc = gc_to_jzgc(gc);
+
+	return irq_set_irq_wake(jzgc->irq, on);
+}
+
+static void ingenic_gpio_irq_handler(struct irq_desc *desc)
+{
+	struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+	struct ingenic_gpio_chip *jzgc = gc_to_jzgc(gc);
+	struct irq_chip *irq_chip = irq_data_get_irq_chip(&desc->irq_data);
+	unsigned long flag, i;
+
+	chained_irq_enter(irq_chip, desc);
+	flag = jzgc->ops->irq_read(jzgc->base);
+
+	for_each_set_bit(i, &flag, 32)
+		generic_handle_irq(irq_linear_revmap(gc->irqdomain, i));
+	chained_irq_exit(irq_chip, desc);
+}
+
+static int ingenic_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+
+	return jzpc->num_groups;
+}
+
+static const char *ingenic_pinctrl_get_group_name(
+		struct pinctrl_dev *pctldev, unsigned int selector)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+
+	return jzpc->groups[selector].name;
+}
+
+static int ingenic_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
+		unsigned int selector, const unsigned int **pins,
+		unsigned int *num_pins)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+
+	if (selector >= jzpc->num_groups)
+		return -EINVAL;
+
+	*pins = jzpc->groups[selector].pin_indices;
+	*num_pins = jzpc->groups[selector].num_pins;
+	return 0;
+}
+
+static int ingenic_pinctrl_dt_node_to_map(
+		struct pinctrl_dev *pctldev, struct device_node *np,
+		struct pinctrl_map **map, unsigned int *num_maps)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+	struct ingenic_pinctrl_func *func;
+	struct ingenic_pinctrl_group *group;
+	struct pinctrl_map *new_map;
+	unsigned int map_num, i;
+
+	group = find_group_by_of_node(jzpc, np);
+	if (!group)
+		return -EINVAL;
+
+	func = find_func_by_of_node(jzpc, of_get_parent(np));
+	if (!func)
+		return -EINVAL;
+
+	map_num = 1 + group->num_pins;
+	new_map = devm_kzalloc(jzpc->dev,
+				sizeof(*new_map) * map_num, GFP_KERNEL);
+	if (!new_map)
+		return -ENOMEM;
+
+	new_map[0].type = PIN_MAP_TYPE_MUX_GROUP;
+	new_map[0].data.mux.function = func->name;
+	new_map[0].data.mux.group = group->name;
+
+	for (i = 0; i < group->num_pins; i++) {
+		new_map[i + 1].type = PIN_MAP_TYPE_CONFIGS_PIN;
+		new_map[i + 1].data.configs.group_or_pin =
+			jzpc->pdesc[group->pins[i].idx].name;
+		new_map[i + 1].data.configs.configs = group->pins[i].configs;
+		new_map[i + 1].data.configs.num_configs =
+			group->pins[i].num_configs;
+	}
+
+	*map = new_map;
+	*num_maps = map_num;
+	return 0;
+}
+
+static void ingenic_pinctrl_dt_free_map(struct pinctrl_dev *pctldev,
+		struct pinctrl_map *map, unsigned int num_maps)
+{
+}
+
+static struct pinctrl_ops ingenic_pctlops = {
+	.get_groups_count = ingenic_pinctrl_get_groups_count,
+	.get_group_name = ingenic_pinctrl_get_group_name,
+	.get_group_pins = ingenic_pinctrl_get_group_pins,
+	.dt_node_to_map = ingenic_pinctrl_dt_node_to_map,
+	.dt_free_map = ingenic_pinctrl_dt_free_map,
+};
+
+static int ingenic_pinmux_get_functions_count(struct pinctrl_dev *pctldev)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+
+	return jzpc->num_funcs;
+}
+
+static const char *ingenic_pinmux_get_function_name(
+		struct pinctrl_dev *pctldev, unsigned int selector)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+
+	return jzpc->funcs[selector].name;
+}
+
+static int ingenic_pinmux_get_function_groups(struct pinctrl_dev *pctldev,
+		unsigned int selector, const char * const **groups,
+		unsigned int * const num_groups)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+
+	if (selector >= jzpc->num_funcs)
+		return -EINVAL;
+
+	*groups = jzpc->funcs[selector].group_names;
+	*num_groups = jzpc->funcs[selector].num_groups;
+
+	return 0;
+}
+
+static int ingenic_pinmux_set_pin_fn(struct ingenic_pinctrl *jzpc,
+		struct ingenic_pinctrl_pin *pin)
+{
+	struct ingenic_gpio_chip *jzgc = &jzpc->gpio_chips[
+		pin->idx / PINS_PER_GPIO_PORT];
+	unsigned int idx = pin->idx % PINS_PER_GPIO_PORT;
+
+	if (pin->func == JZ_PIN_MODE_GPIO) {
+		dev_dbg(jzpc->dev, "set pin P%c%u to GPIO\n",
+				'A' + pin->gpio_chip->idx, idx);
+
+		jzgc->ops->set_gpio(jzgc->base, idx, false);
+	} else if (pin->func < jzgc->ops->nb_functions) {
+		dev_dbg(jzpc->dev, "set pin P%c%u to function %u\n",
+				'A' + pin->gpio_chip->idx, idx, pin->func);
+
+		jzgc->ops->set_function(jzgc->base, idx, pin->func);
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ingenic_pinmux_set_mux(struct pinctrl_dev *pctldev,
+		unsigned int selector, unsigned int group)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+	struct ingenic_pinctrl_group *grp = &jzpc->groups[group];
+	unsigned int i;
+	int err = 0;
+
+	if (selector >= jzpc->num_funcs || group >= jzpc->num_groups)
+		return -EINVAL;
+
+	for (i = 0; i < grp->num_pins; i++) {
+		err = ingenic_pinmux_set_pin_fn(jzpc, &grp->pins[i]);
+		if (err)
+			break;
+	}
+
+	return err;
+}
+
+static int ingenic_pinmux_gpio_set_direction(struct pinctrl_dev *pctldev,
+		struct pinctrl_gpio_range *range,
+		unsigned int offset, bool input)
+{
+	struct ingenic_gpio_chip *jzgc = gc_to_jzgc(range->gc);
+	unsigned int idx;
+
+	idx = offset - (jzgc->idx * PINS_PER_GPIO_PORT);
+
+	jzgc->ops->set_gpio(jzgc->base, idx, !input);
+	return 0;
+}
+
+static struct pinmux_ops ingenic_pmxops = {
+	.get_functions_count = ingenic_pinmux_get_functions_count,
+	.get_function_name = ingenic_pinmux_get_function_name,
+	.get_function_groups = ingenic_pinmux_get_function_groups,
+	.set_mux = ingenic_pinmux_set_mux,
+	.gpio_set_direction = ingenic_pinmux_gpio_set_direction,
+};
+
+static int ingenic_pinconf_get(struct pinctrl_dev *pctldev,
+		unsigned int pin, unsigned long *config)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+	struct ingenic_gpio_chip *jzgc;
+	enum pin_config_param param = pinconf_to_config_param(*config);
+	unsigned int idx, pull;
+
+	if (pin >= (jzpc->num_gpio_chips * PINS_PER_GPIO_PORT))
+		return -EINVAL;
+	jzgc = &jzpc->gpio_chips[pin / PINS_PER_GPIO_PORT];
+	idx = pin % PINS_PER_GPIO_PORT;
+
+	pull = jzgc->ops->get_bias(jzgc->base, idx);
+
+	switch (param) {
+	case PIN_CONFIG_BIAS_DISABLE:
+		if (pull)
+			return -EINVAL;
+		break;
+
+	case PIN_CONFIG_BIAS_PULL_UP:
+		if (!pull || !(jzgc->pull_ups & (1 << idx)))
+			return -EINVAL;
+		break;
+
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+		if (!pull || !(jzgc->pull_downs & (1 << idx)))
+			return -EINVAL;
+		break;
+
+	default:
+		return -ENOTSUPP;
+	}
+
+	*config = pinconf_to_config_packed(param, 1);
+	return 0;
+}
+
+static int ingenic_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+		unsigned long *configs, unsigned int num_configs)
+{
+	struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
+	struct ingenic_gpio_chip *jzgc;
+	unsigned int idx, cfg;
+
+	if (pin >= (jzpc->num_gpio_chips * PINS_PER_GPIO_PORT))
+		return -EINVAL;
+
+	jzgc = &jzpc->gpio_chips[pin / PINS_PER_GPIO_PORT];
+	idx = pin % PINS_PER_GPIO_PORT;
+
+	for (cfg = 0; cfg < num_configs; cfg++) {
+		switch (pinconf_to_config_param(configs[cfg])) {
+		case PIN_CONFIG_BIAS_DISABLE:
+		case PIN_CONFIG_BIAS_PULL_UP:
+		case PIN_CONFIG_BIAS_PULL_DOWN:
+			continue;
+		default:
+			return -ENOTSUPP;
+		}
+	}
+
+	for (cfg = 0; cfg < num_configs; cfg++) {
+		switch (pinconf_to_config_param(configs[cfg])) {
+		case PIN_CONFIG_BIAS_DISABLE:
+			jzgc->ops->set_bias(jzgc->base, idx, false);
+			break;
+
+		case PIN_CONFIG_BIAS_PULL_UP:
+			if (!(jzgc->pull_ups & (1 << idx)))
+				return -EINVAL;
+			jzgc->ops->set_bias(jzgc->base, idx, true);
+			break;
+
+		case PIN_CONFIG_BIAS_PULL_DOWN:
+			if (!(jzgc->pull_downs & (1 << idx)))
+				return -EINVAL;
+			jzgc->ops->set_bias(jzgc->base, idx, true);
+			break;
+
+		default:
+			unreachable();
+		}
+	}
+
+	return 0;
+}
+
+static struct pinconf_ops ingenic_confops = {
+	.is_generic = true,
+	.pin_config_get = ingenic_pinconf_get,
+	.pin_config_set = ingenic_pinconf_set,
+};
+
+static int ingenic_pinctrl_parse_dt_gpio(struct ingenic_pinctrl *jzpc,
+		struct ingenic_gpio_chip *jzgc, struct device_node *np)
+{
+	int err;
+
+	jzgc->pinctrl = jzpc;
+	snprintf(jzgc->name, sizeof(jzgc->name), "P%c", 'A' + jzgc->idx);
+
+	jzgc->base = of_iomap(np, 0);
+	if (!jzgc->base) {
+		dev_err(jzpc->dev, "failed to map IO memory\n");
+		return -ENXIO;
+	}
+
+	jzgc->gc.base = jzpc->base + (jzgc->idx * PINS_PER_GPIO_PORT);
+	jzgc->gc.ngpio = PINS_PER_GPIO_PORT;
+	jzgc->gc.parent = jzpc->dev;
+	jzgc->gc.of_node = np;
+	jzgc->gc.label = np->name;
+	jzgc->gc.owner = THIS_MODULE;
+
+	jzgc->gc.set = ingenic_gpio_set;
+	jzgc->gc.get = ingenic_gpio_get;
+	jzgc->gc.direction_input = ingenic_gpio_direction_input;
+	jzgc->gc.direction_output = ingenic_gpio_direction_output;
+
+	if (of_property_read_u32_index(np, "ingenic,pull-ups", 0,
+				&jzgc->pull_ups))
+		jzgc->pull_ups = 0;
+	if (of_property_read_u32_index(np, "ingenic,pull-downs", 0,
+				&jzgc->pull_downs))
+		jzgc->pull_downs = 0;
+
+	if (jzgc->pull_ups & jzgc->pull_downs) {
+		dev_err(jzpc->dev, "GPIO port %c has overlapping pull ups & pull downs\n",
+			'A' + jzgc->idx);
+		return -EINVAL;
+	}
+
+	err = devm_gpiochip_add_data(jzpc->dev, &jzgc->gc, NULL);
+	if (err)
+		return err;
+
+	if (!of_find_property(np, "interrupt-controller", NULL))
+		return 0;
+
+	jzgc->irq = irq_of_parse_and_map(np, 0);
+	if (!jzgc->irq)
+		return -EINVAL;
+
+	jzgc->irq_chip.name = jzgc->name;
+	jzgc->irq_chip.irq_unmask = ingenic_gpio_irq_unmask;
+	jzgc->irq_chip.irq_mask = ingenic_gpio_irq_mask;
+	jzgc->irq_chip.irq_ack = ingenic_gpio_irq_ack;
+	jzgc->irq_chip.irq_set_type = ingenic_gpio_irq_set_type;
+	jzgc->irq_chip.irq_set_wake = ingenic_gpio_irq_set_wake;
+	jzgc->irq_chip.flags = IRQCHIP_MASK_ON_SUSPEND;
+
+	err = gpiochip_irqchip_add(&jzgc->gc, &jzgc->irq_chip, 0,
+			handle_level_irq, IRQ_TYPE_NONE);
+	if (err)
+		return err;
+
+	gpiochip_set_chained_irqchip(&jzgc->gc, &jzgc->irq_chip,
+			jzgc->irq, ingenic_gpio_irq_handler);
+	return 0;
+}
+
+static int find_gpio_chip_by_of_node(struct gpio_chip *chip, void *data)
+{
+	return chip->of_node == data;
+}
+
+static int ingenic_pinctrl_parse_dt_pincfg(struct ingenic_pinctrl *jzpc,
+		struct ingenic_pinctrl_pin *pin, phandle cfg_handle)
+{
+	struct device_node *cfg_node;
+	int err;
+
+	cfg_node = of_find_node_by_phandle(cfg_handle);
+	if (!cfg_node)
+		return -EINVAL;
+
+	err = pinconf_generic_parse_dt_config(cfg_node, NULL,
+			&pin->configs, &pin->num_configs);
+	if (err)
+		return err;
+
+	err = devm_add_action(jzpc->dev, (void (*)(void *))kfree, pin->configs);
+	if (err) {
+		kfree(pin->configs);
+		return err;
+	}
+
+	return 0;
+}
+
+static int ingenic_pinctrl_parse_dt_func(struct ingenic_pinctrl *jzpc,
+		struct device_node *np, unsigned int *ifunc,
+		unsigned int *igroup)
+{
+	struct ingenic_pinctrl_func *func;
+	struct ingenic_pinctrl_group *grp;
+	struct device_node *group_node, *gpio_node;
+	struct gpio_chip *gpio_chip;
+	phandle gpio_handle, cfg_handle;
+	struct property *pp;
+	__be32 *plist;
+	unsigned int i, j;
+	int err;
+	const unsigned int vals_per_pin = 4;
+
+	func = &jzpc->funcs[(*ifunc)++];
+	func->of_node = np;
+	func->name = np->name;
+
+	func->num_groups = of_get_child_count(np);
+	func->groups = devm_kzalloc(jzpc->dev, sizeof(*func->groups) *
+			func->num_groups, GFP_KERNEL);
+	func->group_names = devm_kzalloc(jzpc->dev,
+			sizeof(*func->group_names) * func->num_groups,
+			GFP_KERNEL);
+	if (!func->groups || !func->group_names)
+		return -ENOMEM;
+
+	i = 0;
+	for_each_child_of_node(np, group_node) {
+		pp = of_find_property(group_node, "ingenic,pins", NULL);
+		if (!pp)
+			return -EINVAL;
+		if ((pp->length / sizeof(__be32)) % vals_per_pin)
+			return -EINVAL;
+
+		grp = &jzpc->groups[(*igroup)++];
+		grp->of_node = group_node;
+		grp->name = group_node->name;
+		grp->num_pins = (pp->length / sizeof(__be32)) / vals_per_pin;
+		grp->pins = devm_kzalloc(jzpc->dev, sizeof(*grp->pins) *
+				grp->num_pins, GFP_KERNEL);
+		grp->pin_indices = devm_kzalloc(jzpc->dev,
+				sizeof(*grp->pin_indices) * grp->num_pins,
+				GFP_KERNEL);
+		if (!grp->pins)
+			return -EINVAL;
+
+		plist = pp->value;
+		for (j = 0; j < grp->num_pins; j++) {
+			gpio_handle = be32_to_cpup(plist++);
+			grp->pins[j].idx = be32_to_cpup(plist++);
+			grp->pins[j].func = be32_to_cpup(plist++);
+			cfg_handle = be32_to_cpup(plist++);
+
+			gpio_node = of_find_node_by_phandle(gpio_handle);
+			if (!gpio_node)
+				return -EINVAL;
+
+			gpio_chip = gpiochip_find(gpio_node,
+					find_gpio_chip_by_of_node);
+			if (!gpio_chip)
+				return -EINVAL;
+
+			grp->pins[j].gpio_chip = gc_to_jzgc(gpio_chip);
+
+			err = ingenic_pinctrl_parse_dt_pincfg(jzpc,
+					&grp->pins[j], cfg_handle);
+			if (err)
+				return err;
+
+			grp->pins[j].idx += grp->pins[j].gpio_chip->idx *
+				PINS_PER_GPIO_PORT;
+			grp->pin_indices[j] = grp->pins[j].idx;
+		}
+
+		func->groups[i] = grp;
+		func->group_names[i] = grp->name;
+		i++;
+	}
+
+	return 0;
+}
+
+int ingenic_pinctrl_probe(struct platform_device *pdev,
+		const struct ingenic_pinctrl_ops *ops)
+{
+	struct device *dev = &pdev->dev;
+	struct ingenic_pinctrl *jzpc;
+	struct ingenic_gpio_chip *jzgc;
+	struct pinctrl_desc *pctl_desc;
+	struct device_node *np, *chips_node, *functions_node;
+	unsigned int i, j;
+	int err;
+
+	if (!dev->of_node) {
+		dev_err(dev, "device tree node not found\n");
+		return -ENODEV;
+	}
+
+	jzpc = devm_kzalloc(dev, sizeof(*jzpc), GFP_KERNEL);
+	if (!jzpc)
+		return -ENOMEM;
+
+	jzpc->dev = dev;
+	platform_set_drvdata(pdev, jzpc);
+
+	jzpc->base = 0;
+	of_property_read_u32(dev->of_node, "base", &jzpc->base);
+
+	chips_node = of_find_node_by_name(dev->of_node, "gpio-chips");
+	if (!chips_node) {
+		dev_err(dev, "Missing \"chips\" devicetree node\n");
+		return -EINVAL;
+	}
+
+	jzpc->num_gpio_chips = of_get_available_child_count(chips_node);
+	if (!jzpc->num_gpio_chips) {
+		dev_err(dev, "No GPIO chips found\n");
+		return -EINVAL;
+	}
+
+	functions_node = of_find_node_by_name(dev->of_node, "functions");
+	if (!functions_node) {
+		dev_err(dev, "Missing \"functions\" devicetree node\n");
+		return -EINVAL;
+	}
+
+	jzpc->num_funcs = of_get_available_child_count(functions_node);
+	if (!jzpc->num_funcs) {
+		dev_err(dev, "No functions found\n");
+		return -EINVAL;
+	}
+
+	for_each_child_of_node(functions_node, np) {
+		jzpc->num_groups += of_get_available_child_count(np);
+	}
+
+	if (!jzpc->num_groups) {
+		dev_err(dev, "No groups found\n");
+		return -EINVAL;
+	}
+
+	/* allocate memory for GPIO chips, pin groups & functions */
+	jzpc->gpio_chips = devm_kzalloc(jzpc->dev, sizeof(*jzpc->gpio_chips) *
+			jzpc->num_gpio_chips, GFP_KERNEL);
+	jzpc->groups = devm_kzalloc(jzpc->dev, sizeof(*jzpc->groups) *
+			jzpc->num_groups, GFP_KERNEL);
+	jzpc->funcs = devm_kzalloc(jzpc->dev, sizeof(*jzpc->funcs) *
+			jzpc->num_funcs, GFP_KERNEL);
+	pctl_desc = devm_kzalloc(&pdev->dev, sizeof(*pctl_desc), GFP_KERNEL);
+	if (!jzpc->gpio_chips || !jzpc->groups || !jzpc->funcs || !pctl_desc)
+		return -ENOMEM;
+
+	/* fill in pinctrl_desc structure */
+	pctl_desc->name = dev_name(dev);
+	pctl_desc->owner = THIS_MODULE;
+	pctl_desc->pctlops = &ingenic_pctlops;
+	pctl_desc->pmxops = &ingenic_pmxops;
+	pctl_desc->confops = &ingenic_confops;
+	pctl_desc->npins = jzpc->num_gpio_chips * PINS_PER_GPIO_PORT;
+	pctl_desc->pins = jzpc->pdesc = devm_kzalloc(&pdev->dev,
+			sizeof(*jzpc->pdesc) * pctl_desc->npins, GFP_KERNEL);
+	if (!jzpc->pdesc)
+		return -ENOMEM;
+
+	for (i = 0; i < pctl_desc->npins; i++) {
+		jzpc->pdesc[i].number = i;
+		jzpc->pdesc[i].name = kasprintf(GFP_KERNEL, "P%c%d",
+						'A' + (i / PINS_PER_GPIO_PORT),
+						i % PINS_PER_GPIO_PORT);
+	}
+
+	/* Register GPIO chips */
+
+	i = 0;
+	for_each_child_of_node(chips_node, np) {
+		if (!of_find_property(np, "gpio-controller", NULL)) {
+			dev_err(dev, "GPIO chip missing \"gpio-controller\" flag\n");
+			return -EINVAL;
+		}
+
+		jzpc->gpio_chips[i].idx = i;
+		jzpc->gpio_chips[i].ops = ops;
+
+		err = ingenic_pinctrl_parse_dt_gpio(jzpc,
+				&jzpc->gpio_chips[i++], np);
+		if (err) {
+			dev_err(dev, "failed to register GPIO chip: %d\n", err);
+			return err;
+		}
+	}
+
+	i = 0;
+	j = 0;
+	for_each_child_of_node(functions_node, np) {
+		err = ingenic_pinctrl_parse_dt_func(jzpc, np, &i, &j);
+		if (err) {
+			dev_err(dev, "failed to parse function %s\n",
+					np->full_name);
+			return err;
+		}
+	}
+
+	for (i = 0; i < jzpc->num_groups; i++)
+		dev_dbg(dev, "group '%s'\n", jzpc->groups[i].name);
+	for (i = 0; i < jzpc->num_funcs; i++)
+		dev_dbg(dev, "func '%s'\n", jzpc->funcs[i].name);
+
+	jzpc->pctl = pinctrl_register(pctl_desc, dev, jzpc);
+	if (!jzpc->pctl) {
+		dev_err(dev, "Failed pinctrl registration\n");
+		return -EINVAL;
+	}
+
+	/* register pinctrl GPIO ranges */
+	for (i = 0; i < jzpc->num_gpio_chips; i++) {
+		jzgc = &jzpc->gpio_chips[i];
+
+		jzgc->grange.name = jzgc->name;
+		jzgc->grange.id = jzgc->idx;
+		jzgc->grange.pin_base = jzgc->idx * PINS_PER_GPIO_PORT;
+		jzgc->grange.base = jzgc->gc.base;
+		jzgc->grange.npins = jzgc->gc.ngpio;
+		jzgc->grange.gc = &jzgc->gc;
+		pinctrl_add_gpio_range(jzpc->pctl, &jzgc->grange);
+	}
+
+	return 0;
+}
diff --git a/drivers/pinctrl/ingenic/pinctrl-ingenic.h b/drivers/pinctrl/ingenic/pinctrl-ingenic.h
new file mode 100644
index 000000000000..76cb7ffa68e5
--- /dev/null
+++ b/drivers/pinctrl/ingenic/pinctrl-ingenic.h
@@ -0,0 +1,42 @@
+/*
+ * Ingenic SoCs pinctrl driver
+ *
+ * Copyright (c) 2013 Imagination Technologies
+ * Copyright (c) 2017 Paul Cercueil <paul@xxxxxxxxxxxxxxx>
+ *
+ * Authors: Paul Burton <paul.burton@xxxxxxxxxx>,
+ *          Paul Cercueil <paul@xxxxxxxxxxxxxxx>
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#ifndef PINCTRL_INGENIC_H
+#define PINCTRL_INGENIC_H
+
+#include <linux/compiler.h>
+#include <linux/types.h>
+
+struct platform_device;
+
+struct ingenic_pinctrl_ops {
+	unsigned int nb_functions;
+
+	void (*set_function)(void __iomem *base,
+			unsigned int offset, unsigned int function);
+	void (*set_gpio)(void __iomem *base, unsigned int offset, bool output);
+	int  (*get_bias)(void __iomem *base, unsigned int offset);
+	void (*set_bias)(void __iomem *base, unsigned int offset, bool enable);
+	void (*gpio_set_value)(void __iomem *base,
+			unsigned int offset, int value);
+	int  (*gpio_get_value)(void __iomem *base, unsigned int offset);
+	u32  (*irq_read)(void __iomem *base);
+	void (*irq_mask)(void __iomem *base, unsigned int irq, bool mask);
+	void (*irq_ack)(void __iomem *base, unsigned int irq);
+	void (*irq_set_type)(void __iomem *base,
+			unsigned int irq, unsigned int type);
+};
+
+int ingenic_pinctrl_probe(struct platform_device *pdev,
+		const struct ingenic_pinctrl_ops *ops);
+
+#endif /* PINCTRL_INGENIC_H */
diff --git a/drivers/pinctrl/ingenic/pinctrl-jz4740.c b/drivers/pinctrl/ingenic/pinctrl-jz4740.c
new file mode 100644
index 000000000000..ae0b9d903258
--- /dev/null
+++ b/drivers/pinctrl/ingenic/pinctrl-jz4740.c
@@ -0,0 +1,190 @@
+/*
+ * Ingenic jz4740 pinctrl driver
+ *
+ * Copyright (c) 2013 Imagination Technologies
+ * Copyright (c) 2017 Paul Cercueil <paul@xxxxxxxxxxxxxxx>
+ *
+ * Authors: Paul Burton <paul.burton@xxxxxxxxxx>,
+ *          Paul Cercueil <paul@xxxxxxxxxxxxxxx>
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include "pinctrl-ingenic.h"
+
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+/* GPIO port register offsets */
+#define GPIO_PIN	0x00
+#define GPIO_DATA	0x10
+#define GPIO_DATAS	0x14
+#define GPIO_DATAC	0x18
+#define GPIO_MASK	0x20
+#define GPIO_MASKS	0x24
+#define GPIO_MASKC	0x28
+#define GPIO_PULL_DIS	0x30
+#define GPIO_PULL_DISS	0x34
+#define GPIO_PULL_DISC	0x38
+#define GPIO_FUNC	0x40
+#define GPIO_FUNCS	0x44
+#define GPIO_FUNCC	0x48
+#define GPIO_SELECT	0x50
+#define GPIO_SELECTS	0x54
+#define GPIO_SELECTC	0x58
+#define GPIO_DIR	0x60
+#define GPIO_DIRS	0x64
+#define GPIO_DIRC	0x68
+#define GPIO_TRIG	0x70
+#define GPIO_TRIGS	0x74
+#define GPIO_TRIGC	0x78
+#define GPIO_FLAG	0x80
+#define GPIO_FLAGC	0x14
+#define GPIO_REGS_SIZE	0x100
+
+static void jz4740_set_gpio(void __iomem *base,
+		unsigned int offset, bool output)
+{
+	writel(1 << offset, base + GPIO_FUNCC);
+	writel(1 << offset, base + GPIO_SELECTC);
+	writel(1 << offset, base + GPIO_TRIGC);
+
+	if (output)
+		writel(1 << offset, base + GPIO_DIRS);
+	else
+		writel(1 << offset, base + GPIO_DIRC);
+}
+
+static int jz4740_get_bias(void __iomem *base, unsigned int offset)
+{
+	return !((readl(base + GPIO_PULL_DIS) >> offset) & 0x1);
+}
+
+static void jz4740_set_bias(void __iomem *base,
+		unsigned int offset, bool enable)
+{
+	if (enable)
+		writel(1 << offset, base + GPIO_PULL_DISC);
+	else
+		writel(1 << offset, base + GPIO_PULL_DISS);
+}
+
+static void jz4740_gpio_set_value(void __iomem *base,
+		unsigned int offset, int value)
+{
+	if (value)
+		writel(1 << offset, base + GPIO_DATAS);
+	else
+		writel(1 << offset, base + GPIO_DATAC);
+}
+
+static int jz4740_gpio_get_value(void __iomem *base, unsigned int offset)
+{
+	return (readl(base + GPIO_DATA) >> offset) & 0x1;
+}
+
+static u32 jz4740_irq_read(void __iomem *base)
+{
+	return readl(base + GPIO_FLAG);
+}
+
+static void jz4740_irq_mask(void __iomem *base, unsigned int irq, bool mask)
+{
+	if (mask)
+		writel(1 << irq, base + GPIO_MASKS);
+	else
+		writel(1 << irq, base + GPIO_MASKC);
+}
+
+static void jz4740_irq_ack(void __iomem *base, unsigned int irq)
+{
+	writel(1 << irq, base + GPIO_FLAGC);
+}
+
+static void jz4740_irq_set_type(void __iomem *base,
+		unsigned int offset, unsigned int type)
+{
+	switch (type) {
+	case IRQ_TYPE_EDGE_RISING:
+		writel(1 << offset, base + GPIO_DIRS);
+		writel(1 << offset, base + GPIO_TRIGS);
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		writel(1 << offset, base + GPIO_DIRC);
+		writel(1 << offset, base + GPIO_TRIGS);
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+		writel(1 << offset, base + GPIO_DIRS);
+		writel(1 << offset, base + GPIO_TRIGC);
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+	default:
+		writel(1 << offset, base + GPIO_DIRC);
+		writel(1 << offset, base + GPIO_TRIGC);
+		break;
+	}
+}
+
+static void jz4740_set_function(void __iomem *base,
+		unsigned int offset, unsigned int func)
+{
+	writel(1 << offset, base + GPIO_FUNCS);
+	writel(1 << offset, base + GPIO_TRIGC);
+
+	switch (func) {
+	case 2:
+		writel(1 << offset, base + GPIO_TRIGS);
+	case 1: /* fallthrough */
+		writel(1 << offset, base + GPIO_SELECTS);
+		break;
+	case 0:
+	default:
+		writel(1 << offset, base + GPIO_SELECTC);
+		break;
+	}
+}
+
+static const struct ingenic_pinctrl_ops jz4740_pinctrl_ops = {
+	.nb_functions	= 3,
+	.set_function	= jz4740_set_function,
+	.set_gpio	= jz4740_set_gpio,
+	.set_bias	= jz4740_set_bias,
+	.get_bias	= jz4740_get_bias,
+	.gpio_set_value	= jz4740_gpio_set_value,
+	.gpio_get_value	= jz4740_gpio_get_value,
+	.irq_read	= jz4740_irq_read,
+	.irq_mask	= jz4740_irq_mask,
+	.irq_ack	= jz4740_irq_ack,
+	.irq_set_type	= jz4740_irq_set_type,
+};
+
+static int jz4740_pinctrl_probe(struct platform_device *pdev)
+{
+	return ingenic_pinctrl_probe(pdev, &jz4740_pinctrl_ops);
+}
+
+static const struct of_device_id jz4740_pinctrl_dt_match[] = {
+	{ .compatible = "ingenic,jz4740-pinctrl", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, jz4740_pinctrl_dt_match);
+
+
+static struct platform_driver jz4740_pinctrl_driver = {
+	.driver = {
+		.name = "jz4740-pinctrl",
+		.of_match_table = of_match_ptr(jz4740_pinctrl_dt_match),
+		.suppress_bind_attrs = true,
+	},
+	.probe = jz4740_pinctrl_probe,
+};
+
+static int __init jz4740_pinctrl_drv_register(void)
+{
+	return platform_driver_register(&jz4740_pinctrl_driver);
+}
+postcore_initcall(jz4740_pinctrl_drv_register);
diff --git a/include/dt-bindings/pinctrl/ingenic.h b/include/dt-bindings/pinctrl/ingenic.h
new file mode 100644
index 000000000000..19eb173844b1
--- /dev/null
+++ b/include/dt-bindings/pinctrl/ingenic.h
@@ -0,0 +1,11 @@
+#ifndef DT_BINDINGS_PINCTRL_INGENIC_H
+#define DT_BINDINGS_PINCTRL_INGENIC_H
+
+#define JZ_PIN_MODE_FUNCTION_0	0
+#define JZ_PIN_MODE_FUNCTION_1	1
+#define JZ_PIN_MODE_FUNCTION_2	2
+#define JZ_PIN_MODE_FUNCTION_3	3
+
+#define JZ_PIN_MODE_GPIO	255
+
+#endif /* DT_BINDINGS_PINCTRL_INGENIC_H */
-- 
2.11.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux