[RFC,2/3] pinctrl: e3xx: Adding support for NI Ettus Research USRP E3xx pinconf
diff mbox

Message ID 1446766883-25703-3-git-send-email-moritz.fischer@ettus.com
State New
Headers show

Commit Message

Moritz Fischer Nov. 5, 2015, 11:41 p.m. UTC
The USRP E3XX series requires pinctrl to configure the idle state
FPGA image for minimizing power consumption.
This is required since different daughtercards have different uses
for pins on a common connector.

Signed-off-by: Moritz Fischer <moritz.fischer@ettus.com>
---
 drivers/pinctrl/Kconfig        |  11 ++
 drivers/pinctrl/Makefile       |   1 +
 drivers/pinctrl/pinctrl-e3xx.c | 403 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 415 insertions(+)
 create mode 100644 drivers/pinctrl/pinctrl-e3xx.c

Comments

Andy Shevchenko Nov. 6, 2015, 8:56 p.m. UTC | #1
On Fri, Nov 6, 2015 at 1:41 AM, Moritz Fischer <moritz.fischer@ettus.com> wrote:
> The USRP E3XX series requires pinctrl to configure the idle state
> FPGA image for minimizing power consumption.
> This is required since different daughtercards have different uses
> for pins on a common connector.

> +#include <linux/pinctrl/pinconf-generic.h>

+ empty line?

> +#include "pinctrl-utils.h"
> +#include "core.h"

> +static int e3xx_pinconf_cfg_get(struct pinctrl_dev *pctldev,
> +                               unsigned pin,
> +                               unsigned long *config)
> +{
> +       u32 reg, mask;
> +       int arg;
> +       struct e3xx_pinctrl *pctrl;
> +       unsigned int param;
> +
> +       param = pinconf_to_config_param(*config);
> +       pctrl = pinctrl_dev_get_drvdata(pctldev);
> +
> +       if (pin >= E3XX_NUM_DB_PINS)
> +               return -ENOTSUPP;
> +
> +       mask = BIT(pin % E3XX_PINS_PER_REG);
> +
> +       switch (param) {
> +       case PIN_CONFIG_OUTPUT:
> +               clk_enable(pctrl->clk);
> +               reg = e3xx_pinctrl_read(pctrl, E3XX_DDR_OFFSET +
> +                                       (pin / E3XX_PINS_PER_REG) * 4);
> +
> +               clk_disable(pctrl->clk);
> +               arg = !!(reg & mask);

So, for now arg variable seems redundant, you may use expression
inside call below.

> +               break;
> +       default:
> +               dev_err(pctrl->dev, "requested illegal configuration");
> +               return -ENOTSUPP;
> +       };
> +
> +       *config = pinconf_to_config_packed(param, arg);
> +
> +       return 0;
> +}
> +
> +static int e3xx_pinconf_cfg_set(struct pinctrl_dev *pctldev,
> +                               unsigned pin,
> +                               unsigned long *configs,
> +                               unsigned num_configs)
> +{
> +       u32 reg, mask;
> +       int i;

unsigned

> +       struct e3xx_pinctrl *pctrl;
> +       unsigned int param, arg;
> +
> +       if (pin >= E3XX_NUM_DB_PINS)
> +               return -ENOTSUPP;
> +       mask = BIT(pin % E3XX_PINS_PER_REG);
> +
> +       pctrl = pinctrl_dev_get_drvdata(pctldev);
> +
> +       clk_enable(pctrl->clk);
> +
> +       for (i = 0; i < num_configs; i++) {
> +               param = pinconf_to_config_param(configs[i]);
> +               arg = pinconf_to_config_argument(configs[i]);
> +
> +               switch (param) {
> +               case PIN_CONFIG_OUTPUT:
> +                       /* deal with value, set out bit if arg is 1 */
> +                       reg = e3xx_pinctrl_read(pctrl, E3XX_OUT_OFFSET +
> +                                               ((pin / E3XX_PINS_PER_REG) * 4))
> +                               ;

Why not one the previous line?

> +                       reg &= ~mask;
> +                       if (arg)
> +                               reg |= mask;



> +
> +                       /* addresses need to be 4 byte aligned */
> +                       e3xx_pinctrl_write(pctrl, E3XX_OUT_OFFSET +
> +                                          ((pin / E3XX_PINS_PER_REG) * 4), reg)
> +                               ;
> +
> +                       /* set ddr bit to high for output */
> +                       reg = e3xx_pinctrl_read(pctrl, E3XX_DDR_OFFSET +
> +                                               ((pin / E3XX_PINS_PER_REG) * 4))
> +                               ;

Ditto.

> +                       reg |= mask;
> +
> +                       /* addresses need to be 4 byte aligned */
> +                       e3xx_pinctrl_write(pctrl, E3XX_DDR_OFFSET +
> +                                         ((pin / E3XX_PINS_PER_REG) * 4), reg);
> +                       break;
> +
> +               default:
> +                       clk_disable(pctrl->clk);
> +                       return -ENOTSUPP;
> +               };
> +       }
> +
> +       clk_disable(pctrl->clk);
> +
> +       return 0;
> +}
> +
> +static int e3xx_pinconf_group_set(struct pinctrl_dev *pctldev,
> +                                 unsigned selector,
> +                                 unsigned long *configs,
> +                                 unsigned num_configs)
> +{
> +       return -EAGAIN;
> +}
> +
> +static const struct pinconf_ops e3xx_pinconf_ops = {
> +       .is_generic = true,
> +       .pin_config_get = e3xx_pinconf_cfg_get,
> +       .pin_config_set = e3xx_pinconf_cfg_set,
> +       .pin_config_group_set = e3xx_pinconf_group_set,
> +};
> +
> +static struct pinctrl_desc e3xx_desc = {
> +       .name = "e3xx_pinctrl",
> +       .pins = e3xx_pins,
> +       .npins = ARRAY_SIZE(e3xx_pins),
> +       .pctlops = &e3xx_pctrl_ops,
> +       .confops = &e3xx_pinconf_ops,
> +       .owner = THIS_MODULE,
> +};
> +
> +static int e3xx_pinctrl_probe(struct platform_device *pdev)
> +{
> +       struct resource *res;
> +       struct e3xx_pinctrl *pctrl;
> +       int err;
> +
> +       pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL);
> +       if (!pctrl)
> +               return -ENOMEM;
> +       pctrl->dev = &pdev->dev;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

> +       if (!res) {
> +               dev_err(&pdev->dev, "missing IO resource\n");
> +               return -ENODEV;
> +       }

Whole condition is redundant.

> +
> +       pctrl->io_base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(pctrl->io_base))
> +               return PTR_ERR(pctrl->io_base);
> +
> +       pctrl->clk = devm_clk_get(&pdev->dev, NULL);
> +       if (IS_ERR(pctrl->clk)) {
> +               dev_err(&pdev->dev, "input clock not found");
> +               return PTR_ERR(pctrl->clk);
> +       }
> +
> +       err = clk_prepare(pctrl->clk);
> +       if (err) {
> +               dev_err(&pdev->dev, "unable to prepare clock");
> +               return err;
> +       }
> +
> +       pctrl->pctl = pinctrl_register(&e3xx_desc, &pdev->dev, pctrl);
> +       if (!pctrl->pctl)
> +               return -ENOMEM;
> +
> +       platform_set_drvdata(pdev, pctrl);
> +
> +       dev_info(&pdev->dev, "NI Ettus Research E3xx pinctrl initialized\n");
> +
> +       return 0;
> +}
> +
> +static int e3xx_pinctrl_remove(struct platform_device *pdev)
> +{
> +       struct e3xx_pinctrl *pctrl = platform_get_drvdata(pdev);
> +
> +       pinctrl_unregister(pctrl->pctl);
> +
> +       clk_unprepare(pctrl->clk);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id e3xx_pinctrl_of_match[] = {
> +       { .compatible = "ettus,e3xx-pinctrl-1.0", },
> +       {},
> +};
> +
> +MODULE_DEVICE_TABLE(of, e3xx_pinctrl_of_match);
> +#endif
> +
> +static struct platform_driver e3xx_pinctrl_driver = {
> +       .probe = e3xx_pinctrl_probe,
> +       .remove = e3xx_pinctrl_remove,
> +       .driver = {
> +               .name = "e3xx_pinctrl",
> +               .of_match_table = of_match_ptr(e3xx_pinctrl_of_match),
> +       },
> +};
> +
> +module_platform_driver(e3xx_pinctrl_driver);
> +
> +MODULE_AUTHOR("Moritz Fischer <moritz.fischer@ettus.com>");
> +MODULE_DESCRIPTION("Ettus Research pinctrl driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.4.3
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
Linus Walleij Nov. 9, 2015, 10:21 a.m. UTC | #2
On Fri, Nov 6, 2015 at 12:41 AM, Moritz Fischer
<moritz.fischer@ettus.com> wrote:

> The USRP E3XX series requires pinctrl to configure the idle state
> FPGA image for minimizing power consumption.
> This is required since different daughtercards have different uses
> for pins on a common connector.
>
> Signed-off-by: Moritz Fischer <moritz.fischer@ettus.com>
(...)

> +static const struct pinctrl_pin_desc e3xx_pins[] = {
> +       /* pin0 doesn't exist */
> +       PINCTRL_PIN(1, "DB_1"),
> +       PINCTRL_PIN(2, "DB_2"),
(...)
> +       PINCTRL_PIN(119, "DB_119"),
> +       PINCTRL_PIN(120, "DB_120"),
> +};

These should be the names on the data sheet for the balls/pins on the ASIC.
Is this the case? I saw some discussion with Arnd that indicated it was
rather rail names for a certain board and that is not OK.

> +static int e3xx_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
> +{
> +       return 0;
> +}
> +
> +static const char *e3xx_pinctrl_get_group_name(struct pinctrl_dev *pctldev,
> +                                              unsigned selector)
> +{
> +       return NULL;
> +}
> +
> +static int e3xx_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
> +                                      unsigned selector,
> +                                      const unsigned **pins,
> +                                      unsigned *num_pins)
> +{
> +       return -ENOTSUPP;
> +}

Hm maybe we should make these group callbacks optional
in the pinctrl core?

> +static int e3xx_pinconf_cfg_set(struct pinctrl_dev *pctldev,
> +                               unsigned pin,
> +                               unsigned long *configs,
> +                               unsigned num_configs)
> +{
> +       u32 reg, mask;
> +       int i;
> +       struct e3xx_pinctrl *pctrl;
> +       unsigned int param, arg;
> +
> +       if (pin >= E3XX_NUM_DB_PINS)
> +               return -ENOTSUPP;
> +       mask = BIT(pin % E3XX_PINS_PER_REG);
> +
> +       pctrl = pinctrl_dev_get_drvdata(pctldev);
> +
> +       clk_enable(pctrl->clk);

Have you considered using pm_runtime_get_sync() etc with
callbacks instead of hammering clk_enable/disable all the
time? See
drivers/hwtracing/coresight/coresight-replicator.c
for an example of how to do it in the runtime PM ops
callbacks. It requires some tweaks (look closely at all setup
there) but it pays off.

> +static int e3xx_pinconf_group_set(struct pinctrl_dev *pctldev,
> +                                 unsigned selector,
> +                                 unsigned long *configs,
> +                                 unsigned num_configs)
> +{
> +       return -EAGAIN;
> +}

Maybe you should group this with the other group callbacks.

Apart from these remarks it looks pretty nice.

Yours,
Linus Walleij
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Moritz Fischer Nov. 10, 2015, 3:55 a.m. UTC | #3
Linus,

Thanks for your review!

On Mon, Nov 9, 2015 at 2:21 AM, Linus Walleij <linus.walleij@linaro.org> wrote:
> On Fri, Nov 6, 2015 at 12:41 AM, Moritz Fischer
> <moritz.fischer@ettus.com> wrote:
>
>> The USRP E3XX series requires pinctrl to configure the idle state
>> FPGA image for minimizing power consumption.
>> This is required since different daughtercards have different uses
>> for pins on a common connector.
>>
>> Signed-off-by: Moritz Fischer <moritz.fischer@ettus.com>
> (...)
>
>> +static const struct pinctrl_pin_desc e3xx_pins[] = {
>> +       /* pin0 doesn't exist */
>> +       PINCTRL_PIN(1, "DB_1"),
>> +       PINCTRL_PIN(2, "DB_2"),
> (...)
>> +       PINCTRL_PIN(119, "DB_119"),
>> +       PINCTRL_PIN(120, "DB_120"),
>> +};
>
> These should be the names on the data sheet for the balls/pins on the ASIC.
> Is this the case? I saw some discussion with Arnd that indicated it was
> rather rail names for a certain board and that is not OK.

Well so the chip is an FPGA, so in theory you can connect any logic
inside to (almost)
any ball on the outside (at design time). In our case we have a
daughter-board slot that gives
different meanings to what I call DB_1-DB_120. The pinctrl chip is a
soft-core in that FPGA.
Are you suggesting to use the balls of the FPGA as pin names instead?

>> +static int e3xx_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
>> +{
>> +       return 0;
>> +}
>> +
>> +static const char *e3xx_pinctrl_get_group_name(struct pinctrl_dev *pctldev,
>> +                                              unsigned selector)
>> +{
>> +       return NULL;
>> +}
>> +
>> +static int e3xx_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
>> +                                      unsigned selector,
>> +                                      const unsigned **pins,
>> +                                      unsigned *num_pins)
>> +{
>> +       return -ENOTSUPP;
>> +}
>
> Hm maybe we should make these group callbacks optional
> in the pinctrl core?

I'll submit a patchset separately, seems like a good idea.
>
>> +static int e3xx_pinconf_cfg_set(struct pinctrl_dev *pctldev,
>> +                               unsigned pin,daughterboard
>> +                               unsigned long *configs,
>> +                               unsigned num_configs)
>> +{
>> +       u32 reg, mask;
>> +       int i;
>> +       struct e3xx_pinctrl *pctrl;
>> +       unsigned int param, arg;
>> +
>> +       if (pin >= E3XX_NUM_DB_PINS)
>> +               return -ENOTSUPP;
>> +       mask = BIT(pin % E3XX_PINS_PER_REG);
>> +
>> +       pctrl = pinctrl_dev_get_drvdata(pctldev);
>> +
>> +       clk_enable(pctrl->clk);
>
> Have you considered using pm_runtime_get_sync() etc with
> callbacks instead of hammering clk_enable/disable all the
> time? See
> drivers/hwtracing/coresight/coresight-replicator.c
> for an example of how to do it in the runtime PM ops
> callbacks. It requires some tweaks (look closely at all setup
> there) but it pays off.

Thanks for the pointer, I'll take a look.

>> +static int e3xx_pinconf_group_set(struct pinctrl_dev *pctldev,
>> +                                 unsigned selector,
>> +                                 unsigned long *configs,
>> +                                 unsigned num_configs)
>> +{
>> +       return -EAGAIN;
>> +}
>
> Maybe you should group this with the other group callbacks.

Will do.
>
> Apart from these remarks it looks pretty nice.

While testing I found some more (minor) issues, that I'll need to iron out
before I submit a real patch. In my cover letter I had a question about
how to deal with a situation where I'm exclusively either input or output,
Any suggestions on the correct semantics for expressing that?
The documentation seems to indicate that 'input-enable' *must not* influence
the output of a pin. How do I reconfigure a output to no longer be an output?

Cheers,

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

Patch
diff mbox

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index b422e4e..fb2e195 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -101,6 +101,17 @@  config PINCTRL_DIGICOLOR
 	select PINMUX
 	select GENERIC_PINCONF
 
+config PINCTRL_E3XX
+	bool "NI Ettus Research USRP E3xx pinctrl driver"
+	depends on OF
+	select PINCONF
+	select GENERIC_PINCONF
+	help
+	  Say Y here to support Ettus Research E3xx pinctrl driver, which is used for
+	  the NI Ettus Research USRP E3xx family of embedded SDRs, including E310
+	  and E330.
+	  This driver requires the pinctrl framework.
+
 config PINCTRL_LANTIQ
 	bool
 	depends on LANTIQ
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 738cb49..799f3ac 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -15,6 +15,7 @@  obj-$(CONFIG_PINCTRL_AT91)	+= pinctrl-at91.o
 obj-$(CONFIG_PINCTRL_AT91PIO4)	+= pinctrl-at91-pio4.o
 obj-$(CONFIG_PINCTRL_AMD)	+= pinctrl-amd.o
 obj-$(CONFIG_PINCTRL_DIGICOLOR)	+= pinctrl-digicolor.o
+obj-$(CONFIG_PINCTRL_E3XX)	+= pinctrl-e3xx.o
 obj-$(CONFIG_PINCTRL_FALCON)	+= pinctrl-falcon.o
 obj-$(CONFIG_PINCTRL_MESON)	+= meson/
 obj-$(CONFIG_PINCTRL_PALMAS)	+= pinctrl-palmas.o
diff --git a/drivers/pinctrl/pinctrl-e3xx.c b/drivers/pinctrl/pinctrl-e3xx.c
new file mode 100644
index 0000000..55576fd
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-e3xx.c
@@ -0,0 +1,403 @@ 
+/*
+ * Copyright (c) 2015 National Instruments Corp.
+ *
+ * Pinctrl Driver for Ettus Research E3XX series daughterboards
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * 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 <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include "pinctrl-utils.h"
+#include "core.h"
+
+#define E3XX_NUM_DB_PINS 120
+#define E3XX_PINS_PER_REG 32
+
+#define E3XX_DDR_OFFSET 0x00
+#define E3XX_OUT_OFFSET 0x20
+
+static const struct pinctrl_pin_desc e3xx_pins[] = {
+	/* pin0 doesn't exist */
+	PINCTRL_PIN(1, "DB_1"),
+	PINCTRL_PIN(2, "DB_2"),
+	PINCTRL_PIN(3, "DB_3"),
+	PINCTRL_PIN(4, "DB_4"),
+	PINCTRL_PIN(5, "DB_5"),
+	PINCTRL_PIN(6, "DB_6"),
+	PINCTRL_PIN(7, "DB_7"),
+	PINCTRL_PIN(8, "DB_8"),
+	PINCTRL_PIN(9, "DB_9"),
+	PINCTRL_PIN(10, "DB_10"),
+	PINCTRL_PIN(11, "DB_11"),
+	PINCTRL_PIN(12, "DB_12"),
+	PINCTRL_PIN(13, "DB_13"),
+	PINCTRL_PIN(14, "DB_14"),
+	PINCTRL_PIN(15, "DB_15"),
+	PINCTRL_PIN(16, "DB_16"),
+	PINCTRL_PIN(17, "DB_17"),
+	PINCTRL_PIN(18, "DB_18"),
+	PINCTRL_PIN(19, "DB_19"),
+	PINCTRL_PIN(20, "DB_20"),
+	PINCTRL_PIN(21, "DB_21"),
+	PINCTRL_PIN(22, "DB_22"),
+	PINCTRL_PIN(23, "DB_23"),
+	PINCTRL_PIN(24, "DB_24"),
+	PINCTRL_PIN(25, "DB_25"),
+	PINCTRL_PIN(26, "DB_26"),
+	PINCTRL_PIN(27, "DB_27"),
+	PINCTRL_PIN(28, "DB_28"),
+	PINCTRL_PIN(29, "DB_29"),
+	PINCTRL_PIN(30, "DB_30"),
+	PINCTRL_PIN(31, "DB_31"),
+	PINCTRL_PIN(32, "DB_32"),
+	PINCTRL_PIN(33, "DB_33"),
+	PINCTRL_PIN(34, "DB_34"),
+	PINCTRL_PIN(35, "DB_35"),
+	PINCTRL_PIN(36, "DB_36"),
+	PINCTRL_PIN(37, "DB_37"),
+	PINCTRL_PIN(38, "DB_38"),
+	PINCTRL_PIN(39, "DB_39"),
+	PINCTRL_PIN(40, "DB_40"),
+	PINCTRL_PIN(41, "DB_41"),
+	PINCTRL_PIN(42, "DB_42"),
+	PINCTRL_PIN(43, "DB_43"),
+	PINCTRL_PIN(44, "DB_44"),
+	PINCTRL_PIN(45, "DB_45"),
+	PINCTRL_PIN(46, "DB_46"),
+	PINCTRL_PIN(47, "DB_47"),
+	PINCTRL_PIN(48, "DB_48"),
+	PINCTRL_PIN(49, "DB_49"),
+	PINCTRL_PIN(50, "DB_50"),
+	PINCTRL_PIN(52, "DB_52"),
+	PINCTRL_PIN(53, "DB_53"),
+	PINCTRL_PIN(54, "DB_54"),
+	PINCTRL_PIN(55, "DB_55"),
+	PINCTRL_PIN(56, "DB_56"),
+	PINCTRL_PIN(57, "DB_57"),
+	PINCTRL_PIN(58, "DB_58"),
+	PINCTRL_PIN(59, "DB_59"),
+	PINCTRL_PIN(60, "DB_60"),
+	PINCTRL_PIN(61, "DB_61"),
+	PINCTRL_PIN(62, "DB_62"),
+	PINCTRL_PIN(63, "DB_63"),
+	PINCTRL_PIN(64, "DB_64"),
+	PINCTRL_PIN(65, "DB_65"),
+	PINCTRL_PIN(66, "DB_66"),
+	PINCTRL_PIN(67, "DB_67"),
+	PINCTRL_PIN(68, "DB_68"),
+	PINCTRL_PIN(69, "DB_69"),
+	PINCTRL_PIN(70, "DB_70"),
+	PINCTRL_PIN(71, "DB_71"),
+	PINCTRL_PIN(72, "DB_72"),
+	PINCTRL_PIN(73, "DB_73"),
+	PINCTRL_PIN(74, "DB_74"),
+	PINCTRL_PIN(75, "DB_75"),
+	PINCTRL_PIN(76, "DB_76"),
+	PINCTRL_PIN(77, "DB_77"),
+	PINCTRL_PIN(78, "DB_78"),
+	PINCTRL_PIN(79, "DB_79"),
+	PINCTRL_PIN(80, "DB_80"),
+	PINCTRL_PIN(81, "DB_81"),
+	PINCTRL_PIN(82, "DB_82"),
+	PINCTRL_PIN(83, "DB_83"),
+	PINCTRL_PIN(84, "DB_84"),
+	PINCTRL_PIN(85, "DB_85"),
+	PINCTRL_PIN(86, "DB_86"),
+	PINCTRL_PIN(87, "DB_87"),
+	PINCTRL_PIN(88, "DB_88"),
+	PINCTRL_PIN(89, "DB_89"),
+	PINCTRL_PIN(90, "DB_90"),
+	PINCTRL_PIN(91, "DB_91"),
+	PINCTRL_PIN(92, "DB_92"),
+	PINCTRL_PIN(93, "DB_93"),
+	PINCTRL_PIN(94, "DB_94"),
+	PINCTRL_PIN(95, "DB_95"),
+	PINCTRL_PIN(96, "DB_96"),
+	PINCTRL_PIN(97, "DB_97"),
+	PINCTRL_PIN(98, "DB_98"),
+	PINCTRL_PIN(99, "DB_99"),
+	PINCTRL_PIN(100, "DB_100"),
+	PINCTRL_PIN(101, "DB_101"),
+	PINCTRL_PIN(102, "DB_102"),
+	PINCTRL_PIN(103, "DB_103"),
+	PINCTRL_PIN(104, "DB_104"),
+	PINCTRL_PIN(105, "DB_105"),
+	PINCTRL_PIN(106, "DB_106"),
+	PINCTRL_PIN(107, "DB_107"),
+	PINCTRL_PIN(108, "DB_108"),
+	PINCTRL_PIN(109, "DB_109"),
+	PINCTRL_PIN(110, "DB_110"),
+	PINCTRL_PIN(111, "DB_111"),
+	PINCTRL_PIN(112, "DB_112"),
+	PINCTRL_PIN(113, "DB_113"),
+	PINCTRL_PIN(114, "DB_114"),
+	PINCTRL_PIN(115, "DB_115"),
+	PINCTRL_PIN(116, "DB_116"),
+	PINCTRL_PIN(117, "DB_117"),
+	PINCTRL_PIN(118, "DB_118"),
+	PINCTRL_PIN(119, "DB_119"),
+	PINCTRL_PIN(120, "DB_120"),
+};
+
+struct e3xx_pinctrl {
+	struct clk *clk;
+	struct device *dev;
+	struct pinctrl_dev *pctl;
+
+	void __iomem *io_base;
+
+	const struct e3xx_pinctrl_group *groups;
+};
+
+static inline void e3xx_pinctrl_write(struct e3xx_pinctrl *pctl, u32 offset,
+				      u32 val)
+{
+	writel_relaxed(val, pctl->io_base + offset);
+}
+
+static inline u32 e3xx_pinctrl_read(struct e3xx_pinctrl *pctl, u32 offset)
+{
+	return readl_relaxed(pctl->io_base + offset);
+}
+
+static int e3xx_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
+{
+	return 0;
+}
+
+static const char *e3xx_pinctrl_get_group_name(struct pinctrl_dev *pctldev,
+					       unsigned selector)
+{
+	return NULL;
+}
+
+static int e3xx_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
+				       unsigned selector,
+				       const unsigned **pins,
+				       unsigned *num_pins)
+{
+	return -ENOTSUPP;
+}
+
+static const struct pinctrl_ops e3xx_pctrl_ops = {
+	.get_groups_count = e3xx_pinctrl_get_groups_count,
+	.get_group_name = e3xx_pinctrl_get_group_name,
+	.get_group_pins = e3xx_pinctrl_get_group_pins,
+	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+	.dt_free_map = pinctrl_utils_dt_free_map,
+};
+
+static int e3xx_pinconf_cfg_get(struct pinctrl_dev *pctldev,
+				unsigned pin,
+				unsigned long *config)
+{
+	u32 reg, mask;
+	int arg;
+	struct e3xx_pinctrl *pctrl;
+	unsigned int param;
+
+	param = pinconf_to_config_param(*config);
+	pctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	if (pin >= E3XX_NUM_DB_PINS)
+		return -ENOTSUPP;
+
+	mask = BIT(pin % E3XX_PINS_PER_REG);
+
+	switch (param) {
+	case PIN_CONFIG_OUTPUT:
+		clk_enable(pctrl->clk);
+		reg = e3xx_pinctrl_read(pctrl, E3XX_DDR_OFFSET +
+					(pin / E3XX_PINS_PER_REG) * 4);
+
+		clk_disable(pctrl->clk);
+		arg = !!(reg & mask);
+		break;
+	default:
+		dev_err(pctrl->dev, "requested illegal configuration");
+		return -ENOTSUPP;
+	};
+
+	*config = pinconf_to_config_packed(param, arg);
+
+	return 0;
+}
+
+static int e3xx_pinconf_cfg_set(struct pinctrl_dev *pctldev,
+				unsigned pin,
+				unsigned long *configs,
+				unsigned num_configs)
+{
+	u32 reg, mask;
+	int i;
+	struct e3xx_pinctrl *pctrl;
+	unsigned int param, arg;
+
+	if (pin >= E3XX_NUM_DB_PINS)
+		return -ENOTSUPP;
+	mask = BIT(pin % E3XX_PINS_PER_REG);
+
+	pctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	clk_enable(pctrl->clk);
+
+	for (i = 0; i < num_configs; i++) {
+		param = pinconf_to_config_param(configs[i]);
+		arg = pinconf_to_config_argument(configs[i]);
+
+		switch (param) {
+		case PIN_CONFIG_OUTPUT:
+			/* deal with value, set out bit if arg is 1 */
+			reg = e3xx_pinctrl_read(pctrl, E3XX_OUT_OFFSET +
+						((pin / E3XX_PINS_PER_REG) * 4))
+				;
+			reg &= ~mask;
+			if (arg)
+				reg |= mask;
+
+			/* addresses need to be 4 byte aligned */
+			e3xx_pinctrl_write(pctrl, E3XX_OUT_OFFSET +
+					   ((pin / E3XX_PINS_PER_REG) * 4), reg)
+				;
+
+			/* set ddr bit to high for output */
+			reg = e3xx_pinctrl_read(pctrl, E3XX_DDR_OFFSET +
+						((pin / E3XX_PINS_PER_REG) * 4))
+				;
+			reg |= mask;
+
+			/* addresses need to be 4 byte aligned */
+			e3xx_pinctrl_write(pctrl, E3XX_DDR_OFFSET +
+					  ((pin / E3XX_PINS_PER_REG) * 4), reg);
+			break;
+
+		default:
+			clk_disable(pctrl->clk);
+			return -ENOTSUPP;
+		};
+	}
+
+	clk_disable(pctrl->clk);
+
+	return 0;
+}
+
+static int e3xx_pinconf_group_set(struct pinctrl_dev *pctldev,
+				  unsigned selector,
+				  unsigned long *configs,
+				  unsigned num_configs)
+{
+	return -EAGAIN;
+}
+
+static const struct pinconf_ops e3xx_pinconf_ops = {
+	.is_generic = true,
+	.pin_config_get = e3xx_pinconf_cfg_get,
+	.pin_config_set = e3xx_pinconf_cfg_set,
+	.pin_config_group_set = e3xx_pinconf_group_set,
+};
+
+static struct pinctrl_desc e3xx_desc = {
+	.name = "e3xx_pinctrl",
+	.pins = e3xx_pins,
+	.npins = ARRAY_SIZE(e3xx_pins),
+	.pctlops = &e3xx_pctrl_ops,
+	.confops = &e3xx_pinconf_ops,
+	.owner = THIS_MODULE,
+};
+
+static int e3xx_pinctrl_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct e3xx_pinctrl *pctrl;
+	int err;
+
+	pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL);
+	if (!pctrl)
+		return -ENOMEM;
+	pctrl->dev = &pdev->dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "missing IO resource\n");
+		return -ENODEV;
+	}
+
+	pctrl->io_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(pctrl->io_base))
+		return PTR_ERR(pctrl->io_base);
+
+	pctrl->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(pctrl->clk)) {
+		dev_err(&pdev->dev, "input clock not found");
+		return PTR_ERR(pctrl->clk);
+	}
+
+	err = clk_prepare(pctrl->clk);
+	if (err) {
+		dev_err(&pdev->dev, "unable to prepare clock");
+		return err;
+	}
+
+	pctrl->pctl = pinctrl_register(&e3xx_desc, &pdev->dev, pctrl);
+	if (!pctrl->pctl)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, pctrl);
+
+	dev_info(&pdev->dev, "NI Ettus Research E3xx pinctrl initialized\n");
+
+	return 0;
+}
+
+static int e3xx_pinctrl_remove(struct platform_device *pdev)
+{
+	struct e3xx_pinctrl *pctrl = platform_get_drvdata(pdev);
+
+	pinctrl_unregister(pctrl->pctl);
+
+	clk_unprepare(pctrl->clk);
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id e3xx_pinctrl_of_match[] = {
+	{ .compatible = "ettus,e3xx-pinctrl-1.0", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, e3xx_pinctrl_of_match);
+#endif
+
+static struct platform_driver e3xx_pinctrl_driver = {
+	.probe = e3xx_pinctrl_probe,
+	.remove = e3xx_pinctrl_remove,
+	.driver = {
+		.name = "e3xx_pinctrl",
+		.of_match_table = of_match_ptr(e3xx_pinctrl_of_match),
+	},
+};
+
+module_platform_driver(e3xx_pinctrl_driver);
+
+MODULE_AUTHOR("Moritz Fischer <moritz.fischer@ettus.com>");
+MODULE_DESCRIPTION("Ettus Research pinctrl driver");
+MODULE_LICENSE("GPL v2");