linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Add pin control driver for Sunplus SP7021 SoC
@ 2021-10-27  8:55 Wells Lu
  2021-10-27  8:55 ` [PATCH 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
                   ` (3 more replies)
  0 siblings, 4 replies; 19+ messages in thread
From: Wells Lu @ 2021-10-27  8:55 UTC (permalink / raw)
  To: linus.walleij, linux-gpio, linux-kernel, robh+dt, devicetree
  Cc: qinjian, dvorkin, Wells Lu

This is a patch series for pinctrl driver for Sunplus SP7021 SoC.

Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates
many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and
etc.) into a single chip. It is designed for industrial control.

Refer to:
https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
https://tibbo.com/store/plus1.html

Wells Lu (3):
  pinctrl: Add driver for Sunplus SP7021
  dt-bindings: pinctrl: Add dt-bindings for Sunplus SP7021
  devicetree: bindings: pinctrl: Add bindings doc for Sunplus SP7021.

 .../bindings/pinctrl/sunplus,sp7021-pinctrl.yaml   | 277 ++++++++++
 MAINTAINERS                                        |  10 +
 drivers/pinctrl/Kconfig                            |   1 +
 drivers/pinctrl/Makefile                           |   1 +
 drivers/pinctrl/sunplus/Kconfig                    |  32 ++
 drivers/pinctrl/sunplus/Makefile                   |  11 +
 drivers/pinctrl/sunplus/gpio_inf_sp7021.c          |  48 ++
 drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c       | 501 +++++++++++++++++
 drivers/pinctrl/sunplus/sppctl.c                   | 359 +++++++++++++
 drivers/pinctrl/sunplus/sppctl.h                   | 181 +++++++
 drivers/pinctrl/sunplus/sppctl_gpio.c              | 136 +++++
 drivers/pinctrl/sunplus/sppctl_gpio.h              |  73 +++
 drivers/pinctrl/sunplus/sppctl_gpio_ops.c          | 288 ++++++++++
 drivers/pinctrl/sunplus/sppctl_gpio_ops.h          |  75 +++
 drivers/pinctrl/sunplus/sppctl_pinctrl.c           | 593 +++++++++++++++++++++
 drivers/pinctrl/sunplus/sppctl_pinctrl.h           |  33 ++
 drivers/pinctrl/sunplus/sppctl_sysfs.c             | 385 +++++++++++++
 drivers/pinctrl/sunplus/sppctl_sysfs.h             |  33 ++
 include/dt-bindings/pinctrl/sppctl-sp7021.h        | 136 +++++
 include/dt-bindings/pinctrl/sppctl.h               |  40 ++
 20 files changed, 3213 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
 create mode 100644 drivers/pinctrl/sunplus/Kconfig
 create mode 100644 drivers/pinctrl/sunplus/Makefile
 create mode 100644 drivers/pinctrl/sunplus/gpio_inf_sp7021.c
 create mode 100644 drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio_ops.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio_ops.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_pinctrl.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_pinctrl.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_sysfs.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_sysfs.h
 create mode 100644 include/dt-bindings/pinctrl/sppctl-sp7021.h
 create mode 100644 include/dt-bindings/pinctrl/sppctl.h

-- 
2.7.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 1/3] pinctrl: Add driver for Sunplus SP7021
  2021-10-27  8:55 [PATCH 0/3] Add pin control driver for Sunplus SP7021 SoC Wells Lu
@ 2021-10-27  8:55 ` Wells Lu
  2021-10-27 22:12   ` Randy Dunlap
  2021-10-27  8:55 ` [PATCH 2/3] dt-bindings: pinctrl: Add dt-bindings " Wells Lu
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 19+ messages in thread
From: Wells Lu @ 2021-10-27  8:55 UTC (permalink / raw)
  To: linus.walleij, linux-gpio, linux-kernel, robh+dt, devicetree
  Cc: qinjian, dvorkin, Wells Lu

Add driver for Sunplus SP7021.

Signed-off-by: Wells Lu <wells.lu@sunplus.com>
---
 MAINTAINERS                                  |   8 +
 drivers/pinctrl/Kconfig                      |   1 +
 drivers/pinctrl/Makefile                     |   1 +
 drivers/pinctrl/sunplus/Kconfig              |  32 ++
 drivers/pinctrl/sunplus/Makefile             |  11 +
 drivers/pinctrl/sunplus/gpio_inf_sp7021.c    |  48 +++
 drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c | 501 ++++++++++++++++++++++
 drivers/pinctrl/sunplus/sppctl.c             | 359 ++++++++++++++++
 drivers/pinctrl/sunplus/sppctl.h             | 181 ++++++++
 drivers/pinctrl/sunplus/sppctl_gpio.c        | 136 ++++++
 drivers/pinctrl/sunplus/sppctl_gpio.h        |  73 ++++
 drivers/pinctrl/sunplus/sppctl_gpio_ops.c    | 288 +++++++++++++
 drivers/pinctrl/sunplus/sppctl_gpio_ops.h    |  75 ++++
 drivers/pinctrl/sunplus/sppctl_pinctrl.c     | 593 +++++++++++++++++++++++++++
 drivers/pinctrl/sunplus/sppctl_pinctrl.h     |  33 ++
 drivers/pinctrl/sunplus/sppctl_sysfs.c       | 385 +++++++++++++++++
 drivers/pinctrl/sunplus/sppctl_sysfs.h       |  33 ++
 17 files changed, 2758 insertions(+)
 create mode 100644 drivers/pinctrl/sunplus/Kconfig
 create mode 100644 drivers/pinctrl/sunplus/Makefile
 create mode 100644 drivers/pinctrl/sunplus/gpio_inf_sp7021.c
 create mode 100644 drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio_ops.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio_ops.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_pinctrl.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_pinctrl.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_sysfs.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_sysfs.h

diff --git a/MAINTAINERS b/MAINTAINERS
index f26920f..43d587c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14866,6 +14866,14 @@ S:	Maintained
 W:	http://www.st.com/spear
 F:	drivers/pinctrl/spear/
 
+PIN CONTROLLER - SUNPLUS
+M:	Dvorkin Dmitry <dvorkin@tibbo.com>
+M:	Wells Lu <wells.lu@sunplus.com>
+L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+S:	Maintained
+W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+F:	drivers/pinctrl/sunplus/
+
 PKTCDVD DRIVER
 M:	linux-block@vger.kernel.org
 S:	Orphan
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 3192110..5fe8e5d 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -452,6 +452,7 @@ source "drivers/pinctrl/mediatek/Kconfig"
 source "drivers/pinctrl/meson/Kconfig"
 source "drivers/pinctrl/cirrus/Kconfig"
 source "drivers/pinctrl/visconti/Kconfig"
+source "drivers/pinctrl/sunplus/Kconfig"
 
 config PINCTRL_XWAY
 	bool
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 200073b..3721877 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -66,6 +66,7 @@ obj-$(CONFIG_PINCTRL_SAMSUNG)	+= samsung/
 obj-$(CONFIG_PINCTRL_SPEAR)	+= spear/
 obj-y				+= sprd/
 obj-$(CONFIG_PINCTRL_STM32)	+= stm32/
+obj-y				+= sunplus/
 obj-$(CONFIG_PINCTRL_SUNXI)	+= sunxi/
 obj-y				+= ti/
 obj-$(CONFIG_PINCTRL_UNIPHIER)	+= uniphier/
diff --git a/drivers/pinctrl/sunplus/Kconfig b/drivers/pinctrl/sunplus/Kconfig
new file mode 100644
index 0000000..93b5ccf
--- /dev/null
+++ b/drivers/pinctrl/sunplus/Kconfig
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Sunplus Pin control driver configuration
+#
+
+config PINCTRL_SPPCTL
+	bool "Sunplus SP7021 pinmux and gpio driver"
+	depends on SOC_SP7021
+	select PINMUX
+	select GENERIC_PINCTRL_GROUPS
+	select CONFIG_GENERIC_PINMUX_FUNCTIONS
+	select PINCONF
+	select GENERIC_PINCONF
+	select OF_GPIO
+	select GPIOLIB
+	select GPIO_SYSFS
+	select GENERIC_IRQ_CHIP
+	select GPIOLIB_IRQCHIP
+	help
+	  Say Y here to support Sunplus SP7021 pinmux controller.
+	  The driveer is selected automatically by platform.
+	  This driver requires the pinctrl framework.
+	  GPIO is provided by the same driver.
+
+config PINCTRL_SPPCTL_DEBUG
+	bool "Sunplus pinmux specific debug"
+	depends on SOC_SP7021 && DEBUG_PINCTRL
+	help
+	  Say Y if you need to debug Sunplus pinmux driver in-depth.
+	  Pin control driver will output more messages if you enable
+	  this item. This function is dependent on DEBUG_PINCTRL. It
+	  should be enabled first.
diff --git a/drivers/pinctrl/sunplus/Makefile b/drivers/pinctrl/sunplus/Makefile
new file mode 100644
index 0000000..a945653
--- /dev/null
+++ b/drivers/pinctrl/sunplus/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Sunplus Pin control drivers.
+#
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_pinctrl.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_sysfs.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_gpio_ops.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_gpio.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += pinctrl_inf_sp7021.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += gpio_inf_sp7021.o
diff --git a/drivers/pinctrl/sunplus/gpio_inf_sp7021.c b/drivers/pinctrl/sunplus/gpio_inf_sp7021.c
new file mode 100644
index 0000000..31f77ce
--- /dev/null
+++ b/drivers/pinctrl/sunplus/gpio_inf_sp7021.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "sppctl_gpio.h"
+
+const char * const sppctlgpio_list_s[] = {
+	D_PIS(0, 0),  D_PIS(0, 1),  D_PIS(0, 2),  D_PIS(0, 3),
+	D_PIS(0, 4),  D_PIS(0, 5),  D_PIS(0, 6),  D_PIS(0, 7),
+	D_PIS(1, 0),  D_PIS(1, 1),  D_PIS(1, 2),  D_PIS(1, 3),
+	D_PIS(1, 4),  D_PIS(1, 5),  D_PIS(1, 6),  D_PIS(1, 7),
+	D_PIS(2, 0),  D_PIS(2, 1),  D_PIS(2, 2),  D_PIS(2, 3),
+	D_PIS(2, 4),  D_PIS(2, 5),  D_PIS(2, 6),  D_PIS(2, 7),
+	D_PIS(3, 0),  D_PIS(3, 1),  D_PIS(3, 2),  D_PIS(3, 3),
+	D_PIS(3, 4),  D_PIS(3, 5),  D_PIS(3, 6),  D_PIS(3, 7),
+	D_PIS(4, 0),  D_PIS(4, 1),  D_PIS(4, 2),  D_PIS(4, 3),
+	D_PIS(4, 4),  D_PIS(4, 5),  D_PIS(4, 6),  D_PIS(4, 7),
+	D_PIS(5, 0),  D_PIS(5, 1),  D_PIS(5, 2),  D_PIS(5, 3),
+	D_PIS(5, 4),  D_PIS(5, 5),  D_PIS(5, 6),  D_PIS(5, 7),
+	D_PIS(6, 0),  D_PIS(6, 1),  D_PIS(6, 2),  D_PIS(6, 3),
+	D_PIS(6, 4),  D_PIS(6, 5),  D_PIS(6, 6),  D_PIS(6, 7),
+	D_PIS(7, 0),  D_PIS(7, 1),  D_PIS(7, 2),  D_PIS(7, 3),
+	D_PIS(7, 4),  D_PIS(7, 5),  D_PIS(7, 6),  D_PIS(7, 7),
+	D_PIS(8, 0),  D_PIS(8, 1),  D_PIS(8, 2),  D_PIS(8, 3),
+	D_PIS(8, 4),  D_PIS(8, 5),  D_PIS(8, 6),  D_PIS(8, 7),
+	D_PIS(9, 0),  D_PIS(9, 1),  D_PIS(9, 2),  D_PIS(9, 3),
+	D_PIS(9, 4),  D_PIS(9, 5),  D_PIS(9, 6),  D_PIS(9, 7),
+	D_PIS(10, 0), D_PIS(10, 1), D_PIS(10, 2), D_PIS(10, 3),
+	D_PIS(10, 4), D_PIS(10, 5), D_PIS(10, 6), D_PIS(10, 7),
+	D_PIS(11, 0), D_PIS(11, 1), D_PIS(11, 2), D_PIS(11, 3),
+	D_PIS(11, 4), D_PIS(11, 5), D_PIS(11, 6), D_PIS(11, 7),
+	D_PIS(12, 0), D_PIS(12, 1), D_PIS(12, 2)
+};
+
+const size_t GPIS_listSZ = sizeof(sppctlgpio_list_s)/sizeof(*(sppctlgpio_list_s));
diff --git a/drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c b/drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c
new file mode 100644
index 0000000..1435fba
--- /dev/null
+++ b/drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c
@@ -0,0 +1,501 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "sppctl.h"
+
+// function: GPIO. list of groups (pins)
+const unsigned int sppctlpins_G[] = {
+	D(0, 0), D(0, 1), D(0, 2), D(0, 3), D(0, 4), D(0, 5), D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6), D(1, 7),
+	D(2, 0), D(2, 1), D(2, 2), D(2, 3), D(2, 4), D(2, 5), D(2, 6), D(2, 7),
+	D(3, 0), D(3, 1), D(3, 2), D(3, 3), D(3, 4), D(3, 5), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5), D(4, 6), D(4, 7),
+	D(5, 0), D(5, 1), D(5, 2), D(5, 3), D(5, 4), D(5, 5), D(5, 6), D(5, 7),
+	D(6, 0), D(6, 1), D(6, 2), D(6, 3), D(6, 4), D(6, 5), D(6, 6), D(6, 7),
+	D(7, 0), D(7, 1), D(7, 2), D(7, 3), D(7, 4), D(7, 5), D(7, 6), D(7, 7),
+	D(8, 0), D(8, 1), D(8, 2), D(8, 3), D(8, 4), D(8, 5), D(8, 6), D(8, 7),
+	D(9, 0), D(9, 1), D(9, 2), D(9, 3), D(9, 4), D(9, 5), D(9, 6), D(9, 7),
+	D(10, 0), D(10, 1), D(10, 2), D(10, 3), D(10, 4), D(10, 5), D(10, 6), D(10, 7),
+	D(11, 0), D(11, 1), D(11, 2), D(11, 3), D(11, 4), D(11, 5), D(11, 6), D(11, 7),
+	D(12, 0), D(12, 1), D(12, 2)
+};
+
+#define P(x, y) PINCTRL_PIN(D(x, y), D_PIS(x, y))
+
+const struct pinctrl_pin_desc sppctlpins_all[] = {
+	// gpio and iop only
+	P(0, 0), P(0, 1), P(0, 2), P(0, 3), P(0, 4), P(0, 5), P(0, 6), P(0, 7),
+	// gpio, iop, muxable
+	P(1, 0), P(1, 1), P(1, 2), P(1, 3), P(1, 4), P(1, 5), P(1, 6), P(1, 7),
+	P(2, 0), P(2, 1), P(2, 2), P(2, 3), P(2, 4), P(2, 5), P(2, 6), P(2, 7),
+	P(3, 0), P(3, 1), P(3, 2), P(3, 3), P(3, 4), P(3, 5), P(3, 6), P(3, 7),
+	P(4, 0), P(4, 1), P(4, 2), P(4, 3), P(4, 4), P(4, 5), P(4, 6), P(4, 7),
+	P(5, 0), P(5, 1), P(5, 2), P(5, 3), P(5, 4), P(5, 5), P(5, 6), P(5, 7),
+	P(6, 0), P(6, 1), P(6, 2), P(6, 3), P(6, 4), P(6, 5), P(6, 6), P(6, 7),
+	P(7, 0), P(7, 1), P(7, 2), P(7, 3), P(7, 4), P(7, 5), P(7, 6), P(7, 7),
+	P(8, 0), P(8, 1), P(8, 2), P(8, 3), P(8, 4), P(8, 5), P(8, 6), P(8, 7),
+	// gpio (not wired) and iop only
+	P(9, 0),  P(9, 1),  P(9, 2),  P(9, 3),  P(9, 4),  P(9, 5),  P(9, 6),  P(9, 7),
+	P(10, 0), P(10, 1), P(10, 2), P(10, 3), P(10, 4), P(10, 5), P(10, 6), P(10, 7),
+	P(11, 0), P(11, 1), P(11, 2), P(11, 3), P(11, 4), P(11, 5), P(11, 6), P(11, 7),
+	P(12, 0), P(12, 1), P(12, 2)
+};
+const size_t sppctlpins_allSZ = ARRAY_SIZE(sppctlpins_all);
+
+// pmux groups: some pins are muxable. group = pin
+const char * const sppctlpmux_list_s[] = {
+	D_PIS(0, 0),
+	D_PIS(1, 0), D_PIS(1, 1), D_PIS(1, 2), D_PIS(1, 3),
+	D_PIS(1, 4), D_PIS(1, 5), D_PIS(1, 6), D_PIS(1, 7),
+	D_PIS(2, 0), D_PIS(2, 1), D_PIS(2, 2), D_PIS(2, 3),
+	D_PIS(2, 4), D_PIS(2, 5), D_PIS(2, 6), D_PIS(2, 7),
+	D_PIS(3, 0), D_PIS(3, 1), D_PIS(3, 2), D_PIS(3, 3),
+	D_PIS(3, 4), D_PIS(3, 5), D_PIS(3, 6), D_PIS(3, 7),
+	D_PIS(4, 0), D_PIS(4, 1), D_PIS(4, 2), D_PIS(4, 3),
+	D_PIS(4, 4), D_PIS(4, 5), D_PIS(4, 6), D_PIS(4, 7),
+	D_PIS(5, 0), D_PIS(5, 1), D_PIS(5, 2), D_PIS(5, 3),
+	D_PIS(5, 4), D_PIS(5, 5), D_PIS(5, 6), D_PIS(5, 7),
+	D_PIS(6, 0), D_PIS(6, 1), D_PIS(6, 2), D_PIS(6, 3),
+	D_PIS(6, 4), D_PIS(6, 5), D_PIS(6, 6), D_PIS(6, 7),
+	D_PIS(7, 0), D_PIS(7, 1), D_PIS(7, 2), D_PIS(7, 3),
+	D_PIS(7, 4), D_PIS(7, 5), D_PIS(7, 6), D_PIS(7, 7),
+	D_PIS(8, 0), D_PIS(8, 1), D_PIS(8, 2), D_PIS(8, 3),
+	D_PIS(8, 4), D_PIS(8, 5), D_PIS(8, 6), D_PIS(8, 7)
+};
+// gpio: is defined in gpio_inf_sp7021.c
+const size_t PMUX_listSZ = sizeof(sppctlpmux_list_s)/sizeof(*(sppctlpmux_list_s));
+
+static const unsigned int pins_spif1[] = { D(10, 3), D(10, 4), D(10, 6), D(10, 7) };
+static const unsigned int pins_spif2[] = { D(9, 4), D(9, 6), D(9, 7), D(10, 1) };
+static const struct sppctlgrp_t sp7021grps_spif[] = {
+	EGRP("SPI_FLASH1", 1, pins_spif1),
+	EGRP("SPI_FLASH2", 2, pins_spif2)
+};
+
+static const unsigned int pins_spi41[] = { D(10, 2), D(10, 5) };
+static const unsigned int pins_spi42[] = { D(9, 5), D(9, 8) };
+static const struct sppctlgrp_t sp7021grps_spi4[] = {
+	EGRP("SPI_FLASH_4BIT1", 1, pins_spi41),
+	EGRP("SPI_FLASH_4BIT2", 2, pins_spi42)
+};
+
+static const unsigned int pins_snan[] = {
+	D(9, 4), D(9, 5), D(9, 6), D(9, 7), D(10, 0), D(10, 1)
+};
+static const struct sppctlgrp_t sp7021grps_snan[] = {
+	EGRP("SPI_NAND", 1, pins_snan)
+};
+
+static const unsigned int pins_emmc[] = {
+	D(9, 0), D(9, 1), D(9, 2), D(9, 3), D(9, 4), D(9, 5),
+	D(9, 6), D(9, 7), D(10, 0), D(10, 1) };
+static const struct sppctlgrp_t sp7021grps_emmc[] = {
+	EGRP("CARD0_EMMC", 1, pins_emmc)
+};
+
+static const unsigned int pins_sdsd[] = {
+	D(8, 1), D(8, 2), D(8, 3), D(8, 4), D(8, 5), D(8, 6)
+};
+static const struct sppctlgrp_t sp7021grps_sdsd[] = {
+	EGRP("SD_CARD", 1, pins_sdsd)
+};
+
+static const unsigned int pins_uar0[] = { D(11, 0), D(11, 1) };
+static const struct sppctlgrp_t sp7021grps_uar0[] = {
+	EGRP("UA0", 1, pins_uar0)
+};
+
+static const unsigned int pins_adbg1[] = { D(10, 2), D(10, 3) };
+static const unsigned int pins_adbg2[] = { D(7, 1), D(7, 2) };
+static const struct sppctlgrp_t sp7021grps_adbg[] = {
+	EGRP("ACHIP_DEBUG1", 1, pins_adbg1),
+	EGRP("ACHIP_DEBUG2", 2, pins_adbg2)
+};
+
+static const unsigned int pins_aua2axi1[] = { D(2, 0), D(2, 1), D(2, 2) };
+static const unsigned int pins_aua2axi2[] = { D(1, 0), D(1, 1), D(1, 2) };
+static const struct sppctlgrp_t sp7021grps_au2x[] = {
+	EGRP("ACHIP_UA2AXI1", 1, pins_aua2axi1),
+	EGRP("ACHIP_UA2AXI2", 2, pins_aua2axi2)
+};
+
+static const unsigned int pins_fpga[] = {
+	D(0, 2), D(0, 3), D(0, 4), D(0, 5), D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5),
+	D(1, 6), D(1, 7), D(2, 0), D(2, 1), D(2, 2), D(2, 3),
+	D(2, 4), D(2, 5), D(2, 6), D(2, 7), D(3, 0), D(3, 1),
+	D(3, 2), D(3, 3), D(3, 4), D(3, 5), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5),
+	D(4, 6), D(4, 7), D(5, 0), D(5, 1), D(5, 2)
+};
+static const struct sppctlgrp_t sp7021grps_fpga[] = {
+	EGRP("FPGA_IFX", 1, pins_fpga)
+};
+
+/* CEC pin is not used. Release it for others. */
+//static const unsigned int pins_hdmi1[] = { D(10, 6), D(10, 7), D(12, 2), D(12, 1) };
+//static const unsigned int pins_hdmi2[] = { D(8, 3), D(8, 4), D(8, 5), D(8, 6) };
+//static const unsigned int pins_hdmi3[] = { D(7, 4), D(7, 5), D(7, 6), D(7, 7) };
+
+static const unsigned int pins_hdmi1[] = { D(10, 6), D(12, 2), D(12, 1) };
+static const unsigned int pins_hdmi2[] = { D(8, 3), D(8, 5), D(8, 6) };
+static const unsigned int pins_hdmi3[] = { D(7, 4), D(7, 6), D(7, 7) };
+static const struct sppctlgrp_t sp7021grps_hdmi[] = {
+	EGRP("HDMI_TX1", 1, pins_hdmi1),
+	EGRP("HDMI_TX2", 2, pins_hdmi2),
+	EGRP("HDMI_TX3", 3, pins_hdmi3)
+};
+
+static const unsigned int pins_eadc[] = {
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6)
+};
+static const struct sppctlgrp_t sp7021grps_eadc[] = {
+	EGRP("AUD_EXT_ADC_IFX0", 1, pins_eadc)
+};
+
+static const unsigned int pins_edac[] = {
+	D(2, 5), D(2, 6), D(2, 7), D(3, 0), D(3, 1), D(3, 2), D(3, 4)
+};
+static const struct sppctlgrp_t sp7021grps_edac[] = {
+	EGRP("AUD_EXT_DAC_IFX0", 1, pins_edac)
+};
+
+static const unsigned int pins_spdi[] = { D(2, 4) };
+static const struct sppctlgrp_t sp7021grps_spdi[] = {
+	EGRP("AUD_IEC_RX0", 1, pins_spdi)
+};
+static const unsigned int pins_spdo[] = { D(3, 6) };
+static const struct sppctlgrp_t sp7021grps_spdo[] = {
+	EGRP("AUD_IEC_TX0", 1, pins_spdo)
+};
+
+static const unsigned int pins_tdmt[] = {
+	D(2, 5), D(2, 6), D(2, 7), D(3, 0), D(3, 1), D(3, 2)
+};
+static const struct sppctlgrp_t sp7021grps_tdmt[] = {
+	EGRP("TDMTX_IFX0", 1, pins_tdmt)
+};
+
+static const unsigned int pins_tdmr[] = { D(1, 7), D(2, 0), D(2, 1), D(2, 2) };
+static const struct sppctlgrp_t sp7021grps_tdmr[] = {
+	EGRP("TDMRX_IFX0", 1, pins_tdmr)
+};
+
+static const unsigned int pins_pdmr[] = {
+	D(1, 7), D(2, 0), D(2, 1), D(2, 2), D(2, 3)
+};
+static const struct sppctlgrp_t sp7021grps_pdmr[] = {
+	EGRP("PDMRX_IFX0", 1, pins_pdmr)
+};
+
+static const unsigned int pins_pcmt[] = {
+	D(3, 7), D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4)
+};
+static const struct sppctlgrp_t sp7021grps_pcmt[] = {
+	EGRP("PCM_IEC_TX", 1, pins_pcmt)
+};
+
+static const unsigned int pins_lcdi[] = {
+	D(1, 4), D(1, 5),
+	D(1, 6), D(1, 7), D(2, 0), D(2, 1), D(2, 2), D(2, 3),
+	D(2, 4), D(2, 5), D(2, 6), D(2, 7), D(3, 0), D(3, 1),
+	D(3, 2), D(3, 3), D(3, 4), D(3, 5), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5),
+	D(4, 6), D(4, 7)
+};
+static const struct sppctlgrp_t sp7021grps_lcdi[] = {
+	EGRP("LCDIF", 1, pins_lcdi)
+};
+
+static const unsigned int pins_dvdd[] = {
+	D(7, 0), D(7, 1), D(7, 2), D(7, 3), D(7, 4), D(7, 5), D(7, 6), D(7, 7),
+	D(8, 0), D(8, 1), D(8, 2), D(8, 3), D(8, 4), D(8, 5)
+};
+static const struct sppctlgrp_t sp7021grps_dvdd[] = {
+	EGRP("DVD_DSP_DEBUG", 1, pins_dvdd)
+};
+
+static const unsigned int pins_i2cd[] = { D(1, 0), D(1, 1) };
+static const struct sppctlgrp_t sp7021grps_i2cd[] = {
+	EGRP("I2C_DEBUG", 1, pins_i2cd)
+};
+
+static const unsigned int pins_i2cs[] = { D(0, 0), D(0, 1) };
+static const struct sppctlgrp_t sp7021grps_i2cs[] = {
+	EGRP("I2C_SLAVE", 1, pins_i2cs)
+};
+
+static const unsigned int pins_wakp[] = { D(10, 5) };
+static const struct sppctlgrp_t sp7021grps_wakp[] = {
+	EGRP("WAKEUP", 1, pins_wakp)
+};
+
+static const unsigned int pins_u2ax[] = { D(2, 0), D(2, 1), D(3, 0), D(3, 1) };
+static const struct sppctlgrp_t sp7021grps_u2ax[] = {
+	EGRP("UART2AXI", 1, pins_u2ax)
+};
+
+static const unsigned int pins_u0ic[] = {
+	D(0, 0), D(0, 1), D(0, 4), D(0, 5), D(1, 0), D(1, 1)
+};
+static const struct sppctlgrp_t sp7021grps_u0ic[] = {
+	EGRP("USB0_I2C", 1, pins_u0ic)
+};
+
+static const unsigned int pins_u1ic[] = {
+	D(0, 2), D(0, 3), D(0, 6), D(0, 7), D(1, 2), D(1, 3)
+};
+static const struct sppctlgrp_t sp7021grps_u1ic[] = {
+	EGRP("USB1_I2C", 1, pins_u1ic)
+};
+
+static const unsigned int pins_u0ot[] = { D(11, 2) };
+static const struct sppctlgrp_t sp7021grps_u0ot[] = {
+	EGRP("USB0_OTG", 1, pins_u0ot)
+};
+
+static const unsigned int pins_u1ot[] = { D(11, 3) };
+static const struct sppctlgrp_t sp7021grps_u1ot[] = {
+	EGRP("USB1_OTG", 1, pins_u1ot)
+};
+
+static const unsigned int pins_uphd[] = {
+	D(0, 1), D(0, 2), D(0, 3), D(7, 4), D(7, 5), D(7, 6),
+	D(7, 7), D(8, 0), D(8, 1), D(8, 2), D(8, 3),
+	D(9, 7), D(10, 2), D(10, 3), D(10, 4)
+};
+static const struct sppctlgrp_t sp7021grps_up0d[] = {
+	EGRP("UPHY0_DEBUG", 1, pins_uphd)
+};
+static const struct sppctlgrp_t sp7021grps_up1d[] = {
+	EGRP("UPHY1_DEBUG", 1, pins_uphd)
+};
+
+static const unsigned int pins_upex[] = {
+	D(0, 0), D(0, 1), D(0, 2), D(0, 3), D(0, 4), D(0, 5), D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6), D(1, 7),
+	D(2, 0), D(2, 1), D(2, 2), D(2, 3), D(2, 4), D(2, 5), D(2, 6), D(2, 7),
+	D(3, 0), D(3, 1), D(3, 2), D(3, 3), D(3, 4), D(3, 5), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5), D(4, 6), D(4, 7),
+	D(5, 0), D(5, 1), D(5, 2), D(5, 3), D(5, 4), D(5, 5), D(5, 6), D(5, 7),
+	D(6, 0), D(6, 1), D(6, 2), D(6, 3), D(6, 4), D(6, 5), D(6, 6), D(6, 7),
+	D(7, 0), D(7, 1), D(7, 2), D(7, 3), D(7, 4), D(7, 5), D(7, 6), D(7, 7),
+	D(8, 0), D(8, 1), D(8, 2), D(8, 3), D(8, 4), D(8, 5), D(8, 6), D(8, 7),
+	D(9, 0), D(9, 1), D(9, 2), D(9, 3), D(9, 4), D(9, 5), D(9, 6), D(9, 7),
+	D(10, 0), D(10, 1), D(10, 2), D(10, 3), D(10, 4), D(10, 5), D(10, 6), D(10, 7)
+};
+static const struct sppctlgrp_t sp7021grps_upex[] = {
+	EGRP("UPHY0_EXT", 1, pins_upex)
+};
+
+static const unsigned int pins_prp1[] = {
+	D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6), D(1, 7),
+	D(2, 1), D(2, 2), D(2, 3), D(2, 4), D(2, 5), D(2, 6), D(2, 7),
+	D(3, 0), D(3, 1), D(3, 2)
+};
+static const unsigned int pins_prp2[] = {
+	D(3, 4), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5), D(4, 6), D(4, 7),
+	D(5, 0), D(5, 1), D(5, 2), D(5, 3), D(5, 4), D(5, 5), D(5, 6), D(5, 7),
+	D(6, 4)
+};
+static const struct sppctlgrp_t sp7021grps_prbp[] = {
+	EGRP("PROBE_PORT1", 1, pins_prp1),
+	EGRP("PROBE_PORT2", 2, pins_prp2)
+};
+
+static const unsigned int pins_anai[] = { D(0, 4), D(0, 5) };
+static const struct sppctlgrp_t sp7021grps_anai[] = {
+	EGRP("ANA_I2C_IF", 1, pins_anai),
+};
+
+static const unsigned int pins_anat[] = {
+	D(0, 0), D(0, 1), D(0, 2), D(0, 3), D(0, 4), D(0, 5), D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6),
+	D(11, 0)
+};
+static const struct sppctlgrp_t sp7021grps_anat[] = {
+	EGRP("ANA_TEST_IF", 1, pins_anat)
+};
+
+struct func_t list_funcs[] = {
+	FNCN("GPIO",            fOFF_0, 0x00, 0, 0),
+	FNCN("IOP",             fOFF_0, 0x00, 0, 0),
+
+	FNCN("L2SW_CLK_OUT",        fOFF_M, 0x00, 0, 7),
+	FNCN("L2SW_MAC_SMI_MDC",    fOFF_M, 0x00, 8, 7),
+	FNCN("L2SW_LED_FLASH0",     fOFF_M, 0x01, 0, 7),
+	FNCN("L2SW_LED_FLASH1",     fOFF_M, 0x01, 8, 7),
+	FNCN("L2SW_LED_ON0",        fOFF_M, 0x02, 0, 7),
+	FNCN("L2SW_LED_ON1",        fOFF_M, 0x02, 8, 7),
+	FNCN("L2SW_MAC_SMI_MDIO",   fOFF_M, 0x03, 0, 7),
+	FNCN("L2SW_P0_MAC_RMII_TXEN",   fOFF_M, 0x03, 8, 7),
+	FNCN("L2SW_P0_MAC_RMII_TXD0",   fOFF_M, 0x04, 0, 7),
+	FNCN("L2SW_P0_MAC_RMII_TXD1",   fOFF_M, 0x04, 8, 7),
+	FNCN("L2SW_P0_MAC_RMII_CRSDV",  fOFF_M, 0x05, 0, 7),
+	FNCN("L2SW_P0_MAC_RMII_RXD0",   fOFF_M, 0x05, 8, 7),
+	FNCN("L2SW_P0_MAC_RMII_RXD1",   fOFF_M, 0x06, 0, 7),
+	FNCN("L2SW_P0_MAC_RMII_RXER",   fOFF_M, 0x06, 8, 7),
+	FNCN("L2SW_P1_MAC_RMII_TXEN",   fOFF_M, 0x07, 0, 7),
+	FNCN("L2SW_P1_MAC_RMII_TXD0",   fOFF_M, 0x07, 8, 7),
+	FNCN("L2SW_P1_MAC_RMII_TXD1",   fOFF_M, 0x08, 0, 7),
+	FNCN("L2SW_P1_MAC_RMII_CRSDV",  fOFF_M, 0x08, 8, 7),
+	FNCN("L2SW_P1_MAC_RMII_RXD0",   fOFF_M, 0x09, 0, 7),
+	FNCN("L2SW_P1_MAC_RMII_RXD1",   fOFF_M, 0x09, 8, 7),
+	FNCN("L2SW_P1_MAC_RMII_RXER",   fOFF_M, 0x0A, 0, 7),
+	FNCN("DAISY_MODE",      fOFF_M, 0x0A, 8, 7),    // mux has no effect now
+	FNCN("SDIO_CLK",        fOFF_M, 0x0B, 0, 7),
+	FNCN("SDIO_CMD",        fOFF_M, 0x0B, 8, 7),
+	FNCN("SDIO_D0",         fOFF_M, 0x0C, 0, 7),
+	FNCN("SDIO_D1",         fOFF_M, 0x0C, 8, 7),
+	FNCN("SDIO_D2",         fOFF_M, 0x0D, 0, 7),
+	FNCN("SDIO_D3",         fOFF_M, 0x0D, 8, 7),
+	FNCN("PWM0",            fOFF_M, 0x0E, 0, 7),
+	FNCN("PWM1",            fOFF_M, 0x0E, 8, 7),
+	FNCN("PWM2",            fOFF_M, 0x0F, 0, 7),
+	FNCN("PWM3",            fOFF_M, 0x0F, 8, 7),
+
+	FNCN("PWM4",            fOFF_M, 0x10, 0, 7),
+	FNCN("PWM5",            fOFF_M, 0x10, 8, 7),
+	FNCN("PWM6",            fOFF_M, 0x11, 0, 7),
+	FNCN("PWM7",            fOFF_M, 0x11, 8, 7),
+	FNCN("ICM0_D",          fOFF_M, 0x12, 0, 7),    // 4x Input captures
+	FNCN("ICM1_D",          fOFF_M, 0x12, 8, 7),
+	FNCN("ICM2_D",          fOFF_M, 0x13, 0, 7),
+	FNCN("ICM3_D",          fOFF_M, 0x13, 8, 7),
+	FNCN("ICM0_CLK",        fOFF_M, 0x14, 0, 7),
+	FNCN("ICM1_CLK",        fOFF_M, 0x14, 8, 7),
+	FNCN("ICM2_CLK",        fOFF_M, 0x15, 0, 7),
+	FNCN("ICM3_CLK",        fOFF_M, 0x15, 8, 7),
+	FNCN("SPIM0_INT",       fOFF_M, 0x16, 0, 7),    // 4x SPI masters
+	FNCN("SPIM0_CLK",       fOFF_M, 0x16, 8, 7),
+	FNCN("SPIM0_EN",        fOFF_M, 0x17, 0, 7),
+	FNCN("SPIM0_DO",        fOFF_M, 0x17, 8, 7),
+	FNCN("SPIM0_DI",        fOFF_M, 0x18, 0, 7),
+	FNCN("SPIM1_INT",       fOFF_M, 0x18, 8, 7),
+	FNCN("SPIM1_CLK",       fOFF_M, 0x19, 0, 7),
+	FNCN("SPIM1_EN",        fOFF_M, 0x19, 8, 7),
+	FNCN("SPIM1_DO",        fOFF_M, 0x1A, 0, 7),
+	FNCN("SPIM1_DI",        fOFF_M, 0x1A, 8, 7),
+	FNCN("SPIM2_INT",       fOFF_M, 0x1B, 0, 7),
+	FNCN("SPIM2_CLK",       fOFF_M, 0x1B, 8, 7),
+	FNCN("SPIM2_EN",        fOFF_M, 0x1C, 0, 7),
+	FNCN("SPIM2_DO",        fOFF_M, 0x1C, 8, 7),
+	FNCN("SPIM2_DI",        fOFF_M, 0x1D, 0, 7),
+	FNCN("SPIM3_INT",       fOFF_M, 0x1D, 8, 7),
+	FNCN("SPIM3_CLK",       fOFF_M, 0x1E, 0, 7),
+	FNCN("SPIM3_EN",        fOFF_M, 0x1E, 8, 7),
+	FNCN("SPIM3_DO",        fOFF_M, 0x1F, 0, 7),
+	FNCN("SPIM3_DI",        fOFF_M, 0x1F, 8, 7),
+
+	FNCN("SPI0S_INT",       fOFF_M, 0x20, 0, 7),    // 4x SPI slaves
+	FNCN("SPI0S_CLK",       fOFF_M, 0x20, 8, 7),
+	FNCN("SPI0S_EN",        fOFF_M, 0x21, 0, 7),
+	FNCN("SPI0S_DO",        fOFF_M, 0x21, 8, 7),
+	FNCN("SPI0S_DI",        fOFF_M, 0x22, 0, 7),
+	FNCN("SPI1S_INT",       fOFF_M, 0x22, 8, 7),
+	FNCN("SPI1S_CLK",       fOFF_M, 0x23, 0, 7),
+	FNCN("SPI1S_EN",        fOFF_M, 0x23, 8, 7),
+	FNCN("SPI1S_DO",        fOFF_M, 0x24, 0, 7),
+	FNCN("SPI1S_DI",        fOFF_M, 0x24, 8, 7),
+	FNCN("SPI2S_INT",       fOFF_M, 0x25, 0, 7),
+	FNCN("SPI2S_CLK",       fOFF_M, 0x25, 8, 7),
+	FNCN("SPI2S_EN",        fOFF_M, 0x26, 0, 7),
+	FNCN("SPI2S_DO",        fOFF_M, 0x26, 8, 7),
+	FNCN("SPI2S_DI",        fOFF_M, 0x27, 0, 7),
+	FNCN("SPI3S_INT",       fOFF_M, 0x27, 8, 7),
+	FNCN("SPI3S_CLK",       fOFF_M, 0x28, 0, 7),
+	FNCN("SPI3S_EN",        fOFF_M, 0x28, 8, 7),
+	FNCN("SPI3S_DO",        fOFF_M, 0x29, 0, 7),
+	FNCN("SPI3S_DI",        fOFF_M, 0x29, 8, 7),
+	FNCN("I2CM0_CLK",       fOFF_M, 0x2A, 0, 7),    // 4x I2C masters
+	FNCN("I2CM0_DAT",       fOFF_M, 0x2A, 8, 7),
+	FNCN("I2CM1_CLK",       fOFF_M, 0x2B, 0, 7),
+	FNCN("I2CM1_DAT",       fOFF_M, 0x2B, 8, 7),
+	FNCN("I2CM2_CLK",       fOFF_M, 0x2C, 0, 7),
+	FNCN("I2CM2_DAT",       fOFF_M, 0x2C, 8, 7),
+	FNCN("I2CM3_CLK",       fOFF_M, 0x2D, 0, 7),
+	FNCN("I2CM3_DAT",       fOFF_M, 0x2D, 8, 7),
+	FNCN("UA1_TX",          fOFF_M, 0x2E, 0, 7),    // +4x muxable UARTS
+	FNCN("UA1_RX",          fOFF_M, 0x2E, 8, 7),
+	FNCN("UA1_CTS",         fOFF_M, 0x2F, 0, 7),
+	FNCN("UA1_RTS",         fOFF_M, 0x2F, 8, 7),
+
+	FNCN("UA2_TX",          fOFF_M, 0x30, 0, 7),
+	FNCN("UA2_RX",          fOFF_M, 0x30, 8, 7),
+	FNCN("UA2_CTS",         fOFF_M, 0x31, 0, 7),
+	FNCN("UA2_RTS",         fOFF_M, 0x31, 8, 7),
+	FNCN("UA3_TX",          fOFF_M, 0x32, 0, 7),
+	FNCN("UA3_RX",          fOFF_M, 0x32, 8, 7),
+	FNCN("UA3_CTS",         fOFF_M, 0x33, 0, 7),
+	FNCN("UA3_RTS",         fOFF_M, 0x33, 8, 7),
+	FNCN("UA4_TX",          fOFF_M, 0x34, 0, 7),
+	FNCN("UA4_RX",          fOFF_M, 0x34, 8, 7),
+	FNCN("UA4_CTS",         fOFF_M, 0x35, 0, 7),
+	FNCN("UA4_RTS",         fOFF_M, 0x35, 8, 7),
+	FNCN("TIMER0_INT",      fOFF_M, 0x36, 0, 7),    // 4x timers interrupts
+	FNCN("TIMER1_INT",      fOFF_M, 0x36, 8, 7),
+	FNCN("TIMER2_INT",      fOFF_M, 0x37, 0, 7),
+	FNCN("TIMER3_INT",      fOFF_M, 0x37, 8, 7),
+	FNCN("GPIO_INT0",       fOFF_M, 0x38, 0, 7),    // 8x GPIO interrupts
+	FNCN("GPIO_INT1",       fOFF_M, 0x38, 8, 7),
+	FNCN("GPIO_INT2",       fOFF_M, 0x39, 0, 7),
+	FNCN("GPIO_INT3",       fOFF_M, 0x39, 8, 7),
+	FNCN("GPIO_INT4",       fOFF_M, 0x3A, 0, 7),
+	FNCN("GPIO_INT5",       fOFF_M, 0x3A, 8, 7),
+	FNCN("GPIO_INT6",       fOFF_M, 0x3B, 0, 7),
+	FNCN("GPIO_INT7",       fOFF_M, 0x3B, 8, 7),
+	// offset from 0x9C000080
+	FNCE("SPI_FLASH",       fOFF_G, 0x01,  0, 2, sp7021grps_spif),
+	FNCE("SPI_FLASH_4BIT",  fOFF_G, 0x01,  2, 2, sp7021grps_spi4),
+	FNCE("SPI_NAND",        fOFF_G, 0x01,  4, 1, sp7021grps_snan),
+	FNCE("CARD0_EMMC",      fOFF_G, 0x01,  5, 1, sp7021grps_emmc),
+	FNCE("SD_CARD",         fOFF_G, 0x01,  6, 1, sp7021grps_sdsd),
+	FNCE("UA0",             fOFF_G, 0x01,  7, 1, sp7021grps_uar0),
+	FNCE("ACHIP_DEBUG",     fOFF_G, 0x01,  8, 2, sp7021grps_adbg),
+	FNCE("ACHIP_UA2AXI",    fOFF_G, 0x01, 10, 2, sp7021grps_au2x),
+	FNCE("FPGA_IFX",        fOFF_G, 0x01, 12, 1, sp7021grps_fpga),
+	FNCE("HDMI_TX",         fOFF_G, 0x01, 13, 2, sp7021grps_hdmi),
+
+	FNCE("AUD_EXT_ADC_IFX0", fOFF_G, 0x01, 15, 1, sp7021grps_eadc), // I2S audio in
+	FNCE("AUD_EXT_DAC_IFX0", fOFF_G, 0x02,  0, 1, sp7021grps_edac), // I2S audio out
+	FNCE("SPDIF_RX",        fOFF_G, 0x02,  2, 1, sp7021grps_spdi),
+	FNCE("SPDIF_TX",        fOFF_G, 0x02,  3, 1, sp7021grps_spdo),
+	FNCE("TDMTX_IFX0",      fOFF_G, 0x02,  4, 1, sp7021grps_tdmt),
+	FNCE("TDMRX_IFX0",      fOFF_G, 0x02,  5, 1, sp7021grps_tdmr),
+	FNCE("PDMRX_IFX0",      fOFF_G, 0x02,  6, 1, sp7021grps_pdmr),
+	FNCE("PCM_IEC_TX",      fOFF_G, 0x02,  7, 1, sp7021grps_pcmt),
+	FNCE("LCDIF",           fOFF_G, 0x04,  6, 1, sp7021grps_lcdi),
+	FNCE("DVD_DSP_DEBUG",   fOFF_G, 0x02,  8, 1, sp7021grps_dvdd),
+	FNCE("I2C_DEBUG",       fOFF_G, 0x02,  9, 1, sp7021grps_i2cd),
+	FNCE("I2C_SLAVE",       fOFF_G, 0x02, 10, 1, sp7021grps_i2cs), // I2C slave
+	FNCE("WAKEUP",          fOFF_G, 0x02, 11, 1, sp7021grps_wakp),
+	FNCE("UART2AXI",        fOFF_G, 0x02, 12, 2, sp7021grps_u2ax),
+	FNCE("USB0_I2C",        fOFF_G, 0x02, 14, 2, sp7021grps_u0ic),
+	FNCE("USB1_I2C",        fOFF_G, 0x03,  0, 2, sp7021grps_u1ic),
+	FNCE("USB0_OTG",        fOFF_G, 0x03,  2, 1, sp7021grps_u0ot),
+	FNCE("USB1_OTG",        fOFF_G, 0x03,  3, 1, sp7021grps_u1ot),
+	FNCE("UPHY0_DEBUG",     fOFF_G, 0x03,  4, 1, sp7021grps_up0d),
+	FNCE("UPHY1_DEBUG",     fOFF_G, 0x03,  5, 1, sp7021grps_up1d),
+	FNCE("UPHY0_EXT",       fOFF_G, 0x03,  6, 1, sp7021grps_upex),
+	FNCE("PROBE_PORT",      fOFF_G, 0x03,  7, 2, sp7021grps_prbp),
+	FNCE("ANA_I2C_IF",      fOFF_G, 0x03,  7, 2, sp7021grps_anai),
+	FNCE("ANA_TEST_IF",     fOFF_G, 0x03,  7, 2, sp7021grps_anat)
+};
+
+const size_t list_funcsSZ = ARRAY_SIZE(list_funcs);
diff --git a/drivers/pinctrl/sunplus/sppctl.c b/drivers/pinctrl/sunplus/sppctl.c
new file mode 100644
index 0000000..ca135d0
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/io.h>
+
+#include "sppctl.h"
+#include "../core.h"
+
+
+void print_device_tree_node(struct device_node *node, int depth)
+{
+	int i = 0;
+	struct device_node *child;
+	struct property    *properties;
+	char                indent[255] = "";
+
+	for (i = 0; i < depth * 3; i++)
+		indent[i] = ' ';
+	indent[i] = '\0';
+
+	++depth;
+	if (depth == 1) {
+		pr_info("%s{ name = %s\n", indent, node->name);
+		for (properties = node->properties; properties != NULL;
+			properties = properties->next)
+			pr_info("%s  %s (%d)\n", indent, properties->name, properties->length);
+		pr_info("%s}\n", indent);
+	}
+
+	for_each_child_of_node(node, child) {
+		pr_info("%s{ name = %s\n", indent, child->name);
+		for (properties = child->properties; properties != NULL;
+			properties = properties->next)
+			pr_info("%s  %s (%d)\n", indent, properties->name, properties->length);
+		print_device_tree_node(child, depth);
+		pr_info("%s}\n", indent);
+	}
+}
+
+void sppctl_gmx_set(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff, uint8_t _bsiz,
+		    uint8_t _rval)
+{
+	uint32_t *r;
+	struct sppctl_reg_t x = { .m = (~(~0 << _bsiz)) << _boff,
+				  .v = ((uint16_t)_rval) << _boff };
+
+	if (_p->debug > 1)
+		KDBG(_p->pcdp->dev, "%s(x%X,x%X,x%X,x%X) m:x%X v:x%X\n",
+		     __func__, _roff, _boff, _bsiz, _rval, x.m, x.v);
+	r = (uint32_t *)&x;
+	writel(*r, _p->baseI + (_roff << 2));
+}
+
+uint8_t sppctl_gmx_get(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff, uint8_t _bsiz)
+{
+	uint8_t rval;
+	struct sppctl_reg_t *x;
+	uint32_t r = readl(_p->baseI + (_roff << 2));
+
+	x = (struct sppctl_reg_t *)&r;
+	rval = (x->v >> _boff) & (~(~0 << _bsiz));
+
+	if (_p->debug > 1)
+		KDBG(_p->pcdp->dev, "%s(x%X,x%X,x%X) v:x%X rval:x%X\n",
+		     __func__, _roff, _boff, _bsiz, x->v, rval);
+
+	return rval;
+}
+
+void sppctl_pin_set(struct sppctl_pdata_t *_p, uint8_t _pin, uint8_t _fun)
+{
+	uint32_t *r;
+	struct sppctl_reg_t x = { .m = 0x007F, .v = (uint16_t)_pin };
+	uint8_t func = (_fun >> 1) << 2;
+
+	if (_fun % 2 == 0)
+		;
+	else {
+		x.v <<= 8;
+		x.m <<= 8;
+	}
+
+	if (_p->debug > 1)
+		KDBG(_p->pcdp->dev, "%s(x%X,x%X) off:x%X m:x%X v:x%X\n",
+		     __func__, _pin, _fun, func, x.m, x.v);
+
+	r = (uint32_t *)&x;
+	writel(*r, _p->baseF + func);
+}
+
+uint8_t sppctl_fun_get(struct sppctl_pdata_t *_p,  uint8_t _fun)
+{
+	uint8_t pin = 0x00;
+	uint8_t func = (_fun >> 1) << 2;
+	struct sppctl_reg_t *x;
+	uint32_t r = readl(_p->baseF + func);
+
+	x = (struct sppctl_reg_t *)&r;
+	if (_fun % 2 == 0)
+		pin = x->v & 0x00FF;
+	else
+		pin = x->v >> 8;
+
+	if (_p->debug > 1)
+		KDBG(_p->pcdp->dev, "%s(x%X) off:x%X m:x%X v:x%X pin:x%X\n",
+		     __func__, _fun, func, x->m, x->v, pin);
+
+	return pin;
+}
+
+static void sppctl_fwload_cb(const struct firmware *_fw, void *_ctx)
+{
+	int i = -1, j = 0;
+	struct sppctl_pdata_t *p = (struct sppctl_pdata_t *)_ctx;
+
+	if (!_fw) {
+		KERR(p->pcdp->dev, "Firmware not found\n");
+		return;
+	}
+	if (_fw->size < list_funcsSZ-2) {
+		KERR(p->pcdp->dev, " fw size %zd < %zd\n", _fw->size, list_funcsSZ);
+		goto out;
+	}
+
+	for (i = 0; i < list_funcsSZ && i < _fw->size; i++) {
+		if (list_funcs[i].freg != fOFF_M)
+			continue;
+		sppctl_pin_set(p, _fw->data[i], i);
+		j++;
+	}
+
+out:
+	release_firmware(_fw);
+}
+
+void sppctl_loadfw(struct device *_dev, const char *_fwname)
+{
+	int ret;
+	struct sppctl_pdata_t *p = (struct sppctl_pdata_t *)_dev->platform_data;
+
+	if (!_fwname)
+		return;
+	if (strlen(_fwname) < 1)
+		return;
+	KINF(_dev, "fw:%s", _fwname);
+
+	ret = request_firmware_nowait(THIS_MODULE, true, _fwname, _dev, GFP_KERNEL, p,
+				      sppctl_fwload_cb);
+	if (ret)
+		KERR(_dev, "Can't load '%s'\n", _fwname);
+}
+
+int sppctl_pctl_resmap(struct platform_device *_pd, struct sppctl_pdata_t *_pc)
+{
+	struct resource *rp;
+
+	// resF
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 0);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#F ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #F:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->baseF = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->baseF)) {
+		KERR(&(_pd->dev), "%s map res#F ERR\n", __func__);
+		return PTR_ERR(_pc->baseF);
+	}
+
+	// res0
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 1);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#0 ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #0:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->base0 = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->base0)) {
+		KERR(&(_pd->dev), "%s map res#0 ERR\n", __func__);
+		return PTR_ERR(_pc->base0);
+	}
+
+	// res1
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 2);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#1 ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #1:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->base1 = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->base1)) {
+		KERR(&(_pd->dev), "%s map res#1 ERR\n", __func__);
+		return PTR_ERR(_pc->base1);
+	}
+
+	// res2
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 3);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#2 ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #2:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->base2 = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->base2)) {
+		KERR(&(_pd->dev), "%s map res#2 ERR\n", __func__);
+		return PTR_ERR(_pc->base2);
+	}
+
+	// iop
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 4);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#I ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #I:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->baseI = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->baseI)) {
+		KERR(&(_pd->dev), "%s map res#I ERR\n", __func__);
+		return PTR_ERR(_pc->baseI);
+	}
+
+	return 0;
+}
+
+static int sppctl_dnew(struct platform_device *_pd)
+{
+	int ret = -ENODEV;
+	struct device_node *np = _pd->dev.of_node;
+	struct sppctl_pdata_t *p = NULL;
+	const char *fwfname = FW_DEFNAME;
+
+	if (!np) {
+		KERR(&(_pd->dev), "Invalid dtb node\n");
+		return -EINVAL;
+	}
+	if (!of_device_is_available(np)) {
+		KERR(&(_pd->dev), "dtb is not available\n");
+		return -ENODEV;
+	}
+
+	// print_device_tree_node(np, 0);
+
+	p = devm_kzalloc(&(_pd->dev), sizeof(*p), GFP_KERNEL);
+	if (!p)
+		return -ENOMEM;
+	memset(p->name, 0, SPPCTL_MAX_NAM);
+	if (np)
+		strcpy(p->name, np->name);
+	else
+		strcpy(p->name, MNAME);
+	dev_set_name(&(_pd->dev), "%s", p->name);
+
+	ret = sppctl_pctl_resmap(_pd, p);
+	if (ret != 0)
+		return ret;
+
+	// set gpio_chip
+	_pd->dev.platform_data = p;
+	sppctl_sysfs_init(_pd);
+	of_property_read_string(np, "fwname", &fwfname);
+	if (fwfname)
+		strcpy(p->fwname, fwfname);
+	sppctl_loadfw(&(_pd->dev), p->fwname);
+
+	ret = sppctl_gpio_new(_pd, p);
+	if (ret != 0)
+		return ret;
+
+	ret = sppctl_pinctrl_init(_pd);
+	if (ret != 0)
+		return ret;
+
+	pinctrl_add_gpio_range(p->pcdp, &(p->gpio_range));
+	pr_info(M_NAM " by " M_ORG "" M_CPR);
+
+	return 0;
+}
+
+static int sppctl_ddel(struct platform_device *_pd)
+{
+	struct sppctl_pdata_t *p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+
+	sppctl_gpio_del(_pd, p);
+	sppctl_sysfs_clean(_pd);
+	sppctl_pinctrl_clea(_pd);
+	return 0;
+}
+
+static const struct of_device_id sppctl_dt_ids[] = {
+	{ .compatible = "sunplus,sp7021-pctl" },
+	{ /* zero */ }
+};
+
+MODULE_DEVICE_TABLE(of, sppctl_dt_ids);
+MODULE_ALIAS("platform:" MNAME);
+
+static struct platform_driver sppctl_driver = {
+	.driver = {
+		.name           = MNAME,
+		.owner          = THIS_MODULE,
+		.of_match_table = of_match_ptr(sppctl_dt_ids),
+	},
+	.probe  = sppctl_dnew,
+	.remove = sppctl_ddel,
+};
+
+static int __init sppctl_drv_reg(void)
+{
+	return platform_driver_register(&sppctl_driver);
+}
+postcore_initcall(sppctl_drv_reg);
+
+static void __exit sppctl_drv_exit(void)
+{
+	platform_driver_unregister(&sppctl_driver);
+}
+module_exit(sppctl_drv_exit);
+
+MODULE_AUTHOR(M_AUT1);
+MODULE_AUTHOR(M_AUT2);
+MODULE_DESCRIPTION(M_NAM);
+MODULE_LICENSE(M_LIC);
diff --git a/drivers/pinctrl/sunplus/sppctl.h b/drivers/pinctrl/sunplus/sppctl.h
new file mode 100644
index 0000000..c64a619
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl.h
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#ifndef SPPCTL_H
+#define SPPCTL_H
+
+#define MNAME "sppctl"
+#define M_LIC "GPL v2"
+#define M_AUT1 "Dvorkin Dmitry <dvorkin@tibbo.com>"
+#define M_AUT2 "Wells Lu <wells.lu@sunplus.com>"
+#define M_NAM "SP7021 PinCtl"
+#define M_ORG "Sunplus/Tibbo Tech."
+#define M_CPR "(C) 2020"
+
+#define FW_DEFNAME NULL
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/sysfs.h>
+#include <linux/printk.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <dt-bindings/pinctrl/sppctl-sp7021.h>
+
+#define SPPCTL_MAX_NAM 64
+#define SPPCTL_MAX_BUF PAGE_SIZE
+
+#define KINF(pd, fmt, args...) \
+	do { \
+		if ((pd) != NULL) \
+			dev_info((pd), fmt, ##args); \
+		else \
+			pr_info(MNAME ": " fmt, ##args); \
+	} while (0)
+#define KERR(pd, fmt, args...) \
+	do { \
+		if ((pd) != NULL) \
+			dev_info((pd), fmt, ##args); \
+		else \
+			pr_err(MNAME ": " fmt, ##args); \
+	} while (0)
+#ifdef CONFIG_PINCTRL_SPPCTL_DEBUG
+#define KDBG(pd, fmt, args...) \
+	do { \
+		if ((pd) != NULL) \
+			dev_info((pd), fmt, ##args); \
+		else \
+			pr_debug(MNAME ": " fmt, ##args); \
+	} while (0)
+#else
+#define KDBG(pd, fmt, args...)
+#endif
+
+#include "sppctl_gpio.h"
+
+struct sppctl_pdata_t {
+	char name[SPPCTL_MAX_NAM];
+	uint8_t debug;
+	char fwname[SPPCTL_MAX_NAM];
+	void *sysfs_sdp;
+	void __iomem *baseF;    // functions
+	void __iomem *base0;    // MASTER , OE , OUT , IN
+	void __iomem *base1;    // I_INV , O_INV , OD
+	void __iomem *base2;    // GPIO_FIRST
+	void __iomem *baseI;    // IOP
+	// pinctrl-related
+	struct pinctrl_desc pdesc;
+	struct pinctrl_dev *pcdp;
+	struct pinctrl_gpio_range gpio_range;
+	struct sppctlgpio_chip_t *gpiod;
+};
+
+struct sppctl_reg_t {
+	uint16_t v;     // value part
+	uint16_t m;     // mask part
+};
+
+#include "sppctl_sysfs.h"
+#include "sppctl_pinctrl.h"
+
+void sppctl_gmx_set(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff,
+		    uint8_t _bsiz, uint8_t _rval);
+uint8_t sppctl_gmx_get(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff,
+		       uint8_t _bsiz);
+void sppctl_pin_set(struct sppctl_pdata_t *_p, uint8_t _pin, uint8_t _fun);
+uint8_t sppctl_fun_get(struct sppctl_pdata_t *_p, uint8_t _pin);
+void sppctl_loadfw(struct device *_dev, const char *_fwname);
+
+enum fOFF_t {
+	fOFF_0, // nowhere
+	fOFF_M, // in mux registers
+	fOFF_G, // mux group registers
+	fOFF_I, // in iop registers
+};
+
+struct sppctlgrp_t {
+	const char * const name;
+	const uint8_t gval;             // value for register
+	const unsigned * const pins;    // list of pins
+	const unsigned int pnum;        // number of pins
+};
+
+#define EGRP(n, v, p) { \
+	.name = n, \
+	.gval = (v), \
+	.pins = (p), \
+	.pnum = ARRAY_SIZE(p), \
+}
+
+struct func_t {
+	const char * const name;
+	const enum fOFF_t freg;     // function register type
+	const uint8_t roff;         // register offset
+	const uint8_t boff;         // bit offset
+	const uint8_t blen;         // number of bits
+	const struct sppctlgrp_t * const grps; // list of groups
+	const unsigned int gnum;    // number of groups
+	const char *grps_sa[5];     // array of pointers to func's grps names
+};
+
+#define FNCE(n, r, o, bo, bl, g) { \
+	.name = n, \
+	.freg = r, \
+	.roff = o, \
+	.boff = bo, \
+	.blen = bl, \
+	.grps = (g), \
+	.gnum = ARRAY_SIZE(g), \
+}
+
+#define FNCN(n, r, o, bo, bl) { \
+	.name = n, \
+	.freg = r, \
+	.roff = o, \
+	.boff = bo, \
+	.blen = bl, \
+	.grps = NULL, \
+	.gnum = 0, \
+}
+extern struct func_t list_funcs[];
+extern const size_t list_funcsSZ;
+
+extern const char * const sppctlpmux_list_s[];
+extern const size_t PMUX_listSZ;
+
+struct grp2fp_map_t {
+	uint16_t f_idx; // function index
+	uint16_t g_idx; // pins/group index inside function
+};
+
+// for debug
+void print_device_tree_node(struct device_node *node, int depth);
+
+#endif // SPPCTL_H
diff --git a/drivers/pinctrl/sunplus/sppctl_gpio.c b/drivers/pinctrl/sunplus/sppctl_gpio.c
new file mode 100644
index 0000000..31d11d6
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_gpio.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/seq_file.h>
+#include <linux/io.h>
+
+#include "sppctl_gpio_ops.h"
+#include "sppctl_gpio.h"
+
+__attribute((unused))
+static irqreturn_t gpio_int_0(int irq, void *data)
+{
+	pr_info("register gpio int0 trigger\n");
+	return IRQ_HANDLED;
+}
+
+int sppctl_gpio_new(struct platform_device *_pd, void *_datap)
+{
+	struct device_node *np = _pd->dev.of_node, *npi;
+	struct sppctlgpio_chip_t *pc = NULL;
+	struct gpio_chip *gchip = NULL;
+	int err = 0, i = 0, npins;
+	struct sppctl_pdata_t *_pctrlp = (struct sppctl_pdata_t *)_datap;
+
+	if (!np) {
+		KERR(&(_pd->dev), "invalid devicetree node\n");
+		return -EINVAL;
+	}
+
+	if (!of_device_is_available(np)) {
+		KERR(&(_pd->dev), "devicetree status is not available\n");
+		return -ENODEV;
+	}
+
+	// print_device_tree_node(np, 0);
+	for_each_child_of_node(np, npi) {
+		if (of_find_property(npi, "gpio-controller", NULL)) {
+			i = 1;
+			break;
+		}
+	}
+
+	if (of_find_property(np, "gpio-controller", NULL))
+		i = 1;
+	if (i == 0) {
+		KERR(&(_pd->dev), "is not gpio-controller\n");
+		return -ENODEV;
+	}
+
+	pc = devm_kzalloc(&(_pd->dev), sizeof(*pc), GFP_KERNEL);
+	if (!pc)
+		return -ENOMEM;
+	gchip = &(pc->chip);
+
+	pc->base0 = _pctrlp->base0;
+	pc->base1 = _pctrlp->base1;
+	pc->base2 = _pctrlp->base2;
+	_pctrlp->gpiod = pc;
+
+	gchip->label =             MNAME;
+	gchip->parent =            &(_pd->dev);
+	gchip->owner =             THIS_MODULE;
+	gchip->request =           gpiochip_generic_request; // place new calls there
+	gchip->free =              gpiochip_generic_free;
+	gchip->get_direction =     sppctlgpio_f_gdi;
+	gchip->direction_input =   sppctlgpio_f_sin;
+	gchip->direction_output =  sppctlgpio_f_sou;
+	gchip->get =               sppctlgpio_f_get;
+	gchip->set =               sppctlgpio_f_set;
+	gchip->set_config =        sppctlgpio_f_scf;
+	gchip->dbg_show =          sppctlgpio_f_dsh;
+	gchip->base =              0; // it is main platform GPIO controller
+	gchip->ngpio =             GPIS_listSZ;
+	gchip->names =             sppctlgpio_list_s;
+	gchip->can_sleep =         0;
+#if defined(CONFIG_OF_GPIO)
+	gchip->of_node =           np;
+#ifdef CONFIG_PINCTRL_SPPCTL
+	gchip->of_gpio_n_cells =   2;
+#endif
+#endif
+	gchip->to_irq =            sppctlgpio_i_map;
+
+	_pctrlp->gpio_range.npins = gchip->ngpio;
+	_pctrlp->gpio_range.base =  gchip->base;
+	_pctrlp->gpio_range.name =  gchip->label;
+	_pctrlp->gpio_range.gc =    gchip;
+
+	// FIXME: can't set pc globally
+	err = devm_gpiochip_add_data(&(_pd->dev), gchip, pc);
+	if (err < 0) {
+		KERR(&(_pd->dev), "gpiochip add failed\n");
+		return err;
+	}
+
+	npins = platform_irq_count(_pd);
+	for (i = 0; i < npins && i < SPPCTL_GPIO_IRQS; i++) {
+		pc->irq[i] = irq_of_parse_and_map(np, i);
+		KDBG(&(_pd->dev), "setting up irq#%d -> %d\n", i, pc->irq[i]);
+	}
+
+	spin_lock_init(&(pc->lock));
+
+	return 0;
+}
+
+int sppctl_gpio_del(struct platform_device *_pd, void *_datap)
+{
+	//struct sppctlgpio_chip_t *cp;
+
+	// FIXME: can't use globally now
+	//cp = platform_get_drvdata(_pd);
+	//if (cp == NULL)
+	//	return -ENODEV;
+	//gpiochip_remove(&(cp->chip));
+	// FIX: remove spinlock_t ?
+	return 0;
+}
diff --git a/drivers/pinctrl/sunplus/sppctl_gpio.h b/drivers/pinctrl/sunplus/sppctl_gpio.h
new file mode 100644
index 0000000..4708d17
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_gpio.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SPPCTL_GPIO_H
+#define SPPCTL_GPIO_H
+
+#define SPPCTL_GPIO_IRQS 8
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/stringify.h>
+#include "sppctl.h"
+
+struct sppctlgpio_chip_t {
+	spinlock_t lock;
+	struct gpio_chip chip;
+	void __iomem *base0;   // MASTER , OE , OUT , IN
+	void __iomem *base1;   // I_INV , O_INV , OD
+	void __iomem *base2;   // GPIO_FIRST
+	int irq[SPPCTL_GPIO_IRQS];
+};
+
+extern const char * const sppctlgpio_list_s[];
+extern const size_t GPIS_listSZ;
+
+int sppctl_gpio_new(struct platform_device *_pd, void *_datap);
+int sppctl_gpio_del(struct platform_device *_pd, void *_datap);
+
+#ifdef CONFIG_PINCTRL_SPPCTL
+#define D_PIS(x, y) "P" __stringify(x) "_0" __stringify(y)
+#else
+#define D_PIS(x) "GPIO" __stringify(x)
+#endif
+
+// FIRST: MUX=0, GPIO=1
+enum muxF_MG_t {
+	muxF_M = 0,
+	muxF_G = 1,
+	muxFKEEP = 2,
+};
+// MASTER: IOP=0,GPIO=1
+enum muxM_IG_t {
+	muxM_I = 0,
+	muxM_G = 1,
+	muxMKEEP = 2,
+};
+
+#endif // SPPCTL_GPIO_H
diff --git a/drivers/pinctrl/sunplus/sppctl_gpio_ops.c b/drivers/pinctrl/sunplus/sppctl_gpio_ops.c
new file mode 100644
index 0000000..9f68fb4
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_gpio_ops.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/seq_file.h>
+#include <linux/io.h>
+
+#include "sppctl_gpio.h"
+#include "sppctl_gpio_ops.h"
+
+#define SPPCTL_GPIO_OFF_GFR     0x00
+#define SPPCTL_GPIO_OFF_CTL     0x00
+#define SPPCTL_GPIO_OFF_OE      0x20
+#define SPPCTL_GPIO_OFF_OUT     0x40
+#define SPPCTL_GPIO_OFF_IN      0x60
+#define SPPCTL_GPIO_OFF_IINV    0x00
+#define SPPCTL_GPIO_OFF_OINV    0x20
+#define SPPCTL_GPIO_OFF_OD      0x40
+
+// (/16)*4
+#define R16_ROF(r)              (((r)>>4)<<2)
+#define R16_BOF(r)              ((r)%16)
+// (/32)*4
+#define R32_ROF(r)              (((r)>>5)<<2)
+#define R32_BOF(r)              ((r)%32)
+#define R32_VAL(r, boff)        (((r)>>(boff)) & BIT(0))
+
+// who is first: GPIO(1) | MUX(0)
+int sppctlgpio_u_gfrst(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base2 + SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+	//KINF(_c->parent, "u F r:%X = %d %px off:%d\n", r, R32_VAL(r,R32_BOF(_n)),
+	//	pc->base2, SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+
+	return R32_VAL(r, R32_BOF(_n));
+}
+
+// who is master: GPIO(1) | IOP(0)
+int sppctlgpio_u_magpi(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base0 + SPPCTL_GPIO_OFF_CTL + R16_ROF(_n));
+	//KINF(_c->parent, "u M r:%X = %d %px off:%d\n", r, R32_VAL(r,R16_BOF(_n)),
+	//	pc->base0, SPPCTL_GPIO_OFF_CTL + R16_ROF(_n));
+
+	return R32_VAL(r, R16_BOF(_n));
+}
+
+// set master: GPIO(1)|IOP(0), first:GPIO(1)|MUX(0)
+void sppctlgpio_u_magpi_set(struct gpio_chip *_c, unsigned int _n, enum muxF_MG_t _f,
+			    enum muxM_IG_t _m)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	// FIRST
+	if (_f != muxFKEEP) {
+		r = readl(pc->base2 + SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+		//KINF(_c->parent, "F r:%X %px off:%d\n", r, pc->base2,
+		//	SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+		if (_f != R32_VAL(r, R32_BOF(_n))) {
+			if (_f == muxF_G)
+				r |= BIT(R32_BOF(_n));
+			else
+				r &= ~BIT(R32_BOF(_n));
+			//KINF(_c->parent, "F w:%X\n", r);
+			writel(r, pc->base2 + SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+		}
+	}
+
+	// MASTER
+	if (_m != muxMKEEP) {
+		r = (BIT(R16_BOF(_n))<<16);
+		if (_m == muxM_G)
+			r |= BIT(R16_BOF(_n));
+		//KINF(_c->parent, "M w:%X %px off:%d\n", r, pc->base0,
+		//	SPPCTL_GPIO_OFF_CTL + R16_ROF(_n));
+		writel(r, pc->base0 + SPPCTL_GPIO_OFF_CTL + R16_ROF(_n));
+	}
+}
+
+// is inv: INVERTED(1) | NORMAL(0)
+int sppctlgpio_u_isinv(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+	u16 inv_off = SPPCTL_GPIO_OFF_IINV;
+
+	if (sppctlgpio_f_gdi(_c, _n) == 0)
+		inv_off = SPPCTL_GPIO_OFF_OINV;
+
+	r = readl(pc->base1 + inv_off + R16_ROF(_n));
+
+	return R32_VAL(r, R16_BOF(_n));
+}
+
+void sppctlgpio_u_siinv(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+	u16 inv_off = SPPCTL_GPIO_OFF_IINV;
+
+	r = (BIT(R16_BOF(_n))<<16) | BIT(R16_BOF(_n));
+	writel(r, pc->base1 + inv_off + R16_ROF(_n));
+}
+
+void sppctlgpio_u_soinv(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+	u16 inv_off = SPPCTL_GPIO_OFF_OINV;
+
+	r = (BIT(R16_BOF(_n))<<16) | BIT(R16_BOF(_n));
+	writel(r, pc->base1 + inv_off + R16_ROF(_n));
+}
+
+// is open-drain: YES(1) | NON(0)
+int sppctlgpio_u_isodr(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base1 + SPPCTL_GPIO_OFF_OD + R16_ROF(_n));
+
+	return R32_VAL(r, R16_BOF(_n));
+}
+
+void sppctlgpio_u_seodr(struct gpio_chip *_c, unsigned int _n, unsigned int _v)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = (BIT(R16_BOF(_n))<<16) | ((_v & BIT(0)) << R16_BOF(_n));
+	writel(r, pc->base1 + SPPCTL_GPIO_OFF_OD + R16_ROF(_n));
+}
+
+// get dir: 0=out, 1=in, -E =err (-EINVAL for ex): OE inverted on ret
+int sppctlgpio_f_gdi(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base0 + SPPCTL_GPIO_OFF_OE + R16_ROF(_n));
+
+	return R32_VAL(r, R16_BOF(_n)) ^ BIT(0);
+}
+
+// set to input: 0:ok: OE=0
+int sppctlgpio_f_sin(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = (BIT(R16_BOF(_n))<<16);
+	writel(r, pc->base0 + SPPCTL_GPIO_OFF_OE + R16_ROF(_n));
+
+	return 0;
+}
+
+// set to output: 0:ok: OE=1,O=_v
+int sppctlgpio_f_sou(struct gpio_chip *_c, unsigned int _n, int _v)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = (BIT(R16_BOF(_n))<<16) | BIT(R16_BOF(_n));
+	writel(r, pc->base0 + SPPCTL_GPIO_OFF_OE + R16_ROF(_n));
+	if (_v < 0)
+		return 0;
+	r = (BIT(R16_BOF(_n))<<16) | ((_v & BIT(0)) << R16_BOF(_n));
+	writel(r, pc->base0 + SPPCTL_GPIO_OFF_OUT + R16_ROF(_n));
+
+	return 0;
+}
+
+// get value for signal: 0=low | 1=high | -err
+int sppctlgpio_f_get(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base0 + SPPCTL_GPIO_OFF_IN + R32_ROF(_n));
+
+	return R32_VAL(r, R32_BOF(_n));
+}
+
+// OUT only: can't call set on IN pin: protected by gpio_chip layer
+void sppctlgpio_f_set(struct gpio_chip *_c, unsigned int _n, int _v)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = (BIT(R16_BOF(_n))<<16) | (_v & 0x0001) << R16_BOF(_n);
+	writel(r, pc->base0 + SPPCTL_GPIO_OFF_OUT + R16_ROF(_n));
+}
+
+// FIX: test in-depth
+int sppctlgpio_f_scf(struct gpio_chip *_c, unsigned int _n, unsigned long _conf)
+{
+	u32 r;
+	int ret = 0;
+	enum pin_config_param cp = pinconf_to_config_param(_conf);
+	u16 ca = pinconf_to_config_argument(_conf);
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	KDBG(_c->parent, "f_scf(%03d,%lX) p:%d a:%d\n", _n, _conf, cp, ca);
+	switch (cp) {
+	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		r = (BIT(R16_BOF(_n))<<16) | BIT(R16_BOF(_n));
+		writel(r, pc->base1 + SPPCTL_GPIO_OFF_OD + R16_ROF(_n));
+		break;
+
+	case PIN_CONFIG_INPUT_ENABLE:
+		KERR(_c->parent, "f_scf(%03d,%lX) input enable arg:%d\n", _n, _conf, ca);
+		break;
+
+	case PIN_CONFIG_OUTPUT:
+		ret = sppctlgpio_f_sou(_c, _n, 0);
+		break;
+
+	case PIN_CONFIG_PERSIST_STATE:
+		KDBG(_c->parent, "f_scf(%03d,%lX) not support pinconf:%d\n", _n, _conf, cp);
+		ret = -EOPNOTSUPP;
+		break;
+
+	default:
+		KDBG(_c->parent, "f_scf(%03d,%lX) unknown pinconf:%d\n", _n, _conf, cp);
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_DEBUG_FS
+void sppctlgpio_f_dsh(struct seq_file *_s, struct gpio_chip *_c)
+{
+	int i;
+	const char *label;
+
+	for (i = 0; i < _c->ngpio; i++) {
+		label = gpiochip_is_requested(_c, i);
+		if (!label)
+			label = "";
+
+		seq_printf(_s, " gpio-%03d (%-16.16s | %-16.16s)", i + _c->base,
+			   _c->names[i], label);
+		seq_printf(_s, " %c", sppctlgpio_f_gdi(_c, i) == 0 ? 'O' : 'I');
+		seq_printf(_s, ":%d", sppctlgpio_f_get(_c, i));
+		seq_printf(_s, " %s", (sppctlgpio_u_gfrst(_c, i) ? "gpi" : "mux"));
+		seq_printf(_s, " %s", (sppctlgpio_u_magpi(_c, i) ? "gpi" : "iop"));
+		seq_printf(_s, " %s", (sppctlgpio_u_isinv(_c, i) ? "inv" : "   "));
+		seq_printf(_s, " %s", (sppctlgpio_u_isodr(_c, i) ? "oDr" : ""));
+		seq_puts(_s, "\n");
+	}
+}
+#else
+#define sppctlgpio_f_dsh NULL
+#endif
+
+int sppctlgpio_i_map(struct gpio_chip *_c, unsigned int _off)
+{
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	if (_off >= 8 && _off < 15)
+		return pc->irq[_off - 8];
+
+	return -ENXIO;
+}
diff --git a/drivers/pinctrl/sunplus/sppctl_gpio_ops.h b/drivers/pinctrl/sunplus/sppctl_gpio_ops.h
new file mode 100644
index 0000000..05928d4
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_gpio_ops.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SPPCTL_GPIO_OPS_H
+#define SPPCTL_GPIO_OPS_H
+
+#include "sppctl_gpio.h"
+
+// who is first: GPIO(1) | MUX(0)
+int sppctlgpio_u_gfrst(struct gpio_chip *_c, unsigned int _n);
+
+// who is master: GPIO(1) | IOP(0)
+int sppctlgpio_u_magpi(struct gpio_chip *_c, unsigned int _n);
+
+// set MASTER and FIRST
+void sppctlgpio_u_magpi_set(struct gpio_chip *_c, unsigned int _n,
+			    enum muxF_MG_t _f, enum muxM_IG_t _m);
+
+// is inv: INVERTED(1) | NORMAL(0)
+int sppctlgpio_u_isinv(struct gpio_chip *_c, unsigned int _n);
+// set (I|O)inv
+void sppctlgpio_u_siinv(struct gpio_chip *_c, unsigned int _n);
+void sppctlgpio_u_soinv(struct gpio_chip *_c, unsigned int _n);
+
+// is open-drain: YES(1) | NON(0)
+int sppctlgpio_u_isodr(struct gpio_chip *_c, unsigned int _n);
+void sppctlgpio_u_seodr(struct gpio_chip *_c, unsigned int _n, unsigned int _v);
+
+// get dir: 0=out, 1=in, -E =err (-EINVAL for ex): OE inverted on ret
+int sppctlgpio_f_gdi(struct gpio_chip *_c, unsigned int _n);
+
+// set to input: 0:ok: OE=0
+int sppctlgpio_f_sin(struct gpio_chip *_c, unsigned int _n);
+
+// set to output: 0:ok: OE=1,O=_v
+int sppctlgpio_f_sou(struct gpio_chip *_c, unsigned int _n, int _v);
+
+// get value for signal: 0=low | 1=high | -err
+int sppctlgpio_f_get(struct gpio_chip *_c, unsigned int _n);
+
+// OUT only: can't call set on IN pin: protected by gpio_chip layer
+void sppctlgpio_f_set(struct gpio_chip *_c, unsigned int _n, int _v);
+
+// FIX: test in-depth
+int sppctlgpio_f_scf(struct gpio_chip *_c, unsigned int _n, unsigned long _conf);
+
+#ifdef CONFIG_DEBUG_FS
+void sppctlgpio_f_dsh(struct seq_file *_s, struct gpio_chip *_c);
+#else
+#define sppctlgpio_f_dsh NULL
+#endif
+
+#ifdef CONFIG_OF_GPIO
+int sppctlgpio_xlate(struct gpio_chip *_c, const struct of_phandle_args *_a,
+		     u32 *_flags);
+#endif
+
+int sppctlgpio_i_map(struct gpio_chip *_c, unsigned int _off);
+
+#endif // SPPCTL_GPIO_OPS_H
diff --git a/drivers/pinctrl/sunplus/sppctl_pinctrl.c b/drivers/pinctrl/sunplus/sppctl_pinctrl.c
new file mode 100644
index 0000000..e1bace5
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_pinctrl.c
@@ -0,0 +1,593 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "../core.h"
+#include "../pinctrl-utils.h"
+#include "../devicetree.h"
+#include "sppctl_pinctrl.h"
+#include "sppctl_gpio_ops.h"
+
+#ifdef CONFIG_PINCTRL_SPPCTL
+#define SUPPORT_PINMUX
+#endif
+
+char const **unq_grps;
+size_t unq_grpsSZ;
+struct grp2fp_map_t *g2fp_maps;
+
+int stpctl_c_p_get(struct pinctrl_dev *_pd, unsigned int _pin, unsigned long *_cfg)
+{
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	unsigned int param = pinconf_to_config_param(*_cfg);
+	unsigned int arg = 0;
+
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+	switch (param) {
+	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		if (!sppctlgpio_u_isodr(&(pctrl->gpiod->chip), _pin))
+			return -EINVAL;
+		break;
+
+	case PIN_CONFIG_OUTPUT:
+		if (!sppctlgpio_u_gfrst(&(pctrl->gpiod->chip), _pin))
+			return -EINVAL;
+		if (!sppctlgpio_u_magpi(&(pctrl->gpiod->chip), _pin))
+			return -EINVAL;
+		if (sppctlgpio_f_gdi(&(pctrl->gpiod->chip), _pin) != 0)
+			return -EINVAL;
+		arg = sppctlgpio_f_get(&(pctrl->gpiod->chip), _pin);
+		break;
+
+	default:
+		//KINF(_pd->dev, "%s(%d) skipping:x%X\n", __FUNCTION__, _pin, param);
+		return -EOPNOTSUPP;
+	}
+	*_cfg = pinconf_to_config_packed(param, arg);
+
+	return 0;
+}
+
+int stpctl_c_p_set(struct pinctrl_dev *_pd, unsigned int _pin, unsigned long *_ca,
+		   unsigned int _clen)
+{
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	int i = 0;
+
+	KDBG(_pd->dev, "%s(%d,%ld,%d)\n", __func__, _pin, *_ca, _clen);
+	// special handling for IOP
+	if (_ca[i] == 0xFF) {
+		sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), _pin, muxF_G, muxM_I);
+		return 0;
+	}
+
+	for (i = 0; i < _clen; i++) {
+		if (_ca[i] & SPPCTL_PCTL_L_OUT) {
+			KDBG(_pd->dev, "%d:OUT\n", i);
+			sppctlgpio_f_sou(&(pctrl->gpiod->chip), _pin, 0);
+		}
+		if (_ca[i] & SPPCTL_PCTL_L_OU1) {
+			KDBG(_pd->dev, "%d:OU1\n", i);
+			sppctlgpio_f_sou(&(pctrl->gpiod->chip), _pin, 1);
+		}
+		if (_ca[i] & SPPCTL_PCTL_L_INV) {
+			KDBG(_pd->dev, "%d:INV\n", i);
+			sppctlgpio_u_siinv(&(pctrl->gpiod->chip), _pin);
+		}
+		if (_ca[i] & SPPCTL_PCTL_L_ONV) {
+			KDBG(_pd->dev, "%d:ONV\n", i);
+			sppctlgpio_u_soinv(&(pctrl->gpiod->chip), _pin);
+		}
+		if (_ca[i] & SPPCTL_PCTL_L_ODR) {
+			KDBG(_pd->dev, "%d:ODR\n", i);
+			sppctlgpio_u_seodr(&(pctrl->gpiod->chip), _pin, 1);
+		}
+		// FIXME: add pullup/pulldown, irq enable/disable
+	}
+
+	return 0;
+}
+
+int stpctl_c_g_get(struct pinctrl_dev *_pd, unsigned int _gid, unsigned long *_config)
+{
+	// KINF(_pd->dev, "%s(%d)\n", __FUNCTION__, _gid);
+	// FIXME: add data
+	return 0;
+}
+
+int stpctl_c_g_set(struct pinctrl_dev *_pd, unsigned int _gid, unsigned long *_configs,
+		   unsigned int _num_configs)
+{
+	// KINF(_pd->dev, "%s(%d,,%d)\n", __FUNCTION__, _gid, _num_configs);
+	// FIXME: delete ?
+	return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+void stpctl_c_d_show(struct pinctrl_dev *_pd, struct seq_file *s, unsigned int _off)
+{
+	// KINF(_pd->dev, "%s(%d)\n", __FUNCTION__, _off);
+	seq_printf(s, " %s", dev_name(_pd->dev));
+}
+
+void stpctl_c_d_group_show(struct pinctrl_dev *_pd, struct seq_file *s, unsigned int _gid)
+{
+	// group: freescale/pinctrl-imx.c, 448
+	// KINF(_pd->dev, "%s(%d)\n", __FUNCTION__, _gid);
+}
+
+void stpctl_c_d_config_show(struct pinctrl_dev *_pd, struct seq_file *s, unsigned long _config)
+{
+	// KINF(_pd->dev, "%s(%ld)\n", __FUNCTION__, _config);
+}
+#else
+#define stpctl_c_d_show NULL
+#define stpctl_c_d_group_show NULL
+#define stpctl_c_d_config_show NULL
+#endif
+
+static struct pinconf_ops sppctl_pconf_ops = {
+	.is_generic                 = true,
+	.pin_config_get             = stpctl_c_p_get,
+	.pin_config_set             = stpctl_c_p_set,
+	//.pin_config_group_get       = stpctl_c_g_get,
+	//.pin_config_group_set       = stpctl_c_g_set,
+	.pin_config_dbg_show        = stpctl_c_d_show,
+	.pin_config_group_dbg_show  = stpctl_c_d_group_show,
+	.pin_config_config_dbg_show = stpctl_c_d_config_show,
+};
+
+int stpctl_m_req(struct pinctrl_dev *_pd, unsigned int _pin)
+{
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+	return 0;
+}
+
+int stpctl_m_fre(struct pinctrl_dev *_pd, unsigned int _pin)
+{
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+	return 0;
+}
+
+int stpctl_m_f_cnt(struct pinctrl_dev *_pd)
+{
+	return list_funcsSZ;
+}
+
+const char *stpctl_m_f_nam(struct pinctrl_dev *_pd, unsigned int _fid)
+{
+	return list_funcs[_fid].name;
+}
+
+int stpctl_m_f_grp(struct pinctrl_dev *_pd, unsigned int _fid, const char * const **grps,
+		   unsigned int *_gnum)
+{
+	struct func_t *f = &(list_funcs[_fid]);
+
+	*_gnum = 0;
+	switch (f->freg) {
+	case fOFF_I:
+	case fOFF_0:   // gen GPIO/IOP: all groups = all pins
+		*_gnum = GPIS_listSZ;
+		*grps = sppctlgpio_list_s;
+		break;
+
+	case fOFF_M:   // pin-mux
+		*_gnum = PMUX_listSZ;
+		*grps = sppctlpmux_list_s;
+		break;
+
+	case fOFF_G:   // pin-group
+		if (!f->grps)
+			break;
+		*_gnum = f->gnum;
+		*grps = (const char * const *)f->grps_sa;
+		break;
+
+	default:
+		KERR(_pd->dev, "%s(_fid:%d) unknown fOFF %d\n", __func__, _fid, f->freg);
+		break;
+	}
+
+	KDBG(_pd->dev, "%s(_fid:%d) %d\n", __func__, _fid, *_gnum);
+	return 0;
+}
+
+int stpctl_m_mux(struct pinctrl_dev *_pd, unsigned int _fid, unsigned int _gid)
+{
+	int i = -1, j = -1;
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	struct func_t *f = &(list_funcs[_fid]);
+
+	struct grp2fp_map_t g2fpm = g2fp_maps[_gid];
+
+	KDBG(_pd->dev, "%s(fun:%d,grp:%d)\n", __func__, _fid, _gid);
+	switch (f->freg) {
+	case fOFF_0:   // GPIO. detouch from all funcs - ?
+		for (i = 0; i < list_funcsSZ; i++) {
+			if (list_funcs[i].freg != fOFF_M)
+				continue;
+			j++;
+			if (sppctl_fun_get(pctrl, j) != _gid)
+				continue;
+			sppctl_pin_set(pctrl, 0, j);
+		}
+		break;
+
+	case fOFF_M:   // MUX :
+		sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), _gid, muxF_M, muxMKEEP);
+		sppctl_pin_set(pctrl, (_gid == 0 ? _gid : _gid - 7), _fid - 2);    // pin, fun FIXME
+		break;
+
+	case fOFF_G:   // GROUP
+		for (i = 0; i < f->grps[g2fpm.g_idx].pnum; i++)
+			sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), f->grps[g2fpm.g_idx].pins[i],
+					       muxF_M, muxMKEEP);
+		sppctl_gmx_set(pctrl, f->roff, f->boff, f->blen, f->grps[g2fpm.g_idx].gval);
+		break;
+
+	case fOFF_I:   // IOP
+		sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), _gid, muxF_G, muxM_I);
+		break;
+
+	default:
+		KERR(_pd->dev, "%s(_fid:%d) unknown fOFF %d\n", __func__, _fid, f->freg);
+		break;
+	}
+
+	return 0;
+}
+
+int stpctl_m_gpio_req(struct pinctrl_dev *_pd, struct pinctrl_gpio_range *range, unsigned int _pin)
+{
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	struct pin_desc *pdesc;
+	int g_f, g_m;
+
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+	g_f = sppctlgpio_u_gfrst(&(pctrl->gpiod->chip), _pin);
+	g_m = sppctlgpio_u_magpi(&(pctrl->gpiod->chip), _pin);
+	if (g_f == muxF_G && g_m == muxM_G)
+		return 0;
+
+	pdesc = pin_desc_get(_pd, _pin);
+	// in non-gpio state: is it claimed already?
+	if (pdesc->mux_owner)
+		return -EACCES;
+
+	sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), _pin, muxF_G, muxM_G);
+	return 0;
+}
+
+void stpctl_m_gpio_fre(struct pinctrl_dev *_pd, struct pinctrl_gpio_range *range,
+		       unsigned int _pin)
+{
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+}
+int stpctl_m_gpio_sdir(struct pinctrl_dev *_pd, struct pinctrl_gpio_range *range,
+		       unsigned int _pin, bool _in)
+{
+	KDBG(_pd->dev, "%s(%d,%d)\n", __func__, _pin, _in);
+	return 0;
+}
+
+static const struct pinmux_ops sppctl_pinmux_ops = {
+	.request             = stpctl_m_req,
+	.free                = stpctl_m_fre,
+	.get_functions_count = stpctl_m_f_cnt,
+	.get_function_name   = stpctl_m_f_nam,
+	.get_function_groups = stpctl_m_f_grp,
+	.set_mux             = stpctl_m_mux,
+	.gpio_request_enable = stpctl_m_gpio_req,
+	.gpio_disable_free   = stpctl_m_gpio_fre,
+	.gpio_set_direction  = stpctl_m_gpio_sdir,
+	.strict              = 1
+};
+
+// all groups
+int stpctl_o_g_cnt(struct pinctrl_dev *_pd)
+{
+	return unq_grpsSZ;
+}
+
+const char *stpctl_o_g_nam(struct pinctrl_dev *_pd, unsigned int _gid)
+{
+	return unq_grps[_gid];
+}
+
+int stpctl_o_g_pins(struct pinctrl_dev *_pd, unsigned int _gid, const unsigned int **pins,
+		    unsigned int *num_pins)
+{
+	struct grp2fp_map_t g2fpm = g2fp_maps[_gid];
+	struct func_t *f = &(list_funcs[g2fpm.f_idx]);
+
+	KDBG(_pd->dev, "grp-pins g:%d f_idx:%d,g_idx:%d freg:%d...\n", _gid, g2fpm.f_idx,
+	     g2fpm.g_idx, f->freg);
+	*num_pins = 0;
+
+	// MUX | GPIO | IOP: 1 pin -> 1 group
+	if (f->freg != fOFF_G) {
+		*num_pins = 1;
+		*pins = &sppctlpins_G[_gid];
+		return 0;
+	}
+
+	// IOP (several pins at once in a group)
+	if (!f->grps)
+		return 0;
+	if (f->gnum < 1)
+		return 0;
+	*num_pins = f->grps[g2fpm.g_idx].pnum;
+	*pins = f->grps[g2fpm.g_idx].pins;
+
+	return 0;
+}
+
+// /sys/kernel/debug/pinctrl/sppctl/pins add: gpio_first and ctrl_sel
+#ifdef CONFIG_DEBUG_FS
+void stpctl_o_show(struct pinctrl_dev *_pd, struct seq_file *_s, unsigned int _n)
+{
+	struct sppctl_pdata_t *p = pinctrl_dev_get_drvdata(_pd);
+	const char *tmpp;
+	uint8_t g_f, g_m;
+
+	seq_printf(_s, "%s", dev_name(_pd->dev));
+	g_f = sppctlgpio_u_gfrst(&(p->gpiod->chip), _n);
+	g_m = sppctlgpio_u_magpi(&(p->gpiod->chip), _n);
+
+	tmpp = "?";
+	if (g_f &&  g_m)
+		tmpp = "GPIO";
+	if (g_f && !g_m)
+		tmpp = " IOP";
+	if (!g_f)
+		tmpp = " MUX";
+	seq_printf(_s, " %s", tmpp);
+}
+#else
+#define stpctl_ops_show NULL
+#endif
+
+int stpctl_o_n2map(struct pinctrl_dev *_pd, struct device_node *_dn, struct pinctrl_map **_map,
+		   unsigned int *_nm)
+{
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	struct device_node *parent;
+	u32 dt_pin, dt_fun;
+	u8 p_p, p_g, p_f, p_l;
+	unsigned long *configs;
+	int i, size = 0;
+	const __be32 *list = of_get_property(_dn, "pins", &size);
+	struct property *prop;
+	const char *s_f, *s_g;
+	int nmG = of_property_count_strings(_dn, "groups");
+	struct func_t *f = NULL;
+
+	//print_device_tree_node(_dn, 0);
+	if (nmG <= 0)
+		nmG = 0;
+
+	parent = of_get_parent(_dn);
+	*_nm = size/sizeof(*list);
+
+	// Check if out of range or invalid?
+	for (i = 0; i < (*_nm); i++) {
+		dt_pin = be32_to_cpu(list[i]);
+		p_p = SPPCTL_PCTLD_P(dt_pin);
+		p_g = SPPCTL_PCTLD_G(dt_pin);
+
+		if ((p_p >= sppctlpins_allSZ)
+#ifndef SUPPORT_PINMUX
+			|| (p_g == SPPCTL_PCTL_G_PMUX)
+#endif
+		) {
+			KDBG(_pd->dev, "Invalid pin property at index %d (0x%08x)\n", i, dt_pin);
+			return -EINVAL;
+		}
+	}
+
+	*_map = kcalloc(*_nm + nmG, sizeof(**_map), GFP_KERNEL);
+	for (i = 0; i < (*_nm); i++) {
+		dt_pin = be32_to_cpu(list[i]);
+		p_p = SPPCTL_PCTLD_P(dt_pin);
+		p_g = SPPCTL_PCTLD_G(dt_pin);
+		p_f = SPPCTL_PCTLD_F(dt_pin);
+		p_l = SPPCTL_PCTLD_L(dt_pin);
+		(*_map)[i].name = parent->name;
+		KDBG(_pd->dev, "map [%d]=%08x p=%d g=%d f=%d l=%d\n", i, dt_pin, p_p, p_g,
+		     p_f, p_l);
+
+		if (p_g == SPPCTL_PCTL_G_GPIO) {
+			// look into parse_dt_cfg(),
+			(*_map)[i].type = PIN_MAP_TYPE_CONFIGS_PIN;
+			(*_map)[i].data.configs.num_configs = 1;
+			(*_map)[i].data.configs.group_or_pin = pin_get_name(_pd, p_p);
+			configs = kcalloc(1, sizeof(*configs), GFP_KERNEL);
+			*configs = p_l;
+			(*_map)[i].data.configs.configs = configs;
+
+			KDBG(_pd->dev, "%s(%d) = x%X\n", (*_map)[i].data.configs.group_or_pin,
+			     p_p, p_l);
+		} else if (p_g == SPPCTL_PCTL_G_IOPP) {
+			(*_map)[i].type = PIN_MAP_TYPE_CONFIGS_PIN;
+			(*_map)[i].data.configs.num_configs = 1;
+			(*_map)[i].data.configs.group_or_pin = pin_get_name(_pd, p_p);
+			configs = kcalloc(1, sizeof(*configs), GFP_KERNEL);
+			*configs = 0xFF;
+			(*_map)[i].data.configs.configs = configs;
+
+			KDBG(_pd->dev, "%s(%d) = x%X\n", (*_map)[i].data.configs.group_or_pin,
+			     p_p, p_l);
+		} else {
+			(*_map)[i].type = PIN_MAP_TYPE_MUX_GROUP;
+			(*_map)[i].data.mux.function = list_funcs[p_f].name;
+			(*_map)[i].data.mux.group = pin_get_name(_pd, p_p);
+
+			KDBG(_pd->dev, "f->p: %s(%d)->%s(%d)\n", (*_map)[i].data.mux.function,
+			     p_f, (*_map)[i].data.mux.group, p_p);
+		}
+	}
+
+	// handle pin-group function
+	if (nmG > 0 && of_property_read_string(_dn, "function", &s_f) == 0) {
+		KDBG(_pd->dev, "found func: %s\n", s_f);
+		of_property_for_each_string(_dn, "groups", prop, s_g) {
+			KDBG(_pd->dev, " %s: %s\n", s_f, s_g);
+			(*_map)[*_nm].type = PIN_MAP_TYPE_MUX_GROUP;
+			(*_map)[*_nm].data.mux.function = s_f;
+			(*_map)[*_nm].data.mux.group = s_g;
+			KDBG(_pd->dev, "f->g: %s->%s\n", (*_map)[*_nm].data.mux.function,
+			     (*_map)[*_nm].data.mux.group);
+			(*_nm)++;
+		}
+	}
+
+	// handle zero function
+	list = of_get_property(_dn, "zero_func", &size);
+	if (list) {
+		for (i = 0; i < size/sizeof(*list); i++) {
+			dt_fun = be32_to_cpu(list[i]);
+			if (dt_fun >= list_funcsSZ) {
+				KERR(_pd->dev, "zero func %d out of range\n", dt_fun);
+				continue;
+			}
+
+			f = &(list_funcs[dt_fun]);
+			switch (f->freg) {
+			case fOFF_M:
+				KDBG(_pd->dev, "zero func: %d (%s)\n", dt_fun, f->name);
+				sppctl_pin_set(pctrl, 0, dt_fun - 2);
+				break;
+
+			case fOFF_G:
+				KDBG(_pd->dev, "zero group: %d (%s)\n", dt_fun, f->name);
+				sppctl_gmx_set(pctrl, f->roff, f->boff, f->blen, 0);
+				break;
+
+			default:
+				KERR(_pd->dev, "wrong zero group: %d (%s)\n", dt_fun, f->name);
+				break;
+			}
+		}
+	}
+
+	of_node_put(parent);
+	KDBG(_pd->dev, "%d pins mapped\n", *_nm);
+	return 0;
+}
+
+void stpctl_o_mfre(struct pinctrl_dev *_pd, struct pinctrl_map *_map, unsigned int num_maps)
+{
+	//KINF(_pd->dev, "%s(%d)\n", __FUNCTION__, num_maps);
+	// FIXME: test
+	pinctrl_utils_free_map(_pd, _map, num_maps);
+}
+
+static const struct pinctrl_ops sppctl_pctl_ops = {
+	.get_groups_count = stpctl_o_g_cnt,
+	.get_group_name   = stpctl_o_g_nam,
+	.get_group_pins   = stpctl_o_g_pins,
+#ifdef CONFIG_DEBUG_FS
+	.pin_dbg_show     = stpctl_o_show,
+#endif
+	.dt_node_to_map   = stpctl_o_n2map,
+	.dt_free_map      = stpctl_o_mfre,
+};
+
+// creates unq_grps[] uniq group names array char *
+// sets unq_grpsSZ
+// creates XXX[group_idx]{func_idx, pins_idx}
+void group_groups(struct platform_device *_pd)
+{
+	int i, k, j = 0;
+
+	// fill array of all groups
+	unq_grps = NULL;
+	unq_grpsSZ = GPIS_listSZ;
+
+	// calc unique group names array size
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg != fOFF_G)
+			continue;
+		unq_grpsSZ += list_funcs[i].gnum;
+	}
+
+	// fill up unique group names array
+	unq_grps = devm_kzalloc(&(_pd->dev), (unq_grpsSZ + 1)*sizeof(char *), GFP_KERNEL);
+	g2fp_maps = devm_kzalloc(&(_pd->dev), (unq_grpsSZ + 1)*sizeof(struct grp2fp_map_t),
+				 GFP_KERNEL);
+
+	// groups == pins
+	j = 0;
+	for (i = 0; i < GPIS_listSZ; i++) {
+		unq_grps[i] = sppctlgpio_list_s[i];
+		g2fp_maps[i].f_idx = 0;
+		g2fp_maps[i].g_idx = i;
+	}
+	j = GPIS_listSZ;
+
+	// +IOP groups
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg != fOFF_G)
+			continue;
+
+		for (k = 0; k < list_funcs[i].gnum; k++) {
+			list_funcs[i].grps_sa[k] = (char *)list_funcs[i].grps[k].name;
+			unq_grps[j] = list_funcs[i].grps[k].name;
+			g2fp_maps[j].f_idx = i;
+			g2fp_maps[j].g_idx = k;
+			j++;
+		}
+	}
+	KINF(&(_pd->dev), "funcs: %zd unq_grps: %zd\n", list_funcsSZ, unq_grpsSZ);
+}
+
+// ---------- main (exported) functions
+int sppctl_pinctrl_init(struct platform_device *_pd)
+{
+	int err;
+	struct device *dev = &_pd->dev;
+	struct device_node *np = of_node_get(dev->of_node);
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+
+	// init pdesc
+	_p->pdesc.owner = THIS_MODULE;
+	_p->pdesc.name = dev_name(&(_pd->dev));
+	_p->pdesc.pins = &(sppctlpins_all[0]);
+	_p->pdesc.npins = sppctlpins_allSZ;
+	_p->pdesc.pctlops = &sppctl_pctl_ops;
+	_p->pdesc.confops = &sppctl_pconf_ops;
+	_p->pdesc.pmxops = &sppctl_pinmux_ops;
+
+	group_groups(_pd);
+
+	err = devm_pinctrl_register_and_init(&(_pd->dev), &(_p->pdesc), _p, &(_p->pcdp));
+	if (err) {
+		KERR(&(_pd->dev), "Failed to register\n");
+		of_node_put(np);
+		return err;
+	}
+
+	pinctrl_enable(_p->pcdp);
+	return 0;
+}
+
+void sppctl_pinctrl_clea(struct platform_device *_pd)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+
+	devm_pinctrl_unregister(&(_pd->dev), _p->pcdp);
+}
diff --git a/drivers/pinctrl/sunplus/sppctl_pinctrl.h b/drivers/pinctrl/sunplus/sppctl_pinctrl.h
new file mode 100644
index 0000000..a634c41
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_pinctrl.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#ifndef SPPCTL_PINCTRL_H
+#define SPPCTL_PINCTRL_H
+
+#include "sppctl.h"
+
+
+int sppctl_pinctrl_init(struct platform_device *_pdev);
+void sppctl_pinctrl_clea(struct platform_device *_pdev);
+
+#define D(x, y) ((x)*8+(y))
+
+extern const struct pinctrl_pin_desc sppctlpins_all[];
+extern const size_t sppctlpins_allSZ;
+extern const unsigned int sppctlpins_G[];
+
+#endif // SPPCTL_PINCTRL_H
diff --git a/drivers/pinctrl/sunplus/sppctl_sysfs.c b/drivers/pinctrl/sunplus/sppctl_sysfs.c
new file mode 100644
index 0000000..7fe54bb
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_sysfs.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "sppctl_sysfs.h"
+#include "sppctl_gpio_ops.h"
+#include "sppctl_pinctrl.h"
+
+
+static ssize_t sppctl_sop_name_R(struct device *_d, struct device_attribute *_a, char *_b)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	return sprintf(_b, "%s\n", _p->name);
+}
+
+static ssize_t sppctl_sop_dbgi_R(struct device *_d, struct device_attribute *_a, char *_b)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	return sprintf(_b, "%d\n", _p->debug);
+}
+
+static ssize_t sppctl_sop_dbgi_W(struct device *_d, struct device_attribute *_a, const char *_b,
+				 size_t _c)
+{
+	int x;
+
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	if (kstrtoint(_b, 10, &x) < 0)
+		return -EIO;
+	_p->debug = x;
+
+	return _c;
+}
+
+static ssize_t sppctl_sop_fwname_R(struct device *_d, struct device_attribute *_a, char *_b)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	return sprintf(_b, "%s", _p->fwname);
+}
+
+static ssize_t sppctl_sop_fwname_W(struct device *_d, struct device_attribute *_a, const char *_b,
+				   size_t _c)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	strcpy(_p->fwname, _b);
+	if (_p->fwname[strlen(_p->fwname)-1] == 0x0A)
+		_p->fwname[strlen(_p->fwname)-1] = 0;
+	sppctl_loadfw(_d, _p->fwname);
+
+	return _c;
+}
+
+static ssize_t sppctl_sop_list_muxes_R(struct file *filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t off, size_t count)
+{
+	int i = -1, ret = 0, pos = off;
+	const char *tmpp;
+	struct sppctl_pdata_t *_p = NULL;
+	struct device *_pdev = container_of(_k, struct device, kobj);
+
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg == fOFF_0)
+			continue;
+		if (list_funcs[i].freg == fOFF_I)
+			continue;
+		tmpp = list_funcs[i].name;
+		if (pos > 0) {
+			pos -= (strlen(tmpp) + 1);
+			continue;
+		}
+		sprintf(_b + ret, "%s\n", tmpp);
+		ret += strlen(tmpp) + 1;
+		if (ret > SPPCTL_MAX_BUF - SPPCTL_MAX_NAM)
+			break;
+	}
+
+	return ret;
+}
+
+static ssize_t sppctl_sop_txt_map_R(struct file *filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t off, size_t count)
+{
+	int i = -1, j = 0, ret = 0, pos = off;
+	char tmps[SPPCTL_MAX_NAM + 3];
+	uint8_t pin = 0;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+	struct device *_pdev = container_of(_k, struct device, kobj);
+
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	for (i = 0; i < list_funcsSZ; i++) {
+		f = &(list_funcs[i]);
+		pin = 0;
+		if (f->freg == fOFF_0)
+			continue;
+		if (f->freg == fOFF_I)
+			continue;
+		memset(tmps, 0, SPPCTL_MAX_NAM + 3);
+
+		// muxable pins are P1_xx, stored -7, absolute idx = +7
+		pin = sppctl_fun_get(_p, j++);
+		if (f->freg == fOFF_M && pin > 0)
+			pin += 7;
+		if (f->freg == fOFF_G)
+			pin = sppctl_gmx_get(_p, f->roff, f->boff, f->blen);
+		sprintf(tmps, "%03d %s", pin, f->name);
+
+		if (pos > 0) {
+			pos -= (strlen(tmps) + 1);
+			continue;
+		}
+		sprintf(_b + ret, "%s\n", tmps);
+		ret += strlen(tmps) + 1;
+		if (ret > SPPCTL_MAX_BUF - SPPCTL_MAX_NAM)
+			break;
+	}
+
+	return ret;
+}
+
+static ssize_t sppctl_sop_func_R(struct file *_filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t _off, size_t _count)
+{
+	struct device *_pdev = NULL;
+	struct sppctl_sdata_t *sdp = NULL;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+
+	if (_off > 0)
+		return 0;
+
+	_pdev = container_of(_k, struct device, kobj);
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	sdp = (struct sppctl_sdata_t *)_a->private;
+	if (!sdp)
+		return -ENXIO;
+
+	f = &(list_funcs[sdp->i]);
+	if (f->freg == fOFF_M)
+		_b[0] = sppctl_fun_get(_p, sdp->ridx);
+	if (f->freg == fOFF_G)
+		_b[0] = sppctl_gmx_get(_p, f->roff, f->boff, f->blen);
+	_b[1] = 0x00;
+	if (_p->debug)
+		KDBG(_pdev, "%s(%s,i:%d) _b:%d\n", __func__, _a->attr.name, sdp->ridx, _b[0]);
+
+	return 1;
+}
+
+static ssize_t sppctl_sop_func_W(struct file *_filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t _off, size_t _count)
+{
+	struct device *_pdev = NULL;
+	struct sppctl_sdata_t *sdp = NULL;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+
+	if (_off > 0)
+		return 0;
+
+	_pdev = container_of(_k, struct device, kobj);
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	sdp = (struct sppctl_sdata_t *)_a->private;
+	if (!sdp)
+		return -ENXIO;
+
+	f = &(list_funcs[sdp->i]);
+	// for mux it should be PIN-7, case muxable pins start from 8'th
+	if (f->freg == fOFF_M)
+		sppctl_pin_set(_p, (_b[0] < 8 ? 0 : _b[0] - 7), sdp->ridx);
+	if (f->freg == fOFF_G)
+		sppctl_gmx_set(_p, f->roff, f->boff, f->blen, _b[0]);
+	if (_p->debug)
+		KDBG(_pdev, "%s(%s,i:%d) _b:%d\n", __func__, _a->attr.name, sdp->ridx, _b[0]);
+
+	return _count;
+}
+
+static ssize_t sppctl_sop_fw_R(struct file *filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t _off, size_t _count)
+{
+	int i = 0, j = 0, ret = 0, pos = _off;
+	uint8_t pin = 0;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+	struct device *_pdev = container_of(_k, struct device, kobj);
+
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	for (i = 0; i < list_funcsSZ && ret < _count; i++) {
+		f = &(list_funcs[i]);
+		if (f->freg == fOFF_0)
+			continue;
+		if (f->freg == fOFF_I)
+			continue;
+		if (f->freg == fOFF_M)
+			pin = sppctl_fun_get(_p, j++);
+		if (f->freg == fOFF_G)
+			pin = sppctl_gmx_get(_p, f->roff, f->boff, f->blen);
+		if (pos > 0) {
+			pos -= sizeof(pin);
+			continue;
+		}
+		_b[ret] = pin;
+		ret += sizeof(pin);
+		if (ret > SPPCTL_MAX_BUF - SPPCTL_MAX_NAM)
+			break;
+	}
+
+	return ret;
+}
+
+static ssize_t sppctl_sop_fw_W(struct file *filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t _off, size_t _count)
+{
+	int i = 0, j = 0, pos = 0;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+	struct device *_pdev = container_of(_k, struct device, kobj);
+
+	if (_off + _count < (list_funcsSZ - 2))
+		KINF(_pdev, "%s() fw size %zd < %zd\n", __func__, _count, list_funcsSZ);
+
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	for (; i < list_funcsSZ && pos < _count; i++) {
+		f = &(list_funcs[i]);
+		if (f->freg == fOFF_0)
+			continue;
+		if (f->freg == fOFF_I)
+			continue;
+		if (j < _off) {
+			j++;
+			continue;
+		}
+
+		if (f->freg == fOFF_M)
+			sppctl_pin_set(_p, _b[pos], j++);
+		if (f->freg == fOFF_G)
+			sppctl_gmx_set(_p, f->roff, f->boff, f->blen, _b[pos]);
+
+		pos++;
+	}
+
+	return pos;
+}
+
+static struct device_attribute sppctl_sysfs_attrsD[] = {
+	__ATTR(name,   0444, sppctl_sop_name_R,   NULL),
+	__ATTR(dbgi,   0644, sppctl_sop_dbgi_R,   sppctl_sop_dbgi_W),
+	__ATTR(fwname, 0644, sppctl_sop_fwname_R, sppctl_sop_fwname_W),
+};
+
+static struct bin_attribute sppctl_sysfs_attrsB[] = {
+	__BIN_ATTR(list_muxes, 0444, sppctl_sop_list_muxes_R, NULL,            SPPCTL_MAX_BUF),
+	__BIN_ATTR(txt_map,    0444, sppctl_sop_txt_map_R,    NULL,            SPPCTL_MAX_BUF),
+	__BIN_ATTR(fw,         0644, sppctl_sop_fw_R,         sppctl_sop_fw_W, SPPCTL_MAX_BUF),
+};
+
+struct bin_attribute *sppctl_sysfs_Fap;
+
+// ---------- main (exported) functions
+void sppctl_sysfs_init(struct platform_device *_pd)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+	struct sppctl_sdata_t *sdp = NULL;
+	int i, ret, ridx = 0;
+	const char *tmpp;
+
+	for (i = 0; i < ARRAY_SIZE(sppctl_sysfs_attrsD); i++) {
+		ret = device_create_file(&(_pd->dev), &sppctl_sysfs_attrsD[i]);
+		if (ret)
+			KERR(&(_pd->dev), "createD[%d] error\n", i);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(sppctl_sysfs_attrsB); i++) {
+		ret = device_create_bin_file(&(_pd->dev), &sppctl_sysfs_attrsB[i]);
+		if (ret)
+			KERR(&(_pd->dev), "createB[%d] error\n", i);
+	}
+
+	i = -1;
+	sppctl_sysfs_Fap = kcalloc(list_funcsSZ, sizeof(struct bin_attribute), GFP_KERNEL);
+	sdp = kcalloc(list_funcsSZ, sizeof(struct sppctl_sdata_t), GFP_KERNEL);
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg == fOFF_0)
+			continue;
+		if (list_funcs[i].freg == fOFF_I)
+			continue;
+
+		tmpp = list_funcs[i].name;
+		sdp[i].i = i;
+		sdp[i].ridx = ridx++;
+		sdp[i].pdata = _p;
+
+		sysfs_bin_attr_init(sppctl_sysfs_Fap[i]);
+		sppctl_sysfs_Fap[i].attr.name = tmpp;
+		sppctl_sysfs_Fap[i].attr.mode = 0644;
+		sppctl_sysfs_Fap[i].read  = sppctl_sop_func_R;
+		sppctl_sysfs_Fap[i].write = sppctl_sop_func_W;
+		sppctl_sysfs_Fap[i].size = SPPCTL_MAX_BUF;
+		sppctl_sysfs_Fap[i].private = &(sdp[i]);
+		ret = device_create_bin_file(&(_pd->dev), &(sppctl_sysfs_Fap[i]));
+
+		if (ret)
+			KERR(&(_pd->dev), "createF[%d,%s] error\n", i, tmpp);
+	}
+	_p->sysfs_sdp = sdp;
+}
+
+void sppctl_sysfs_clean(struct platform_device *_pd)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(sppctl_sysfs_attrsD); i++)
+		device_remove_file(&(_pd->dev), &sppctl_sysfs_attrsD[i]);
+	for (i = 0; i < ARRAY_SIZE(sppctl_sysfs_attrsB); i++)
+		device_remove_bin_file(&(_pd->dev), &sppctl_sysfs_attrsB[i]);
+
+	i = -1;
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg == fOFF_0)
+			continue;
+		if (list_funcs[i].freg == fOFF_I)
+			continue;
+		device_remove_bin_file(&(_pd->dev), &(sppctl_sysfs_Fap[i]));
+	}
+
+	kfree(sppctl_sysfs_Fap);
+	kfree(_p->sysfs_sdp);
+}
diff --git a/drivers/pinctrl/sunplus/sppctl_sysfs.h b/drivers/pinctrl/sunplus/sppctl_sysfs.h
new file mode 100644
index 0000000..f37b8cf
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_sysfs.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#ifndef SPPCTL_SYSFS_H
+#define SPPCTL_SYSFS_H
+
+#include "sppctl.h"
+
+
+struct sppctl_sdata_t {
+	uint8_t i;
+	uint8_t ridx;
+	struct sppctl_pdata_t *pdata;
+};
+
+void sppctl_sysfs_init(struct platform_device *_pdev);
+void sppctl_sysfs_clean(struct platform_device *_pdev);
+
+#endif // SPPCTL_SYSFS_H
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 2/3] dt-bindings: pinctrl: Add dt-bindings for Sunplus SP7021
  2021-10-27  8:55 [PATCH 0/3] Add pin control driver for Sunplus SP7021 SoC Wells Lu
  2021-10-27  8:55 ` [PATCH 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
@ 2021-10-27  8:55 ` Wells Lu
  2021-10-27  8:55 ` [PATCH 3/3] devicetree: bindings: pinctrl: Add bindings doc " Wells Lu
  2021-11-01  8:11 ` [PATCH v2 0/3] This is a patch series for pinctrl driver for Sunplus SP7021 SoC Wells Lu
  3 siblings, 0 replies; 19+ messages in thread
From: Wells Lu @ 2021-10-27  8:55 UTC (permalink / raw)
  To: linus.walleij, linux-gpio, linux-kernel, robh+dt, devicetree
  Cc: qinjian, dvorkin, Wells Lu

Add dt-bindings header files for Sunplus SP7021 SoC.

Signed-off-by: Wells Lu <wells.lu@sunplus.com>
---
 MAINTAINERS                                 |   1 +
 include/dt-bindings/pinctrl/sppctl-sp7021.h | 136 ++++++++++++++++++++++++++++
 include/dt-bindings/pinctrl/sppctl.h        |  40 ++++++++
 3 files changed, 177 insertions(+)
 create mode 100644 include/dt-bindings/pinctrl/sppctl-sp7021.h
 create mode 100644 include/dt-bindings/pinctrl/sppctl.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 43d587c..9cae8e7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14873,6 +14873,7 @@ L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
 S:	Maintained
 W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
 F:	drivers/pinctrl/sunplus/
+F:	include/dt-bindings/pinctrl/sppctl*
 
 PKTCDVD DRIVER
 M:	linux-block@vger.kernel.org
diff --git a/include/dt-bindings/pinctrl/sppctl-sp7021.h b/include/dt-bindings/pinctrl/sppctl-sp7021.h
new file mode 100644
index 0000000..2900310
--- /dev/null
+++ b/include/dt-bindings/pinctrl/sppctl-sp7021.h
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux pinctrl bindings.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ */
+
+#ifndef _DT_BINDINGS_PINCTRL_SPPCTL_SP7021_H
+#define _DT_BINDINGS_PINCTRL_SPPCTL_SP7021_H
+
+#include <dt-bindings/pinctrl/sppctl.h>
+
+#define MUXF_GPIO                       0
+#define MUXF_IOP                        1
+#define MUXF_L2SW_CLK_OUT               2
+#define MUXF_L2SW_MAC_SMI_MDC           3
+#define MUXF_L2SW_LED_FLASH0            4
+#define MUXF_L2SW_LED_FLASH1            5
+#define MUXF_L2SW_LED_ON0               6
+#define MUXF_L2SW_LED_ON1               7
+#define MUXF_L2SW_MAC_SMI_MDIO          8
+#define MUXF_L2SW_P0_MAC_RMII_TXEN      9
+#define MUXF_L2SW_P0_MAC_RMII_TXD0      10
+#define MUXF_L2SW_P0_MAC_RMII_TXD1      11
+#define MUXF_L2SW_P0_MAC_RMII_CRSDV     12
+#define MUXF_L2SW_P0_MAC_RMII_RXD0      13
+#define MUXF_L2SW_P0_MAC_RMII_RXD1      14
+#define MUXF_L2SW_P0_MAC_RMII_RXER      15
+#define MUXF_L2SW_P1_MAC_RMII_TXEN      16
+#define MUXF_L2SW_P1_MAC_RMII_TXD0      17
+#define MUXF_L2SW_P1_MAC_RMII_TXD1      18
+#define MUXF_L2SW_P1_MAC_RMII_CRSDV     19
+#define MUXF_L2SW_P1_MAC_RMII_RXD0      20
+#define MUXF_L2SW_P1_MAC_RMII_RXD1      21
+#define MUXF_L2SW_P1_MAC_RMII_RXER      22
+#define MUXF_DAISY_MODE                 23
+#define MUXF_SDIO_CLK                   24
+#define MUXF_SDIO_CMD                   25
+#define MUXF_SDIO_D0                    26
+#define MUXF_SDIO_D1                    27
+#define MUXF_SDIO_D2                    28
+#define MUXF_SDIO_D3                    29
+#define MUXF_PWM0                       30
+#define MUXF_PWM1                       31
+#define MUXF_PWM2                       32
+#define MUXF_PWM3                       33
+#define MUXF_PWM4                       34
+#define MUXF_PWM5                       35
+#define MUXF_PWM6                       36
+#define MUXF_PWM7                       37
+#define MUXF_ICM0_D                     38
+#define MUXF_ICM1_D                     39
+#define MUXF_ICM2_D                     40
+#define MUXF_ICM3_D                     41
+#define MUXF_ICM0_CLK                   42
+#define MUXF_ICM1_CLK                   43
+#define MUXF_ICM2_CLK                   44
+#define MUXF_ICM3_CLK                   45
+#define MUXF_SPIM0_INT                  46
+#define MUXF_SPIM0_CLK                  47
+#define MUXF_SPIM0_EN                   48
+#define MUXF_SPIM0_DO                   49
+#define MUXF_SPIM0_DI                   50
+#define MUXF_SPIM1_INT                  51
+#define MUXF_SPIM1_CLK                  52
+#define MUXF_SPIM1_EN                   53
+#define MUXF_SPIM1_DO                   54
+#define MUXF_SPIM1_DI                   55
+#define MUXF_SPIM2_INT                  56
+#define MUXF_SPIM2_CLK                  57
+#define MUXF_SPIM2_EN                   58
+#define MUXF_SPIM2_DO                   59
+#define MUXF_SPIM2_DI                   60
+#define MUXF_SPIM3_INT                  61
+#define MUXF_SPIM3_CLK                  62
+#define MUXF_SPIM3_EN                   63
+#define MUXF_SPIM3_DO                   64
+#define MUXF_SPIM3_DI                   65
+#define MUXF_SPI0S_INT                  66
+#define MUXF_SPI0S_CLK                  67
+#define MUXF_SPI0S_EN                   68
+#define MUXF_SPI0S_DO                   69
+#define MUXF_SPI0S_DI                   70
+#define MUXF_SPI1S_INT                  71
+#define MUXF_SPI1S_CLK                  72
+#define MUXF_SPI1S_EN                   73
+#define MUXF_SPI1S_DO                   74
+#define MUXF_SPI1S_DI                   75
+#define MUXF_SPI2S_INT                  76
+#define MUXF_SPI2S_CLK                  77
+#define MUXF_SPI2S_EN                   78
+#define MUXF_SPI2S_DO                   79
+#define MUXF_SPI2S_DI                   80
+#define MUXF_SPI3S_INT                  81
+#define MUXF_SPI3S_CLK                  82
+#define MUXF_SPI3S_EN                   83
+#define MUXF_SPI3S_DO                   84
+#define MUXF_SPI3S_DI                   85
+#define MUXF_I2CM0_CLK                  86
+#define MUXF_I2CM0_DAT                  87
+#define MUXF_I2CM1_CLK                  88
+#define MUXF_I2CM1_DAT                  89
+#define MUXF_I2CM2_CLK                  90
+#define MUXF_I2CM2_DAT                  91
+#define MUXF_I2CM3_CLK                  92
+#define MUXF_I2CM3_DAT                  93
+#define MUXF_UA1_TX                     94
+#define MUXF_UA1_RX                     95
+#define MUXF_UA1_CTS                    96
+#define MUXF_UA1_RTS                    97
+#define MUXF_UA2_TX                     98
+#define MUXF_UA2_RX                     99
+#define MUXF_UA2_CTS                    100
+#define MUXF_UA2_RTS                    101
+#define MUXF_UA3_TX                     102
+#define MUXF_UA3_RX                     103
+#define MUXF_UA3_CTS                    104
+#define MUXF_UA3_RTS                    105
+#define MUXF_UA4_TX                     106
+#define MUXF_UA4_RX                     107
+#define MUXF_UA4_CTS                    108
+#define MUXF_UA4_RTS                    109
+#define MUXF_TIMER0_INT                 110
+#define MUXF_TIMER1_INT                 111
+#define MUXF_TIMER2_INT                 112
+#define MUXF_TIMER3_INT                 113
+#define MUXF_GPIO_INT0                  114
+#define MUXF_GPIO_INT1                  115
+#define MUXF_GPIO_INT2                  116
+#define MUXF_GPIO_INT3                  117
+#define MUXF_GPIO_INT4                  118
+#define MUXF_GPIO_INT5                  119
+#define MUXF_GPIO_INT6                  120
+#define MUXF_GPIO_INT7                  121
+
+#endif
diff --git a/include/dt-bindings/pinctrl/sppctl.h b/include/dt-bindings/pinctrl/sppctl.h
new file mode 100644
index 0000000..3e82989
--- /dev/null
+++ b/include/dt-bindings/pinctrl/sppctl.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux pinctrl bindings.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ */
+
+#ifndef _DT_BINDINGS_PINCTRL_SPPCTL_H
+#define _DT_BINDINGS_PINCTRL_SPPCTL_H
+
+#define IOP_G_MASTE             (0x01<<0)
+#define IOP_G_FIRST             (0x01<<1)
+
+#define SPPCTL_PCTL_G_PMUX      (0x00|IOP_G_MASTE)
+#define SPPCTL_PCTL_G_GPIO      (IOP_G_FIRST|IOP_G_MASTE)
+#define SPPCTL_PCTL_G_IOPP      (IOP_G_FIRST|0x00)
+
+#define SPPCTL_PCTL_L_OUT       (0x01<<0)
+#define SPPCTL_PCTL_L_OU1       (0x01<<1)
+#define SPPCTL_PCTL_L_INV       (0x01<<2)
+#define SPPCTL_PCTL_L_ONV       (0x01<<3)
+#define SPPCTL_PCTL_L_ODR       (0x01<<4)
+
+#define SPPCTL_PCTLE_P(v)       ((v)<<24)
+#define SPPCTL_PCTLE_G(v)       ((v)<<16)
+#define SPPCTL_PCTLE_F(v)       ((v)<<8)
+#define SPPCTL_PCTLE_L(v)       ((v)<<0)
+
+#define SPPCTL_PCTLD_P(v)       (((v)>>24) & 0xFF)
+#define SPPCTL_PCTLD_G(v)       (((v)>>16) & 0xFF)
+#define SPPCTL_PCTLD_F(v)       (((v) >> 8) & 0xFF)
+#define SPPCTL_PCTLD_L(v)       (((v) >> 0) & 0xFF)
+
+/*
+ * pack into 32-bit value:
+ * pin#{8bit}, typ{8bit}, function{8bit}, flags{8bit}
+ */
+#define SPPCTL_IOPAD(pin, typ, fun, fls) (((pin)<<24)|((typ)<<16)|((fun)<<8)|(fls))
+
+#endif
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH 3/3] devicetree: bindings: pinctrl: Add bindings doc for Sunplus SP7021.
  2021-10-27  8:55 [PATCH 0/3] Add pin control driver for Sunplus SP7021 SoC Wells Lu
  2021-10-27  8:55 ` [PATCH 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
  2021-10-27  8:55 ` [PATCH 2/3] dt-bindings: pinctrl: Add dt-bindings " Wells Lu
@ 2021-10-27  8:55 ` Wells Lu
  2021-11-09  3:59   ` Linus Walleij
  2021-11-01  8:11 ` [PATCH v2 0/3] This is a patch series for pinctrl driver for Sunplus SP7021 SoC Wells Lu
  3 siblings, 1 reply; 19+ messages in thread
From: Wells Lu @ 2021-10-27  8:55 UTC (permalink / raw)
  To: linus.walleij, linux-gpio, linux-kernel, robh+dt, devicetree
  Cc: qinjian, dvorkin, Wells Lu

Add bindings documentation for Sunplus SP7021.

Signed-off-by: Wells Lu <wells.lu@sunplus.com>
---
 .../bindings/pinctrl/sunplus,sp7021-pinctrl.yaml   | 277 +++++++++++++++++++++
 MAINTAINERS                                        |   1 +
 2 files changed, 278 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml

diff --git a/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
new file mode 100644
index 0000000..7cfa0ce
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
@@ -0,0 +1,277 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pinctrl/sunplus,sp7021-pinctrl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 Pin Controller Device Tree Bindings
+
+maintainers:
+  - Dvorkin Dmitry <dvorkin@tibbo.com>
+  - Wells Lu <wells.lu@sunplus.com>
+
+description: |
+  The Sunplus SP7021 pin controller is used to control SoC pins. Please
+  refer to pinctrl-bindings.txt in this directory for details of the common
+  pinctrl bindings used by client devices.
+
+  Refer to https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/pages/
+  1443495991/How+to+setup+pins+of+SP7021+in+device-tree+source
+
+  The device node of pin controller of Sunplus SP7021 has following
+  properties.
+
+properties:
+  compatible:
+    const: sunplus,sp7021-pctl
+
+  gpio-controller: true
+
+  '#gpio-cells':
+    const: 2
+
+  reg:
+    items:
+      - description: Base address and length of the MOON2 registers.
+      - description: Base address and length of the GPIOXT registers.
+      - description: Base address and length of the GPIOXT2 registers.
+      - description: Base address and length of the FIRST registers.
+      - description: Base address and length of the MOON1 registers.
+
+  clocks:
+    maxItems: 1
+
+  resets:
+    maxItems: 1
+
+patternProperties:
+  '^.*$':
+    if:
+      type: object
+    then:
+      description: |
+        A pinctrl node should contain at least one subnodes representing the
+        pins or function-pins group available on the machine. Each subnode
+        will list the pins it needs, and how they should be configured.
+
+        Pinctrl node's client devices use subnodes for desired pin
+        configuration. Client device subnodes use below standard properties.
+
+      properties:
+        pins:
+          description: |
+            Define pins which are used by pinctrl node's client device.
+
+            It consists of one or more integers which represents the config
+            setting for corresponding pin. Please use macro SPPCTL_IOPAD to
+            define the integers for pins.
+
+            The first argument of the macro is pin number, the second is pin
+            type, the third is type of GPIO, the last is default output state
+            of GPIO.
+          $ref: /schemas/types.yaml#/definitions/uint32-array
+
+        function:
+          description: |
+            Define pin-function which is used by pinctrl node's client device.
+            The name should be one of string in the following enumeration.
+          $ref: "/schemas/types.yaml#/definitions/string"
+          enum: [ SPI_FLASH, SPI_FLASH_4BIT, SPI_NAND, CARD0_EMMC, SD_CARD,
+                  UA0, FPGA_IFX, HDMI_TX, LCDIF, USB0_OTG, USB1_OTG ]
+
+        groups:
+          description: |
+            Define pin-group in a specified pin-function.
+            The name should be one of string in the following enumeration.
+          $ref: "/schemas/types.yaml#/definitions/string"
+          enum: [ SPI_FLASH1, SPI_FLASH2, SPI_FLASH_4BIT1, SPI_FLASH_4BIT2,
+                  SPI_NAND, CARD0_EMMC, SD_CARD, UA0, FPGA_IFX, HDMI_TX1,
+                  HDMI_TX2, HDMI_TX3, LCDIF, USB0_OTG, USB1_OTG ]
+
+        zero_func:
+          description: |
+            Disabled pins which are not used by pinctrl node's client device.
+          $ref: /schemas/types.yaml#/definitions/uint32-array
+
+      additionalProperties: false
+
+      allOf:
+        - if:
+            properties:
+              function:
+                enum:
+                  - SPI_FLASH
+          then:
+            properties:
+              groups:
+                enum:
+                  - SPI_FLASH1
+                  - SPI_FLASH2
+        - if:
+            properties:
+              function:
+                enum:
+                  - SPI_FLASH_4BIT
+          then:
+            properties:
+              groups:
+                enum:
+                  - SPI_FLASH_4BIT1
+                  - SPI_FLASH_4BIT2
+        - if:
+            properties:
+              function:
+                enum:
+                  - SPI_NAND
+          then:
+            properties:
+              groups:
+                enum:
+                  - SPI_NAND
+        - if:
+            properties:
+              function:
+                enum:
+                  - CARD0_EMMC
+          then:
+            properties:
+              groups:
+                enum:
+                  - CARD0_EMMC
+        - if:
+            properties:
+              function:
+                enum:
+                  - SD_CARD
+          then:
+            properties:
+              groups:
+                enum:
+                  - SD_CARD
+        - if:
+            properties:
+              function:
+                enum:
+                  - UA0
+          then:
+            properties:
+              groups:
+                enum:
+                  - UA0
+        - if:
+            properties:
+              function:
+                enum:
+                  - FPGA_IFX
+          then:
+            properties:
+              groups:
+                enum:
+                  - FPGA_IFX
+        - if:
+            properties:
+              function:
+                enum:
+                  - HDMI_TX
+          then:
+            properties:
+              groups:
+                enum:
+                  - HDMI_TX1
+                  - HDMI_TX2
+                  - HDMI_TX3
+        - if:
+            properties:
+              function:
+                enum:
+                  - LCDIF
+          then:
+            properties:
+              groups:
+                enum:
+                  - LCDIF
+        - if:
+            properties:
+              function:
+                enum:
+                  - USB0_OTG
+          then:
+            properties:
+              groups:
+                enum:
+                  - USB0_OTG
+        - if:
+            properties:
+              function:
+                enum:
+                  - USB1_OTG
+          then:
+            properties:
+              groups:
+                enum:
+                  - USB1_OTG
+
+required:
+  - compatible
+  - reg
+  - "#gpio-cells"
+  - gpio-controller
+  - clocks
+  - resets
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/sp-sp7021.h>
+    #include <dt-bindings/reset/sp-sp7021.h>
+    #include <dt-bindings/pinctrl/sppctl-sp7021.h>
+
+    pctl: pctl@9C000100 {
+        compatible = "sunplus,sp7021-pctl";
+        reg = <0x9C000100 0x100>, <0x9C000300 0x80>, <0x9C000380 0x80>,
+              <0x9C0032e4 0x1C>, <0x9C000080 0x20>;
+        gpio-controller;
+        #gpio-cells = <2>;
+        clocks = <&clkc GPIO>;
+        resets = <&rstc RST_GPIO>;
+
+        pins_uart0: pins_uart0 {
+            function = "UA0";
+            groups = "UA0";
+        };
+
+        pins_uart1: pins_uart1 {
+            pins = <
+                SPPCTL_IOPAD(11,SPPCTL_PCTL_G_PMUX,MUXF_UA1_TX,0)
+                SPPCTL_IOPAD(10,SPPCTL_PCTL_G_PMUX,MUXF_UA1_RX,0)
+                SPPCTL_IOPAD(7,SPPCTL_PCTL_G_GPIO,0,SPPCTL_PCTL_L_OUT)
+            >;
+        };
+
+        emmc_mux: emmc_mux {
+            function = "CARD0_EMMC";
+            groups = "CARD0_EMMC";
+        };
+
+        mmc1_mux: mmc1_mux {
+            function = "SD_CARD";
+            groups = "SD_CARD";
+            pins = < SPPCTL_IOPAD(91,SPPCTL_PCTL_G_GPIO,0,0) >;
+        };
+
+        hdmi_A_tx1: hdmi_A_tx1_pins {
+            function = "HDMI_TX";
+            groups = "HDMI_TX1";
+        };
+        hdmi_A_tx2: hdmi_A_tx2_pins {
+            function = "HDMI_TX";
+            groups = "HDMI_TX2";
+        };
+        hdmi_A_tx3: hdmi_A_tx3_pins {
+            function = "HDMI_TX";
+            groups = "HDMI_TX3";
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 9cae8e7..fe3f359 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14872,6 +14872,7 @@ M:	Wells Lu <wells.lu@sunplus.com>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
 S:	Maintained
 W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+F:	Documentation/devicetree/bindings/pinctrl/sunplus,*
 F:	drivers/pinctrl/sunplus/
 F:	include/dt-bindings/pinctrl/sppctl*
 
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [PATCH 1/3] pinctrl: Add driver for Sunplus SP7021
  2021-10-27  8:55 ` [PATCH 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
@ 2021-10-27 22:12   ` Randy Dunlap
  2021-10-29  3:40     ` Wells Lu 呂芳騰
  0 siblings, 1 reply; 19+ messages in thread
From: Randy Dunlap @ 2021-10-27 22:12 UTC (permalink / raw)
  To: Wells Lu, linus.walleij, linux-gpio, linux-kernel, robh+dt, devicetree
  Cc: qinjian, dvorkin, Wells Lu

Hi--

On 10/27/21 1:55 AM, Wells Lu wrote:
> diff --git a/drivers/pinctrl/sunplus/Kconfig b/drivers/pinctrl/sunplus/Kconfig
> new file mode 100644
> index 0000000..93b5ccf
> --- /dev/null
> +++ b/drivers/pinctrl/sunplus/Kconfig
> @@ -0,0 +1,32 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Sunplus Pin control driver configuration
> +#
> +
> +config PINCTRL_SPPCTL
> +	bool "Sunplus SP7021 pinmux and gpio driver"

Preferably                              GPIO

> +	depends on SOC_SP7021
> +	select PINMUX
> +	select GENERIC_PINCTRL_GROUPS
> +	select CONFIG_GENERIC_PINMUX_FUNCTIONS

	Drop   CONFIG_

> +	select PINCONF
> +	select GENERIC_PINCONF
> +	select OF_GPIO

Probably
	depends on OF && HAS_IOMEM
Otherwise how do you know that it's safe to do
	select OF_GPIO
?


> +	select GPIOLIB
> +	select GPIO_SYSFS
> +	select GENERIC_IRQ_CHIP
> +	select GPIOLIB_IRQCHIP
> +	help
> +	  Say Y here to support Sunplus SP7021 pinmux controller.
> +	  The driveer is selected automatically by platform.

	      driver

> +	  This driver requires the pinctrl framework.
> +	  GPIO is provided by the same driver.
> +
> +config PINCTRL_SPPCTL_DEBUG
> +	bool "Sunplus pinmux specific debug"
> +	depends on SOC_SP7021 && DEBUG_PINCTRL
> +	help
> +	  Say Y if you need to debug Sunplus pinmux driver in-depth.
> +	  Pin control driver will output more messages if you enable
> +	  this item. This function is dependent on DEBUG_PINCTRL. It
> +	  should be enabled first.


thanks.
-- 
~Randy

^ permalink raw reply	[flat|nested] 19+ messages in thread

* RE: [PATCH 1/3] pinctrl: Add driver for Sunplus SP7021
  2021-10-27 22:12   ` Randy Dunlap
@ 2021-10-29  3:40     ` Wells Lu 呂芳騰
  2021-10-29  4:38       ` Randy Dunlap
  0 siblings, 1 reply; 19+ messages in thread
From: Wells Lu 呂芳騰 @ 2021-10-29  3:40 UTC (permalink / raw)
  To: Randy Dunlap, Wells Lu, linus.walleij, linux-gpio, linux-kernel,
	robh+dt, devicetree
  Cc: qinjian, dvorkin

Hi Sir,

Thank you for your review.

I modified errors in Kconfig you pointed out.

Could you please teach me what is the next step I need to do
(This is my first submission to Linux kernel main-line)?

1. Should I make a patch [PATHC 1/3 v2] for Kconfig and submit 
  again?
2. Or wait for other files of the patch [PATCH 1/3] being 
  reviewed and then submit patch again.
3. At first patch, I used 'git send-email' command to send a 
  patch series. The command is:
  git send-email \
  --to linus.walleij@linaro.org \
  --to linux-gpio@vger.kernel.org \
  --to linux-kernel@vger.kernel.org \
  --to robh+dt@kernel.org \
  --to devicetree@vger.kernel.org \
  --cc dvorkin@tibbo.com \
  --cc qinjian@cqplus1.com \
  --thread 000*.patch
  Please teach me what command options of 'git send-email' 
  I should use to send the version 2 patch.
4. If this is not a correct way to reply your email, please teach 
  me how to reply maintainers' email.


Best regards,

Wells Lu


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 1/3] pinctrl: Add driver for Sunplus SP7021
  2021-10-29  3:40     ` Wells Lu 呂芳騰
@ 2021-10-29  4:38       ` Randy Dunlap
  0 siblings, 0 replies; 19+ messages in thread
From: Randy Dunlap @ 2021-10-29  4:38 UTC (permalink / raw)
  To: Wells Lu 呂芳騰,
	Wells Lu, linus.walleij, linux-gpio, linux-kernel, robh+dt,
	devicetree
  Cc: qinjian, dvorkin

On 10/28/21 8:40 PM, Wells Lu 呂芳騰 wrote:
> Hi Sir,
> 
> Thank you for your review.
> 
> I modified errors in Kconfig you pointed out.
> 
> Could you please teach me what is the next step I need to do
> (This is my first submission to Linux kernel main-line)?

Hi,

I would wait another day or two to see if there are any other
comments or reviews of the patch series, then send a v2 of
the entire series.

> 1. Should I make a patch [PATHC 1/3 v2] for Kconfig and submit
>    again?
> 2. Or wait for other files of the patch [PATCH 1/3] being
>    reviewed and then submit patch again.
> 3. At first patch, I used 'git send-email' command to send a
>    patch series. The command is:
>    git send-email \
>    --to linus.walleij@linaro.org \
>    --to linux-gpio@vger.kernel.org \
>    --to linux-kernel@vger.kernel.org \
>    --to robh+dt@kernel.org \
>    --to devicetree@vger.kernel.org \
>    --cc dvorkin@tibbo.com \
>    --cc qinjian@cqplus1.com \
>    --thread 000*.patch
>    Please teach me what command options of 'git send-email'
>    I should use to send the version 2 patch.

I suppose that you are using git to create patches.
I don't create patches with git, so I don't know what it takes to
create and send v2 of a patch series.

If you are not using git to create patches, you can just modify
the Subject: line in each email to include the "v2" string.

In either case, but sure to describe the changes from v1 to v2.
This description goes after (below) the "---" line. I put it before
the diffstat output with a blank line separating them.

> 4. If this is not a correct way to reply your email, please teach
>    me how to reply maintainers' email.

This is fine.

thanks.
-- 
~Randy

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v2 0/3] This is a patch series for pinctrl driver for Sunplus SP7021 SoC.
  2021-10-27  8:55 [PATCH 0/3] Add pin control driver for Sunplus SP7021 SoC Wells Lu
                   ` (2 preceding siblings ...)
  2021-10-27  8:55 ` [PATCH 3/3] devicetree: bindings: pinctrl: Add bindings doc " Wells Lu
@ 2021-11-01  8:11 ` Wells Lu
  2021-11-01  8:11   ` [PATCH v2 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
                     ` (2 more replies)
  3 siblings, 3 replies; 19+ messages in thread
From: Wells Lu @ 2021-11-01  8:11 UTC (permalink / raw)
  To: linus.walleij, linux-gpio, linux-kernel, robh+dt, devicetree
  Cc: qinjian, dvorkin, Wells Lu

Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates
many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and
etc.) into a single chip. It is designed for industrial control.

Refer to:
https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
https://tibbo.com/store/plus1.html

Changes in v2:
 - Addressed all comments from Mr. Randy Dunlap.
 - Added more 'defines' in dt-bindings header files (forgot to add in v1).
 - Modified vendor name in MAINTAINERS file.

Wells Lu (3):
  pinctrl: Add driver for Sunplus SP7021
  dt-bindings: pinctrl: Add dt-bindings for Sunplus SP7021
  devicetree: bindings: pinctrl: Add bindings doc for Sunplus SP7021.

 .../bindings/pinctrl/sunplus,sp7021-pinctrl.yaml   | 277 ++++++++++
 MAINTAINERS                                        |  10 +
 drivers/pinctrl/Kconfig                            |   1 +
 drivers/pinctrl/Makefile                           |   1 +
 drivers/pinctrl/sunplus/Kconfig                    |  33 ++
 drivers/pinctrl/sunplus/Makefile                   |  11 +
 drivers/pinctrl/sunplus/gpio_inf_sp7021.c          |  48 ++
 drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c       | 501 +++++++++++++++++
 drivers/pinctrl/sunplus/sppctl.c                   | 359 +++++++++++++
 drivers/pinctrl/sunplus/sppctl.h                   | 181 +++++++
 drivers/pinctrl/sunplus/sppctl_gpio.c              | 136 +++++
 drivers/pinctrl/sunplus/sppctl_gpio.h              |  73 +++
 drivers/pinctrl/sunplus/sppctl_gpio_ops.c          | 288 ++++++++++
 drivers/pinctrl/sunplus/sppctl_gpio_ops.h          |  75 +++
 drivers/pinctrl/sunplus/sppctl_pinctrl.c           | 593 +++++++++++++++++++++
 drivers/pinctrl/sunplus/sppctl_pinctrl.h           |  33 ++
 drivers/pinctrl/sunplus/sppctl_sysfs.c             | 385 +++++++++++++
 drivers/pinctrl/sunplus/sppctl_sysfs.h             |  33 ++
 include/dt-bindings/pinctrl/sppctl-sp7021.h        | 171 ++++++
 include/dt-bindings/pinctrl/sppctl.h               |  40 ++
 20 files changed, 3249 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
 create mode 100644 drivers/pinctrl/sunplus/Kconfig
 create mode 100644 drivers/pinctrl/sunplus/Makefile
 create mode 100644 drivers/pinctrl/sunplus/gpio_inf_sp7021.c
 create mode 100644 drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio_ops.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio_ops.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_pinctrl.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_pinctrl.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_sysfs.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_sysfs.h
 create mode 100644 include/dt-bindings/pinctrl/sppctl-sp7021.h
 create mode 100644 include/dt-bindings/pinctrl/sppctl.h

-- 
2.7.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v2 1/3] pinctrl: Add driver for Sunplus SP7021
  2021-11-01  8:11 ` [PATCH v2 0/3] This is a patch series for pinctrl driver for Sunplus SP7021 SoC Wells Lu
@ 2021-11-01  8:11   ` Wells Lu
  2021-11-09  4:30     ` Linus Walleij
  2021-11-01  8:11   ` [PATCH v2 2/3] dt-bindings: pinctrl: Add dt-bindings " Wells Lu
  2021-11-01  8:11   ` [PATCH v2 3/3] devicetree: bindings: pinctrl: Add bindings doc " Wells Lu
  2 siblings, 1 reply; 19+ messages in thread
From: Wells Lu @ 2021-11-01  8:11 UTC (permalink / raw)
  To: linus.walleij, linux-gpio, linux-kernel, robh+dt, devicetree
  Cc: qinjian, dvorkin, Wells Lu

Add driver for Sunplus SP7021.

Signed-off-by: Wells Lu <wells.lu@sunplus.com>
---
Changes in v2:
 - Addressed all comments from Mr. Randy Dunlap.
 - Modified vendor name in MAINTAINERS file.

 MAINTAINERS                                  |   8 +
 drivers/pinctrl/Kconfig                      |   1 +
 drivers/pinctrl/Makefile                     |   1 +
 drivers/pinctrl/sunplus/Kconfig              |  33 ++
 drivers/pinctrl/sunplus/Makefile             |  11 +
 drivers/pinctrl/sunplus/gpio_inf_sp7021.c    |  48 +++
 drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c | 501 ++++++++++++++++++++++
 drivers/pinctrl/sunplus/sppctl.c             | 359 ++++++++++++++++
 drivers/pinctrl/sunplus/sppctl.h             | 181 ++++++++
 drivers/pinctrl/sunplus/sppctl_gpio.c        | 136 ++++++
 drivers/pinctrl/sunplus/sppctl_gpio.h        |  73 ++++
 drivers/pinctrl/sunplus/sppctl_gpio_ops.c    | 288 +++++++++++++
 drivers/pinctrl/sunplus/sppctl_gpio_ops.h    |  75 ++++
 drivers/pinctrl/sunplus/sppctl_pinctrl.c     | 593 +++++++++++++++++++++++++++
 drivers/pinctrl/sunplus/sppctl_pinctrl.h     |  33 ++
 drivers/pinctrl/sunplus/sppctl_sysfs.c       | 385 +++++++++++++++++
 drivers/pinctrl/sunplus/sppctl_sysfs.h       |  33 ++
 17 files changed, 2759 insertions(+)
 create mode 100644 drivers/pinctrl/sunplus/Kconfig
 create mode 100644 drivers/pinctrl/sunplus/Makefile
 create mode 100644 drivers/pinctrl/sunplus/gpio_inf_sp7021.c
 create mode 100644 drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio_ops.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_gpio_ops.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_pinctrl.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_pinctrl.h
 create mode 100644 drivers/pinctrl/sunplus/sppctl_sysfs.c
 create mode 100644 drivers/pinctrl/sunplus/sppctl_sysfs.h

diff --git a/MAINTAINERS b/MAINTAINERS
index f26920f..fd82c77 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14866,6 +14866,14 @@ S:	Maintained
 W:	http://www.st.com/spear
 F:	drivers/pinctrl/spear/
 
+PIN CONTROLLER - SUNPLUS / TIBBO
+M:	Dvorkin Dmitry <dvorkin@tibbo.com>
+M:	Wells Lu <wells.lu@sunplus.com>
+L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+S:	Maintained
+W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+F:	drivers/pinctrl/sunplus/
+
 PKTCDVD DRIVER
 M:	linux-block@vger.kernel.org
 S:	Orphan
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 3192110..5fe8e5d 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -452,6 +452,7 @@ source "drivers/pinctrl/mediatek/Kconfig"
 source "drivers/pinctrl/meson/Kconfig"
 source "drivers/pinctrl/cirrus/Kconfig"
 source "drivers/pinctrl/visconti/Kconfig"
+source "drivers/pinctrl/sunplus/Kconfig"
 
 config PINCTRL_XWAY
 	bool
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 200073b..3721877 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -66,6 +66,7 @@ obj-$(CONFIG_PINCTRL_SAMSUNG)	+= samsung/
 obj-$(CONFIG_PINCTRL_SPEAR)	+= spear/
 obj-y				+= sprd/
 obj-$(CONFIG_PINCTRL_STM32)	+= stm32/
+obj-y				+= sunplus/
 obj-$(CONFIG_PINCTRL_SUNXI)	+= sunxi/
 obj-y				+= ti/
 obj-$(CONFIG_PINCTRL_UNIPHIER)	+= uniphier/
diff --git a/drivers/pinctrl/sunplus/Kconfig b/drivers/pinctrl/sunplus/Kconfig
new file mode 100644
index 0000000..502a8fa
--- /dev/null
+++ b/drivers/pinctrl/sunplus/Kconfig
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Sunplus Pin control driver configuration
+#
+
+config PINCTRL_SPPCTL
+	bool "Sunplus SP7021 pinmux and GPIO driver"
+	depends on SOC_SP7021
+	depends on OF && HAS_IOMEM
+	select PINMUX
+	select GENERIC_PINCTRL_GROUPS
+	select GENERIC_PINMUX_FUNCTIONS
+	select PINCONF
+	select GENERIC_PINCONF
+	select OF_GPIO
+	select GPIOLIB
+	select GPIO_SYSFS
+	select GENERIC_IRQ_CHIP
+	select GPIOLIB_IRQCHIP
+	help
+	  Say Y here to support Sunplus SP7021 pinmux controller.
+	  The driver is selected automatically by platform.
+	  This driver requires the pinctrl framework.
+	  GPIO is provided by the same driver.
+
+config PINCTRL_SPPCTL_DEBUG
+	bool "Sunplus pinmux specific debug"
+	depends on SOC_SP7021 && DEBUG_PINCTRL
+	help
+	  Say Y if you need to debug Sunplus pinmux driver in-depth.
+	  Pin control driver will output more messages if you enable
+	  this item. This function is dependent on DEBUG_PINCTRL. It
+	  should be enabled first.
diff --git a/drivers/pinctrl/sunplus/Makefile b/drivers/pinctrl/sunplus/Makefile
new file mode 100644
index 0000000..a945653
--- /dev/null
+++ b/drivers/pinctrl/sunplus/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Sunplus Pin control drivers.
+#
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_pinctrl.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_sysfs.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_gpio_ops.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_gpio.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += pinctrl_inf_sp7021.o
+obj-$(CONFIG_PINCTRL_SPPCTL) += gpio_inf_sp7021.o
diff --git a/drivers/pinctrl/sunplus/gpio_inf_sp7021.c b/drivers/pinctrl/sunplus/gpio_inf_sp7021.c
new file mode 100644
index 0000000..31f77ce
--- /dev/null
+++ b/drivers/pinctrl/sunplus/gpio_inf_sp7021.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "sppctl_gpio.h"
+
+const char * const sppctlgpio_list_s[] = {
+	D_PIS(0, 0),  D_PIS(0, 1),  D_PIS(0, 2),  D_PIS(0, 3),
+	D_PIS(0, 4),  D_PIS(0, 5),  D_PIS(0, 6),  D_PIS(0, 7),
+	D_PIS(1, 0),  D_PIS(1, 1),  D_PIS(1, 2),  D_PIS(1, 3),
+	D_PIS(1, 4),  D_PIS(1, 5),  D_PIS(1, 6),  D_PIS(1, 7),
+	D_PIS(2, 0),  D_PIS(2, 1),  D_PIS(2, 2),  D_PIS(2, 3),
+	D_PIS(2, 4),  D_PIS(2, 5),  D_PIS(2, 6),  D_PIS(2, 7),
+	D_PIS(3, 0),  D_PIS(3, 1),  D_PIS(3, 2),  D_PIS(3, 3),
+	D_PIS(3, 4),  D_PIS(3, 5),  D_PIS(3, 6),  D_PIS(3, 7),
+	D_PIS(4, 0),  D_PIS(4, 1),  D_PIS(4, 2),  D_PIS(4, 3),
+	D_PIS(4, 4),  D_PIS(4, 5),  D_PIS(4, 6),  D_PIS(4, 7),
+	D_PIS(5, 0),  D_PIS(5, 1),  D_PIS(5, 2),  D_PIS(5, 3),
+	D_PIS(5, 4),  D_PIS(5, 5),  D_PIS(5, 6),  D_PIS(5, 7),
+	D_PIS(6, 0),  D_PIS(6, 1),  D_PIS(6, 2),  D_PIS(6, 3),
+	D_PIS(6, 4),  D_PIS(6, 5),  D_PIS(6, 6),  D_PIS(6, 7),
+	D_PIS(7, 0),  D_PIS(7, 1),  D_PIS(7, 2),  D_PIS(7, 3),
+	D_PIS(7, 4),  D_PIS(7, 5),  D_PIS(7, 6),  D_PIS(7, 7),
+	D_PIS(8, 0),  D_PIS(8, 1),  D_PIS(8, 2),  D_PIS(8, 3),
+	D_PIS(8, 4),  D_PIS(8, 5),  D_PIS(8, 6),  D_PIS(8, 7),
+	D_PIS(9, 0),  D_PIS(9, 1),  D_PIS(9, 2),  D_PIS(9, 3),
+	D_PIS(9, 4),  D_PIS(9, 5),  D_PIS(9, 6),  D_PIS(9, 7),
+	D_PIS(10, 0), D_PIS(10, 1), D_PIS(10, 2), D_PIS(10, 3),
+	D_PIS(10, 4), D_PIS(10, 5), D_PIS(10, 6), D_PIS(10, 7),
+	D_PIS(11, 0), D_PIS(11, 1), D_PIS(11, 2), D_PIS(11, 3),
+	D_PIS(11, 4), D_PIS(11, 5), D_PIS(11, 6), D_PIS(11, 7),
+	D_PIS(12, 0), D_PIS(12, 1), D_PIS(12, 2)
+};
+
+const size_t GPIS_listSZ = sizeof(sppctlgpio_list_s)/sizeof(*(sppctlgpio_list_s));
diff --git a/drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c b/drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c
new file mode 100644
index 0000000..1435fba
--- /dev/null
+++ b/drivers/pinctrl/sunplus/pinctrl_inf_sp7021.c
@@ -0,0 +1,501 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "sppctl.h"
+
+// function: GPIO. list of groups (pins)
+const unsigned int sppctlpins_G[] = {
+	D(0, 0), D(0, 1), D(0, 2), D(0, 3), D(0, 4), D(0, 5), D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6), D(1, 7),
+	D(2, 0), D(2, 1), D(2, 2), D(2, 3), D(2, 4), D(2, 5), D(2, 6), D(2, 7),
+	D(3, 0), D(3, 1), D(3, 2), D(3, 3), D(3, 4), D(3, 5), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5), D(4, 6), D(4, 7),
+	D(5, 0), D(5, 1), D(5, 2), D(5, 3), D(5, 4), D(5, 5), D(5, 6), D(5, 7),
+	D(6, 0), D(6, 1), D(6, 2), D(6, 3), D(6, 4), D(6, 5), D(6, 6), D(6, 7),
+	D(7, 0), D(7, 1), D(7, 2), D(7, 3), D(7, 4), D(7, 5), D(7, 6), D(7, 7),
+	D(8, 0), D(8, 1), D(8, 2), D(8, 3), D(8, 4), D(8, 5), D(8, 6), D(8, 7),
+	D(9, 0), D(9, 1), D(9, 2), D(9, 3), D(9, 4), D(9, 5), D(9, 6), D(9, 7),
+	D(10, 0), D(10, 1), D(10, 2), D(10, 3), D(10, 4), D(10, 5), D(10, 6), D(10, 7),
+	D(11, 0), D(11, 1), D(11, 2), D(11, 3), D(11, 4), D(11, 5), D(11, 6), D(11, 7),
+	D(12, 0), D(12, 1), D(12, 2)
+};
+
+#define P(x, y) PINCTRL_PIN(D(x, y), D_PIS(x, y))
+
+const struct pinctrl_pin_desc sppctlpins_all[] = {
+	// gpio and iop only
+	P(0, 0), P(0, 1), P(0, 2), P(0, 3), P(0, 4), P(0, 5), P(0, 6), P(0, 7),
+	// gpio, iop, muxable
+	P(1, 0), P(1, 1), P(1, 2), P(1, 3), P(1, 4), P(1, 5), P(1, 6), P(1, 7),
+	P(2, 0), P(2, 1), P(2, 2), P(2, 3), P(2, 4), P(2, 5), P(2, 6), P(2, 7),
+	P(3, 0), P(3, 1), P(3, 2), P(3, 3), P(3, 4), P(3, 5), P(3, 6), P(3, 7),
+	P(4, 0), P(4, 1), P(4, 2), P(4, 3), P(4, 4), P(4, 5), P(4, 6), P(4, 7),
+	P(5, 0), P(5, 1), P(5, 2), P(5, 3), P(5, 4), P(5, 5), P(5, 6), P(5, 7),
+	P(6, 0), P(6, 1), P(6, 2), P(6, 3), P(6, 4), P(6, 5), P(6, 6), P(6, 7),
+	P(7, 0), P(7, 1), P(7, 2), P(7, 3), P(7, 4), P(7, 5), P(7, 6), P(7, 7),
+	P(8, 0), P(8, 1), P(8, 2), P(8, 3), P(8, 4), P(8, 5), P(8, 6), P(8, 7),
+	// gpio (not wired) and iop only
+	P(9, 0),  P(9, 1),  P(9, 2),  P(9, 3),  P(9, 4),  P(9, 5),  P(9, 6),  P(9, 7),
+	P(10, 0), P(10, 1), P(10, 2), P(10, 3), P(10, 4), P(10, 5), P(10, 6), P(10, 7),
+	P(11, 0), P(11, 1), P(11, 2), P(11, 3), P(11, 4), P(11, 5), P(11, 6), P(11, 7),
+	P(12, 0), P(12, 1), P(12, 2)
+};
+const size_t sppctlpins_allSZ = ARRAY_SIZE(sppctlpins_all);
+
+// pmux groups: some pins are muxable. group = pin
+const char * const sppctlpmux_list_s[] = {
+	D_PIS(0, 0),
+	D_PIS(1, 0), D_PIS(1, 1), D_PIS(1, 2), D_PIS(1, 3),
+	D_PIS(1, 4), D_PIS(1, 5), D_PIS(1, 6), D_PIS(1, 7),
+	D_PIS(2, 0), D_PIS(2, 1), D_PIS(2, 2), D_PIS(2, 3),
+	D_PIS(2, 4), D_PIS(2, 5), D_PIS(2, 6), D_PIS(2, 7),
+	D_PIS(3, 0), D_PIS(3, 1), D_PIS(3, 2), D_PIS(3, 3),
+	D_PIS(3, 4), D_PIS(3, 5), D_PIS(3, 6), D_PIS(3, 7),
+	D_PIS(4, 0), D_PIS(4, 1), D_PIS(4, 2), D_PIS(4, 3),
+	D_PIS(4, 4), D_PIS(4, 5), D_PIS(4, 6), D_PIS(4, 7),
+	D_PIS(5, 0), D_PIS(5, 1), D_PIS(5, 2), D_PIS(5, 3),
+	D_PIS(5, 4), D_PIS(5, 5), D_PIS(5, 6), D_PIS(5, 7),
+	D_PIS(6, 0), D_PIS(6, 1), D_PIS(6, 2), D_PIS(6, 3),
+	D_PIS(6, 4), D_PIS(6, 5), D_PIS(6, 6), D_PIS(6, 7),
+	D_PIS(7, 0), D_PIS(7, 1), D_PIS(7, 2), D_PIS(7, 3),
+	D_PIS(7, 4), D_PIS(7, 5), D_PIS(7, 6), D_PIS(7, 7),
+	D_PIS(8, 0), D_PIS(8, 1), D_PIS(8, 2), D_PIS(8, 3),
+	D_PIS(8, 4), D_PIS(8, 5), D_PIS(8, 6), D_PIS(8, 7)
+};
+// gpio: is defined in gpio_inf_sp7021.c
+const size_t PMUX_listSZ = sizeof(sppctlpmux_list_s)/sizeof(*(sppctlpmux_list_s));
+
+static const unsigned int pins_spif1[] = { D(10, 3), D(10, 4), D(10, 6), D(10, 7) };
+static const unsigned int pins_spif2[] = { D(9, 4), D(9, 6), D(9, 7), D(10, 1) };
+static const struct sppctlgrp_t sp7021grps_spif[] = {
+	EGRP("SPI_FLASH1", 1, pins_spif1),
+	EGRP("SPI_FLASH2", 2, pins_spif2)
+};
+
+static const unsigned int pins_spi41[] = { D(10, 2), D(10, 5) };
+static const unsigned int pins_spi42[] = { D(9, 5), D(9, 8) };
+static const struct sppctlgrp_t sp7021grps_spi4[] = {
+	EGRP("SPI_FLASH_4BIT1", 1, pins_spi41),
+	EGRP("SPI_FLASH_4BIT2", 2, pins_spi42)
+};
+
+static const unsigned int pins_snan[] = {
+	D(9, 4), D(9, 5), D(9, 6), D(9, 7), D(10, 0), D(10, 1)
+};
+static const struct sppctlgrp_t sp7021grps_snan[] = {
+	EGRP("SPI_NAND", 1, pins_snan)
+};
+
+static const unsigned int pins_emmc[] = {
+	D(9, 0), D(9, 1), D(9, 2), D(9, 3), D(9, 4), D(9, 5),
+	D(9, 6), D(9, 7), D(10, 0), D(10, 1) };
+static const struct sppctlgrp_t sp7021grps_emmc[] = {
+	EGRP("CARD0_EMMC", 1, pins_emmc)
+};
+
+static const unsigned int pins_sdsd[] = {
+	D(8, 1), D(8, 2), D(8, 3), D(8, 4), D(8, 5), D(8, 6)
+};
+static const struct sppctlgrp_t sp7021grps_sdsd[] = {
+	EGRP("SD_CARD", 1, pins_sdsd)
+};
+
+static const unsigned int pins_uar0[] = { D(11, 0), D(11, 1) };
+static const struct sppctlgrp_t sp7021grps_uar0[] = {
+	EGRP("UA0", 1, pins_uar0)
+};
+
+static const unsigned int pins_adbg1[] = { D(10, 2), D(10, 3) };
+static const unsigned int pins_adbg2[] = { D(7, 1), D(7, 2) };
+static const struct sppctlgrp_t sp7021grps_adbg[] = {
+	EGRP("ACHIP_DEBUG1", 1, pins_adbg1),
+	EGRP("ACHIP_DEBUG2", 2, pins_adbg2)
+};
+
+static const unsigned int pins_aua2axi1[] = { D(2, 0), D(2, 1), D(2, 2) };
+static const unsigned int pins_aua2axi2[] = { D(1, 0), D(1, 1), D(1, 2) };
+static const struct sppctlgrp_t sp7021grps_au2x[] = {
+	EGRP("ACHIP_UA2AXI1", 1, pins_aua2axi1),
+	EGRP("ACHIP_UA2AXI2", 2, pins_aua2axi2)
+};
+
+static const unsigned int pins_fpga[] = {
+	D(0, 2), D(0, 3), D(0, 4), D(0, 5), D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5),
+	D(1, 6), D(1, 7), D(2, 0), D(2, 1), D(2, 2), D(2, 3),
+	D(2, 4), D(2, 5), D(2, 6), D(2, 7), D(3, 0), D(3, 1),
+	D(3, 2), D(3, 3), D(3, 4), D(3, 5), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5),
+	D(4, 6), D(4, 7), D(5, 0), D(5, 1), D(5, 2)
+};
+static const struct sppctlgrp_t sp7021grps_fpga[] = {
+	EGRP("FPGA_IFX", 1, pins_fpga)
+};
+
+/* CEC pin is not used. Release it for others. */
+//static const unsigned int pins_hdmi1[] = { D(10, 6), D(10, 7), D(12, 2), D(12, 1) };
+//static const unsigned int pins_hdmi2[] = { D(8, 3), D(8, 4), D(8, 5), D(8, 6) };
+//static const unsigned int pins_hdmi3[] = { D(7, 4), D(7, 5), D(7, 6), D(7, 7) };
+
+static const unsigned int pins_hdmi1[] = { D(10, 6), D(12, 2), D(12, 1) };
+static const unsigned int pins_hdmi2[] = { D(8, 3), D(8, 5), D(8, 6) };
+static const unsigned int pins_hdmi3[] = { D(7, 4), D(7, 6), D(7, 7) };
+static const struct sppctlgrp_t sp7021grps_hdmi[] = {
+	EGRP("HDMI_TX1", 1, pins_hdmi1),
+	EGRP("HDMI_TX2", 2, pins_hdmi2),
+	EGRP("HDMI_TX3", 3, pins_hdmi3)
+};
+
+static const unsigned int pins_eadc[] = {
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6)
+};
+static const struct sppctlgrp_t sp7021grps_eadc[] = {
+	EGRP("AUD_EXT_ADC_IFX0", 1, pins_eadc)
+};
+
+static const unsigned int pins_edac[] = {
+	D(2, 5), D(2, 6), D(2, 7), D(3, 0), D(3, 1), D(3, 2), D(3, 4)
+};
+static const struct sppctlgrp_t sp7021grps_edac[] = {
+	EGRP("AUD_EXT_DAC_IFX0", 1, pins_edac)
+};
+
+static const unsigned int pins_spdi[] = { D(2, 4) };
+static const struct sppctlgrp_t sp7021grps_spdi[] = {
+	EGRP("AUD_IEC_RX0", 1, pins_spdi)
+};
+static const unsigned int pins_spdo[] = { D(3, 6) };
+static const struct sppctlgrp_t sp7021grps_spdo[] = {
+	EGRP("AUD_IEC_TX0", 1, pins_spdo)
+};
+
+static const unsigned int pins_tdmt[] = {
+	D(2, 5), D(2, 6), D(2, 7), D(3, 0), D(3, 1), D(3, 2)
+};
+static const struct sppctlgrp_t sp7021grps_tdmt[] = {
+	EGRP("TDMTX_IFX0", 1, pins_tdmt)
+};
+
+static const unsigned int pins_tdmr[] = { D(1, 7), D(2, 0), D(2, 1), D(2, 2) };
+static const struct sppctlgrp_t sp7021grps_tdmr[] = {
+	EGRP("TDMRX_IFX0", 1, pins_tdmr)
+};
+
+static const unsigned int pins_pdmr[] = {
+	D(1, 7), D(2, 0), D(2, 1), D(2, 2), D(2, 3)
+};
+static const struct sppctlgrp_t sp7021grps_pdmr[] = {
+	EGRP("PDMRX_IFX0", 1, pins_pdmr)
+};
+
+static const unsigned int pins_pcmt[] = {
+	D(3, 7), D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4)
+};
+static const struct sppctlgrp_t sp7021grps_pcmt[] = {
+	EGRP("PCM_IEC_TX", 1, pins_pcmt)
+};
+
+static const unsigned int pins_lcdi[] = {
+	D(1, 4), D(1, 5),
+	D(1, 6), D(1, 7), D(2, 0), D(2, 1), D(2, 2), D(2, 3),
+	D(2, 4), D(2, 5), D(2, 6), D(2, 7), D(3, 0), D(3, 1),
+	D(3, 2), D(3, 3), D(3, 4), D(3, 5), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5),
+	D(4, 6), D(4, 7)
+};
+static const struct sppctlgrp_t sp7021grps_lcdi[] = {
+	EGRP("LCDIF", 1, pins_lcdi)
+};
+
+static const unsigned int pins_dvdd[] = {
+	D(7, 0), D(7, 1), D(7, 2), D(7, 3), D(7, 4), D(7, 5), D(7, 6), D(7, 7),
+	D(8, 0), D(8, 1), D(8, 2), D(8, 3), D(8, 4), D(8, 5)
+};
+static const struct sppctlgrp_t sp7021grps_dvdd[] = {
+	EGRP("DVD_DSP_DEBUG", 1, pins_dvdd)
+};
+
+static const unsigned int pins_i2cd[] = { D(1, 0), D(1, 1) };
+static const struct sppctlgrp_t sp7021grps_i2cd[] = {
+	EGRP("I2C_DEBUG", 1, pins_i2cd)
+};
+
+static const unsigned int pins_i2cs[] = { D(0, 0), D(0, 1) };
+static const struct sppctlgrp_t sp7021grps_i2cs[] = {
+	EGRP("I2C_SLAVE", 1, pins_i2cs)
+};
+
+static const unsigned int pins_wakp[] = { D(10, 5) };
+static const struct sppctlgrp_t sp7021grps_wakp[] = {
+	EGRP("WAKEUP", 1, pins_wakp)
+};
+
+static const unsigned int pins_u2ax[] = { D(2, 0), D(2, 1), D(3, 0), D(3, 1) };
+static const struct sppctlgrp_t sp7021grps_u2ax[] = {
+	EGRP("UART2AXI", 1, pins_u2ax)
+};
+
+static const unsigned int pins_u0ic[] = {
+	D(0, 0), D(0, 1), D(0, 4), D(0, 5), D(1, 0), D(1, 1)
+};
+static const struct sppctlgrp_t sp7021grps_u0ic[] = {
+	EGRP("USB0_I2C", 1, pins_u0ic)
+};
+
+static const unsigned int pins_u1ic[] = {
+	D(0, 2), D(0, 3), D(0, 6), D(0, 7), D(1, 2), D(1, 3)
+};
+static const struct sppctlgrp_t sp7021grps_u1ic[] = {
+	EGRP("USB1_I2C", 1, pins_u1ic)
+};
+
+static const unsigned int pins_u0ot[] = { D(11, 2) };
+static const struct sppctlgrp_t sp7021grps_u0ot[] = {
+	EGRP("USB0_OTG", 1, pins_u0ot)
+};
+
+static const unsigned int pins_u1ot[] = { D(11, 3) };
+static const struct sppctlgrp_t sp7021grps_u1ot[] = {
+	EGRP("USB1_OTG", 1, pins_u1ot)
+};
+
+static const unsigned int pins_uphd[] = {
+	D(0, 1), D(0, 2), D(0, 3), D(7, 4), D(7, 5), D(7, 6),
+	D(7, 7), D(8, 0), D(8, 1), D(8, 2), D(8, 3),
+	D(9, 7), D(10, 2), D(10, 3), D(10, 4)
+};
+static const struct sppctlgrp_t sp7021grps_up0d[] = {
+	EGRP("UPHY0_DEBUG", 1, pins_uphd)
+};
+static const struct sppctlgrp_t sp7021grps_up1d[] = {
+	EGRP("UPHY1_DEBUG", 1, pins_uphd)
+};
+
+static const unsigned int pins_upex[] = {
+	D(0, 0), D(0, 1), D(0, 2), D(0, 3), D(0, 4), D(0, 5), D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6), D(1, 7),
+	D(2, 0), D(2, 1), D(2, 2), D(2, 3), D(2, 4), D(2, 5), D(2, 6), D(2, 7),
+	D(3, 0), D(3, 1), D(3, 2), D(3, 3), D(3, 4), D(3, 5), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5), D(4, 6), D(4, 7),
+	D(5, 0), D(5, 1), D(5, 2), D(5, 3), D(5, 4), D(5, 5), D(5, 6), D(5, 7),
+	D(6, 0), D(6, 1), D(6, 2), D(6, 3), D(6, 4), D(6, 5), D(6, 6), D(6, 7),
+	D(7, 0), D(7, 1), D(7, 2), D(7, 3), D(7, 4), D(7, 5), D(7, 6), D(7, 7),
+	D(8, 0), D(8, 1), D(8, 2), D(8, 3), D(8, 4), D(8, 5), D(8, 6), D(8, 7),
+	D(9, 0), D(9, 1), D(9, 2), D(9, 3), D(9, 4), D(9, 5), D(9, 6), D(9, 7),
+	D(10, 0), D(10, 1), D(10, 2), D(10, 3), D(10, 4), D(10, 5), D(10, 6), D(10, 7)
+};
+static const struct sppctlgrp_t sp7021grps_upex[] = {
+	EGRP("UPHY0_EXT", 1, pins_upex)
+};
+
+static const unsigned int pins_prp1[] = {
+	D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6), D(1, 7),
+	D(2, 1), D(2, 2), D(2, 3), D(2, 4), D(2, 5), D(2, 6), D(2, 7),
+	D(3, 0), D(3, 1), D(3, 2)
+};
+static const unsigned int pins_prp2[] = {
+	D(3, 4), D(3, 6), D(3, 7),
+	D(4, 0), D(4, 1), D(4, 2), D(4, 3), D(4, 4), D(4, 5), D(4, 6), D(4, 7),
+	D(5, 0), D(5, 1), D(5, 2), D(5, 3), D(5, 4), D(5, 5), D(5, 6), D(5, 7),
+	D(6, 4)
+};
+static const struct sppctlgrp_t sp7021grps_prbp[] = {
+	EGRP("PROBE_PORT1", 1, pins_prp1),
+	EGRP("PROBE_PORT2", 2, pins_prp2)
+};
+
+static const unsigned int pins_anai[] = { D(0, 4), D(0, 5) };
+static const struct sppctlgrp_t sp7021grps_anai[] = {
+	EGRP("ANA_I2C_IF", 1, pins_anai),
+};
+
+static const unsigned int pins_anat[] = {
+	D(0, 0), D(0, 1), D(0, 2), D(0, 3), D(0, 4), D(0, 5), D(0, 6), D(0, 7),
+	D(1, 0), D(1, 1), D(1, 2), D(1, 3), D(1, 4), D(1, 5), D(1, 6),
+	D(11, 0)
+};
+static const struct sppctlgrp_t sp7021grps_anat[] = {
+	EGRP("ANA_TEST_IF", 1, pins_anat)
+};
+
+struct func_t list_funcs[] = {
+	FNCN("GPIO",            fOFF_0, 0x00, 0, 0),
+	FNCN("IOP",             fOFF_0, 0x00, 0, 0),
+
+	FNCN("L2SW_CLK_OUT",        fOFF_M, 0x00, 0, 7),
+	FNCN("L2SW_MAC_SMI_MDC",    fOFF_M, 0x00, 8, 7),
+	FNCN("L2SW_LED_FLASH0",     fOFF_M, 0x01, 0, 7),
+	FNCN("L2SW_LED_FLASH1",     fOFF_M, 0x01, 8, 7),
+	FNCN("L2SW_LED_ON0",        fOFF_M, 0x02, 0, 7),
+	FNCN("L2SW_LED_ON1",        fOFF_M, 0x02, 8, 7),
+	FNCN("L2SW_MAC_SMI_MDIO",   fOFF_M, 0x03, 0, 7),
+	FNCN("L2SW_P0_MAC_RMII_TXEN",   fOFF_M, 0x03, 8, 7),
+	FNCN("L2SW_P0_MAC_RMII_TXD0",   fOFF_M, 0x04, 0, 7),
+	FNCN("L2SW_P0_MAC_RMII_TXD1",   fOFF_M, 0x04, 8, 7),
+	FNCN("L2SW_P0_MAC_RMII_CRSDV",  fOFF_M, 0x05, 0, 7),
+	FNCN("L2SW_P0_MAC_RMII_RXD0",   fOFF_M, 0x05, 8, 7),
+	FNCN("L2SW_P0_MAC_RMII_RXD1",   fOFF_M, 0x06, 0, 7),
+	FNCN("L2SW_P0_MAC_RMII_RXER",   fOFF_M, 0x06, 8, 7),
+	FNCN("L2SW_P1_MAC_RMII_TXEN",   fOFF_M, 0x07, 0, 7),
+	FNCN("L2SW_P1_MAC_RMII_TXD0",   fOFF_M, 0x07, 8, 7),
+	FNCN("L2SW_P1_MAC_RMII_TXD1",   fOFF_M, 0x08, 0, 7),
+	FNCN("L2SW_P1_MAC_RMII_CRSDV",  fOFF_M, 0x08, 8, 7),
+	FNCN("L2SW_P1_MAC_RMII_RXD0",   fOFF_M, 0x09, 0, 7),
+	FNCN("L2SW_P1_MAC_RMII_RXD1",   fOFF_M, 0x09, 8, 7),
+	FNCN("L2SW_P1_MAC_RMII_RXER",   fOFF_M, 0x0A, 0, 7),
+	FNCN("DAISY_MODE",      fOFF_M, 0x0A, 8, 7),    // mux has no effect now
+	FNCN("SDIO_CLK",        fOFF_M, 0x0B, 0, 7),
+	FNCN("SDIO_CMD",        fOFF_M, 0x0B, 8, 7),
+	FNCN("SDIO_D0",         fOFF_M, 0x0C, 0, 7),
+	FNCN("SDIO_D1",         fOFF_M, 0x0C, 8, 7),
+	FNCN("SDIO_D2",         fOFF_M, 0x0D, 0, 7),
+	FNCN("SDIO_D3",         fOFF_M, 0x0D, 8, 7),
+	FNCN("PWM0",            fOFF_M, 0x0E, 0, 7),
+	FNCN("PWM1",            fOFF_M, 0x0E, 8, 7),
+	FNCN("PWM2",            fOFF_M, 0x0F, 0, 7),
+	FNCN("PWM3",            fOFF_M, 0x0F, 8, 7),
+
+	FNCN("PWM4",            fOFF_M, 0x10, 0, 7),
+	FNCN("PWM5",            fOFF_M, 0x10, 8, 7),
+	FNCN("PWM6",            fOFF_M, 0x11, 0, 7),
+	FNCN("PWM7",            fOFF_M, 0x11, 8, 7),
+	FNCN("ICM0_D",          fOFF_M, 0x12, 0, 7),    // 4x Input captures
+	FNCN("ICM1_D",          fOFF_M, 0x12, 8, 7),
+	FNCN("ICM2_D",          fOFF_M, 0x13, 0, 7),
+	FNCN("ICM3_D",          fOFF_M, 0x13, 8, 7),
+	FNCN("ICM0_CLK",        fOFF_M, 0x14, 0, 7),
+	FNCN("ICM1_CLK",        fOFF_M, 0x14, 8, 7),
+	FNCN("ICM2_CLK",        fOFF_M, 0x15, 0, 7),
+	FNCN("ICM3_CLK",        fOFF_M, 0x15, 8, 7),
+	FNCN("SPIM0_INT",       fOFF_M, 0x16, 0, 7),    // 4x SPI masters
+	FNCN("SPIM0_CLK",       fOFF_M, 0x16, 8, 7),
+	FNCN("SPIM0_EN",        fOFF_M, 0x17, 0, 7),
+	FNCN("SPIM0_DO",        fOFF_M, 0x17, 8, 7),
+	FNCN("SPIM0_DI",        fOFF_M, 0x18, 0, 7),
+	FNCN("SPIM1_INT",       fOFF_M, 0x18, 8, 7),
+	FNCN("SPIM1_CLK",       fOFF_M, 0x19, 0, 7),
+	FNCN("SPIM1_EN",        fOFF_M, 0x19, 8, 7),
+	FNCN("SPIM1_DO",        fOFF_M, 0x1A, 0, 7),
+	FNCN("SPIM1_DI",        fOFF_M, 0x1A, 8, 7),
+	FNCN("SPIM2_INT",       fOFF_M, 0x1B, 0, 7),
+	FNCN("SPIM2_CLK",       fOFF_M, 0x1B, 8, 7),
+	FNCN("SPIM2_EN",        fOFF_M, 0x1C, 0, 7),
+	FNCN("SPIM2_DO",        fOFF_M, 0x1C, 8, 7),
+	FNCN("SPIM2_DI",        fOFF_M, 0x1D, 0, 7),
+	FNCN("SPIM3_INT",       fOFF_M, 0x1D, 8, 7),
+	FNCN("SPIM3_CLK",       fOFF_M, 0x1E, 0, 7),
+	FNCN("SPIM3_EN",        fOFF_M, 0x1E, 8, 7),
+	FNCN("SPIM3_DO",        fOFF_M, 0x1F, 0, 7),
+	FNCN("SPIM3_DI",        fOFF_M, 0x1F, 8, 7),
+
+	FNCN("SPI0S_INT",       fOFF_M, 0x20, 0, 7),    // 4x SPI slaves
+	FNCN("SPI0S_CLK",       fOFF_M, 0x20, 8, 7),
+	FNCN("SPI0S_EN",        fOFF_M, 0x21, 0, 7),
+	FNCN("SPI0S_DO",        fOFF_M, 0x21, 8, 7),
+	FNCN("SPI0S_DI",        fOFF_M, 0x22, 0, 7),
+	FNCN("SPI1S_INT",       fOFF_M, 0x22, 8, 7),
+	FNCN("SPI1S_CLK",       fOFF_M, 0x23, 0, 7),
+	FNCN("SPI1S_EN",        fOFF_M, 0x23, 8, 7),
+	FNCN("SPI1S_DO",        fOFF_M, 0x24, 0, 7),
+	FNCN("SPI1S_DI",        fOFF_M, 0x24, 8, 7),
+	FNCN("SPI2S_INT",       fOFF_M, 0x25, 0, 7),
+	FNCN("SPI2S_CLK",       fOFF_M, 0x25, 8, 7),
+	FNCN("SPI2S_EN",        fOFF_M, 0x26, 0, 7),
+	FNCN("SPI2S_DO",        fOFF_M, 0x26, 8, 7),
+	FNCN("SPI2S_DI",        fOFF_M, 0x27, 0, 7),
+	FNCN("SPI3S_INT",       fOFF_M, 0x27, 8, 7),
+	FNCN("SPI3S_CLK",       fOFF_M, 0x28, 0, 7),
+	FNCN("SPI3S_EN",        fOFF_M, 0x28, 8, 7),
+	FNCN("SPI3S_DO",        fOFF_M, 0x29, 0, 7),
+	FNCN("SPI3S_DI",        fOFF_M, 0x29, 8, 7),
+	FNCN("I2CM0_CLK",       fOFF_M, 0x2A, 0, 7),    // 4x I2C masters
+	FNCN("I2CM0_DAT",       fOFF_M, 0x2A, 8, 7),
+	FNCN("I2CM1_CLK",       fOFF_M, 0x2B, 0, 7),
+	FNCN("I2CM1_DAT",       fOFF_M, 0x2B, 8, 7),
+	FNCN("I2CM2_CLK",       fOFF_M, 0x2C, 0, 7),
+	FNCN("I2CM2_DAT",       fOFF_M, 0x2C, 8, 7),
+	FNCN("I2CM3_CLK",       fOFF_M, 0x2D, 0, 7),
+	FNCN("I2CM3_DAT",       fOFF_M, 0x2D, 8, 7),
+	FNCN("UA1_TX",          fOFF_M, 0x2E, 0, 7),    // +4x muxable UARTS
+	FNCN("UA1_RX",          fOFF_M, 0x2E, 8, 7),
+	FNCN("UA1_CTS",         fOFF_M, 0x2F, 0, 7),
+	FNCN("UA1_RTS",         fOFF_M, 0x2F, 8, 7),
+
+	FNCN("UA2_TX",          fOFF_M, 0x30, 0, 7),
+	FNCN("UA2_RX",          fOFF_M, 0x30, 8, 7),
+	FNCN("UA2_CTS",         fOFF_M, 0x31, 0, 7),
+	FNCN("UA2_RTS",         fOFF_M, 0x31, 8, 7),
+	FNCN("UA3_TX",          fOFF_M, 0x32, 0, 7),
+	FNCN("UA3_RX",          fOFF_M, 0x32, 8, 7),
+	FNCN("UA3_CTS",         fOFF_M, 0x33, 0, 7),
+	FNCN("UA3_RTS",         fOFF_M, 0x33, 8, 7),
+	FNCN("UA4_TX",          fOFF_M, 0x34, 0, 7),
+	FNCN("UA4_RX",          fOFF_M, 0x34, 8, 7),
+	FNCN("UA4_CTS",         fOFF_M, 0x35, 0, 7),
+	FNCN("UA4_RTS",         fOFF_M, 0x35, 8, 7),
+	FNCN("TIMER0_INT",      fOFF_M, 0x36, 0, 7),    // 4x timers interrupts
+	FNCN("TIMER1_INT",      fOFF_M, 0x36, 8, 7),
+	FNCN("TIMER2_INT",      fOFF_M, 0x37, 0, 7),
+	FNCN("TIMER3_INT",      fOFF_M, 0x37, 8, 7),
+	FNCN("GPIO_INT0",       fOFF_M, 0x38, 0, 7),    // 8x GPIO interrupts
+	FNCN("GPIO_INT1",       fOFF_M, 0x38, 8, 7),
+	FNCN("GPIO_INT2",       fOFF_M, 0x39, 0, 7),
+	FNCN("GPIO_INT3",       fOFF_M, 0x39, 8, 7),
+	FNCN("GPIO_INT4",       fOFF_M, 0x3A, 0, 7),
+	FNCN("GPIO_INT5",       fOFF_M, 0x3A, 8, 7),
+	FNCN("GPIO_INT6",       fOFF_M, 0x3B, 0, 7),
+	FNCN("GPIO_INT7",       fOFF_M, 0x3B, 8, 7),
+	// offset from 0x9C000080
+	FNCE("SPI_FLASH",       fOFF_G, 0x01,  0, 2, sp7021grps_spif),
+	FNCE("SPI_FLASH_4BIT",  fOFF_G, 0x01,  2, 2, sp7021grps_spi4),
+	FNCE("SPI_NAND",        fOFF_G, 0x01,  4, 1, sp7021grps_snan),
+	FNCE("CARD0_EMMC",      fOFF_G, 0x01,  5, 1, sp7021grps_emmc),
+	FNCE("SD_CARD",         fOFF_G, 0x01,  6, 1, sp7021grps_sdsd),
+	FNCE("UA0",             fOFF_G, 0x01,  7, 1, sp7021grps_uar0),
+	FNCE("ACHIP_DEBUG",     fOFF_G, 0x01,  8, 2, sp7021grps_adbg),
+	FNCE("ACHIP_UA2AXI",    fOFF_G, 0x01, 10, 2, sp7021grps_au2x),
+	FNCE("FPGA_IFX",        fOFF_G, 0x01, 12, 1, sp7021grps_fpga),
+	FNCE("HDMI_TX",         fOFF_G, 0x01, 13, 2, sp7021grps_hdmi),
+
+	FNCE("AUD_EXT_ADC_IFX0", fOFF_G, 0x01, 15, 1, sp7021grps_eadc), // I2S audio in
+	FNCE("AUD_EXT_DAC_IFX0", fOFF_G, 0x02,  0, 1, sp7021grps_edac), // I2S audio out
+	FNCE("SPDIF_RX",        fOFF_G, 0x02,  2, 1, sp7021grps_spdi),
+	FNCE("SPDIF_TX",        fOFF_G, 0x02,  3, 1, sp7021grps_spdo),
+	FNCE("TDMTX_IFX0",      fOFF_G, 0x02,  4, 1, sp7021grps_tdmt),
+	FNCE("TDMRX_IFX0",      fOFF_G, 0x02,  5, 1, sp7021grps_tdmr),
+	FNCE("PDMRX_IFX0",      fOFF_G, 0x02,  6, 1, sp7021grps_pdmr),
+	FNCE("PCM_IEC_TX",      fOFF_G, 0x02,  7, 1, sp7021grps_pcmt),
+	FNCE("LCDIF",           fOFF_G, 0x04,  6, 1, sp7021grps_lcdi),
+	FNCE("DVD_DSP_DEBUG",   fOFF_G, 0x02,  8, 1, sp7021grps_dvdd),
+	FNCE("I2C_DEBUG",       fOFF_G, 0x02,  9, 1, sp7021grps_i2cd),
+	FNCE("I2C_SLAVE",       fOFF_G, 0x02, 10, 1, sp7021grps_i2cs), // I2C slave
+	FNCE("WAKEUP",          fOFF_G, 0x02, 11, 1, sp7021grps_wakp),
+	FNCE("UART2AXI",        fOFF_G, 0x02, 12, 2, sp7021grps_u2ax),
+	FNCE("USB0_I2C",        fOFF_G, 0x02, 14, 2, sp7021grps_u0ic),
+	FNCE("USB1_I2C",        fOFF_G, 0x03,  0, 2, sp7021grps_u1ic),
+	FNCE("USB0_OTG",        fOFF_G, 0x03,  2, 1, sp7021grps_u0ot),
+	FNCE("USB1_OTG",        fOFF_G, 0x03,  3, 1, sp7021grps_u1ot),
+	FNCE("UPHY0_DEBUG",     fOFF_G, 0x03,  4, 1, sp7021grps_up0d),
+	FNCE("UPHY1_DEBUG",     fOFF_G, 0x03,  5, 1, sp7021grps_up1d),
+	FNCE("UPHY0_EXT",       fOFF_G, 0x03,  6, 1, sp7021grps_upex),
+	FNCE("PROBE_PORT",      fOFF_G, 0x03,  7, 2, sp7021grps_prbp),
+	FNCE("ANA_I2C_IF",      fOFF_G, 0x03,  7, 2, sp7021grps_anai),
+	FNCE("ANA_TEST_IF",     fOFF_G, 0x03,  7, 2, sp7021grps_anat)
+};
+
+const size_t list_funcsSZ = ARRAY_SIZE(list_funcs);
diff --git a/drivers/pinctrl/sunplus/sppctl.c b/drivers/pinctrl/sunplus/sppctl.c
new file mode 100644
index 0000000..ca135d0
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/io.h>
+
+#include "sppctl.h"
+#include "../core.h"
+
+
+void print_device_tree_node(struct device_node *node, int depth)
+{
+	int i = 0;
+	struct device_node *child;
+	struct property    *properties;
+	char                indent[255] = "";
+
+	for (i = 0; i < depth * 3; i++)
+		indent[i] = ' ';
+	indent[i] = '\0';
+
+	++depth;
+	if (depth == 1) {
+		pr_info("%s{ name = %s\n", indent, node->name);
+		for (properties = node->properties; properties != NULL;
+			properties = properties->next)
+			pr_info("%s  %s (%d)\n", indent, properties->name, properties->length);
+		pr_info("%s}\n", indent);
+	}
+
+	for_each_child_of_node(node, child) {
+		pr_info("%s{ name = %s\n", indent, child->name);
+		for (properties = child->properties; properties != NULL;
+			properties = properties->next)
+			pr_info("%s  %s (%d)\n", indent, properties->name, properties->length);
+		print_device_tree_node(child, depth);
+		pr_info("%s}\n", indent);
+	}
+}
+
+void sppctl_gmx_set(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff, uint8_t _bsiz,
+		    uint8_t _rval)
+{
+	uint32_t *r;
+	struct sppctl_reg_t x = { .m = (~(~0 << _bsiz)) << _boff,
+				  .v = ((uint16_t)_rval) << _boff };
+
+	if (_p->debug > 1)
+		KDBG(_p->pcdp->dev, "%s(x%X,x%X,x%X,x%X) m:x%X v:x%X\n",
+		     __func__, _roff, _boff, _bsiz, _rval, x.m, x.v);
+	r = (uint32_t *)&x;
+	writel(*r, _p->baseI + (_roff << 2));
+}
+
+uint8_t sppctl_gmx_get(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff, uint8_t _bsiz)
+{
+	uint8_t rval;
+	struct sppctl_reg_t *x;
+	uint32_t r = readl(_p->baseI + (_roff << 2));
+
+	x = (struct sppctl_reg_t *)&r;
+	rval = (x->v >> _boff) & (~(~0 << _bsiz));
+
+	if (_p->debug > 1)
+		KDBG(_p->pcdp->dev, "%s(x%X,x%X,x%X) v:x%X rval:x%X\n",
+		     __func__, _roff, _boff, _bsiz, x->v, rval);
+
+	return rval;
+}
+
+void sppctl_pin_set(struct sppctl_pdata_t *_p, uint8_t _pin, uint8_t _fun)
+{
+	uint32_t *r;
+	struct sppctl_reg_t x = { .m = 0x007F, .v = (uint16_t)_pin };
+	uint8_t func = (_fun >> 1) << 2;
+
+	if (_fun % 2 == 0)
+		;
+	else {
+		x.v <<= 8;
+		x.m <<= 8;
+	}
+
+	if (_p->debug > 1)
+		KDBG(_p->pcdp->dev, "%s(x%X,x%X) off:x%X m:x%X v:x%X\n",
+		     __func__, _pin, _fun, func, x.m, x.v);
+
+	r = (uint32_t *)&x;
+	writel(*r, _p->baseF + func);
+}
+
+uint8_t sppctl_fun_get(struct sppctl_pdata_t *_p,  uint8_t _fun)
+{
+	uint8_t pin = 0x00;
+	uint8_t func = (_fun >> 1) << 2;
+	struct sppctl_reg_t *x;
+	uint32_t r = readl(_p->baseF + func);
+
+	x = (struct sppctl_reg_t *)&r;
+	if (_fun % 2 == 0)
+		pin = x->v & 0x00FF;
+	else
+		pin = x->v >> 8;
+
+	if (_p->debug > 1)
+		KDBG(_p->pcdp->dev, "%s(x%X) off:x%X m:x%X v:x%X pin:x%X\n",
+		     __func__, _fun, func, x->m, x->v, pin);
+
+	return pin;
+}
+
+static void sppctl_fwload_cb(const struct firmware *_fw, void *_ctx)
+{
+	int i = -1, j = 0;
+	struct sppctl_pdata_t *p = (struct sppctl_pdata_t *)_ctx;
+
+	if (!_fw) {
+		KERR(p->pcdp->dev, "Firmware not found\n");
+		return;
+	}
+	if (_fw->size < list_funcsSZ-2) {
+		KERR(p->pcdp->dev, " fw size %zd < %zd\n", _fw->size, list_funcsSZ);
+		goto out;
+	}
+
+	for (i = 0; i < list_funcsSZ && i < _fw->size; i++) {
+		if (list_funcs[i].freg != fOFF_M)
+			continue;
+		sppctl_pin_set(p, _fw->data[i], i);
+		j++;
+	}
+
+out:
+	release_firmware(_fw);
+}
+
+void sppctl_loadfw(struct device *_dev, const char *_fwname)
+{
+	int ret;
+	struct sppctl_pdata_t *p = (struct sppctl_pdata_t *)_dev->platform_data;
+
+	if (!_fwname)
+		return;
+	if (strlen(_fwname) < 1)
+		return;
+	KINF(_dev, "fw:%s", _fwname);
+
+	ret = request_firmware_nowait(THIS_MODULE, true, _fwname, _dev, GFP_KERNEL, p,
+				      sppctl_fwload_cb);
+	if (ret)
+		KERR(_dev, "Can't load '%s'\n", _fwname);
+}
+
+int sppctl_pctl_resmap(struct platform_device *_pd, struct sppctl_pdata_t *_pc)
+{
+	struct resource *rp;
+
+	// resF
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 0);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#F ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #F:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->baseF = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->baseF)) {
+		KERR(&(_pd->dev), "%s map res#F ERR\n", __func__);
+		return PTR_ERR(_pc->baseF);
+	}
+
+	// res0
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 1);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#0 ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #0:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->base0 = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->base0)) {
+		KERR(&(_pd->dev), "%s map res#0 ERR\n", __func__);
+		return PTR_ERR(_pc->base0);
+	}
+
+	// res1
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 2);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#1 ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #1:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->base1 = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->base1)) {
+		KERR(&(_pd->dev), "%s map res#1 ERR\n", __func__);
+		return PTR_ERR(_pc->base1);
+	}
+
+	// res2
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 3);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#2 ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #2:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->base2 = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->base2)) {
+		KERR(&(_pd->dev), "%s map res#2 ERR\n", __func__);
+		return PTR_ERR(_pc->base2);
+	}
+
+	// iop
+	rp = platform_get_resource(_pd, IORESOURCE_MEM, 4);
+	if (IS_ERR(rp)) {
+		KERR(&(_pd->dev), "%s get res#I ERR\n", __func__);
+		return PTR_ERR(rp);
+	}
+	KDBG(&(_pd->dev), "mres #I:%p\n", rp);
+	if (!rp)
+		return -EFAULT;
+	KDBG(&(_pd->dev), "mapping [%pa-%pa]\n", &rp->start, &rp->end);
+
+	_pc->baseI = devm_ioremap_resource(&(_pd->dev), rp);
+	if (IS_ERR(_pc->baseI)) {
+		KERR(&(_pd->dev), "%s map res#I ERR\n", __func__);
+		return PTR_ERR(_pc->baseI);
+	}
+
+	return 0;
+}
+
+static int sppctl_dnew(struct platform_device *_pd)
+{
+	int ret = -ENODEV;
+	struct device_node *np = _pd->dev.of_node;
+	struct sppctl_pdata_t *p = NULL;
+	const char *fwfname = FW_DEFNAME;
+
+	if (!np) {
+		KERR(&(_pd->dev), "Invalid dtb node\n");
+		return -EINVAL;
+	}
+	if (!of_device_is_available(np)) {
+		KERR(&(_pd->dev), "dtb is not available\n");
+		return -ENODEV;
+	}
+
+	// print_device_tree_node(np, 0);
+
+	p = devm_kzalloc(&(_pd->dev), sizeof(*p), GFP_KERNEL);
+	if (!p)
+		return -ENOMEM;
+	memset(p->name, 0, SPPCTL_MAX_NAM);
+	if (np)
+		strcpy(p->name, np->name);
+	else
+		strcpy(p->name, MNAME);
+	dev_set_name(&(_pd->dev), "%s", p->name);
+
+	ret = sppctl_pctl_resmap(_pd, p);
+	if (ret != 0)
+		return ret;
+
+	// set gpio_chip
+	_pd->dev.platform_data = p;
+	sppctl_sysfs_init(_pd);
+	of_property_read_string(np, "fwname", &fwfname);
+	if (fwfname)
+		strcpy(p->fwname, fwfname);
+	sppctl_loadfw(&(_pd->dev), p->fwname);
+
+	ret = sppctl_gpio_new(_pd, p);
+	if (ret != 0)
+		return ret;
+
+	ret = sppctl_pinctrl_init(_pd);
+	if (ret != 0)
+		return ret;
+
+	pinctrl_add_gpio_range(p->pcdp, &(p->gpio_range));
+	pr_info(M_NAM " by " M_ORG "" M_CPR);
+
+	return 0;
+}
+
+static int sppctl_ddel(struct platform_device *_pd)
+{
+	struct sppctl_pdata_t *p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+
+	sppctl_gpio_del(_pd, p);
+	sppctl_sysfs_clean(_pd);
+	sppctl_pinctrl_clea(_pd);
+	return 0;
+}
+
+static const struct of_device_id sppctl_dt_ids[] = {
+	{ .compatible = "sunplus,sp7021-pctl" },
+	{ /* zero */ }
+};
+
+MODULE_DEVICE_TABLE(of, sppctl_dt_ids);
+MODULE_ALIAS("platform:" MNAME);
+
+static struct platform_driver sppctl_driver = {
+	.driver = {
+		.name           = MNAME,
+		.owner          = THIS_MODULE,
+		.of_match_table = of_match_ptr(sppctl_dt_ids),
+	},
+	.probe  = sppctl_dnew,
+	.remove = sppctl_ddel,
+};
+
+static int __init sppctl_drv_reg(void)
+{
+	return platform_driver_register(&sppctl_driver);
+}
+postcore_initcall(sppctl_drv_reg);
+
+static void __exit sppctl_drv_exit(void)
+{
+	platform_driver_unregister(&sppctl_driver);
+}
+module_exit(sppctl_drv_exit);
+
+MODULE_AUTHOR(M_AUT1);
+MODULE_AUTHOR(M_AUT2);
+MODULE_DESCRIPTION(M_NAM);
+MODULE_LICENSE(M_LIC);
diff --git a/drivers/pinctrl/sunplus/sppctl.h b/drivers/pinctrl/sunplus/sppctl.h
new file mode 100644
index 0000000..c64a619
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl.h
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#ifndef SPPCTL_H
+#define SPPCTL_H
+
+#define MNAME "sppctl"
+#define M_LIC "GPL v2"
+#define M_AUT1 "Dvorkin Dmitry <dvorkin@tibbo.com>"
+#define M_AUT2 "Wells Lu <wells.lu@sunplus.com>"
+#define M_NAM "SP7021 PinCtl"
+#define M_ORG "Sunplus/Tibbo Tech."
+#define M_CPR "(C) 2020"
+
+#define FW_DEFNAME NULL
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/sysfs.h>
+#include <linux/printk.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <dt-bindings/pinctrl/sppctl-sp7021.h>
+
+#define SPPCTL_MAX_NAM 64
+#define SPPCTL_MAX_BUF PAGE_SIZE
+
+#define KINF(pd, fmt, args...) \
+	do { \
+		if ((pd) != NULL) \
+			dev_info((pd), fmt, ##args); \
+		else \
+			pr_info(MNAME ": " fmt, ##args); \
+	} while (0)
+#define KERR(pd, fmt, args...) \
+	do { \
+		if ((pd) != NULL) \
+			dev_info((pd), fmt, ##args); \
+		else \
+			pr_err(MNAME ": " fmt, ##args); \
+	} while (0)
+#ifdef CONFIG_PINCTRL_SPPCTL_DEBUG
+#define KDBG(pd, fmt, args...) \
+	do { \
+		if ((pd) != NULL) \
+			dev_info((pd), fmt, ##args); \
+		else \
+			pr_debug(MNAME ": " fmt, ##args); \
+	} while (0)
+#else
+#define KDBG(pd, fmt, args...)
+#endif
+
+#include "sppctl_gpio.h"
+
+struct sppctl_pdata_t {
+	char name[SPPCTL_MAX_NAM];
+	uint8_t debug;
+	char fwname[SPPCTL_MAX_NAM];
+	void *sysfs_sdp;
+	void __iomem *baseF;    // functions
+	void __iomem *base0;    // MASTER , OE , OUT , IN
+	void __iomem *base1;    // I_INV , O_INV , OD
+	void __iomem *base2;    // GPIO_FIRST
+	void __iomem *baseI;    // IOP
+	// pinctrl-related
+	struct pinctrl_desc pdesc;
+	struct pinctrl_dev *pcdp;
+	struct pinctrl_gpio_range gpio_range;
+	struct sppctlgpio_chip_t *gpiod;
+};
+
+struct sppctl_reg_t {
+	uint16_t v;     // value part
+	uint16_t m;     // mask part
+};
+
+#include "sppctl_sysfs.h"
+#include "sppctl_pinctrl.h"
+
+void sppctl_gmx_set(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff,
+		    uint8_t _bsiz, uint8_t _rval);
+uint8_t sppctl_gmx_get(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff,
+		       uint8_t _bsiz);
+void sppctl_pin_set(struct sppctl_pdata_t *_p, uint8_t _pin, uint8_t _fun);
+uint8_t sppctl_fun_get(struct sppctl_pdata_t *_p, uint8_t _pin);
+void sppctl_loadfw(struct device *_dev, const char *_fwname);
+
+enum fOFF_t {
+	fOFF_0, // nowhere
+	fOFF_M, // in mux registers
+	fOFF_G, // mux group registers
+	fOFF_I, // in iop registers
+};
+
+struct sppctlgrp_t {
+	const char * const name;
+	const uint8_t gval;             // value for register
+	const unsigned * const pins;    // list of pins
+	const unsigned int pnum;        // number of pins
+};
+
+#define EGRP(n, v, p) { \
+	.name = n, \
+	.gval = (v), \
+	.pins = (p), \
+	.pnum = ARRAY_SIZE(p), \
+}
+
+struct func_t {
+	const char * const name;
+	const enum fOFF_t freg;     // function register type
+	const uint8_t roff;         // register offset
+	const uint8_t boff;         // bit offset
+	const uint8_t blen;         // number of bits
+	const struct sppctlgrp_t * const grps; // list of groups
+	const unsigned int gnum;    // number of groups
+	const char *grps_sa[5];     // array of pointers to func's grps names
+};
+
+#define FNCE(n, r, o, bo, bl, g) { \
+	.name = n, \
+	.freg = r, \
+	.roff = o, \
+	.boff = bo, \
+	.blen = bl, \
+	.grps = (g), \
+	.gnum = ARRAY_SIZE(g), \
+}
+
+#define FNCN(n, r, o, bo, bl) { \
+	.name = n, \
+	.freg = r, \
+	.roff = o, \
+	.boff = bo, \
+	.blen = bl, \
+	.grps = NULL, \
+	.gnum = 0, \
+}
+extern struct func_t list_funcs[];
+extern const size_t list_funcsSZ;
+
+extern const char * const sppctlpmux_list_s[];
+extern const size_t PMUX_listSZ;
+
+struct grp2fp_map_t {
+	uint16_t f_idx; // function index
+	uint16_t g_idx; // pins/group index inside function
+};
+
+// for debug
+void print_device_tree_node(struct device_node *node, int depth);
+
+#endif // SPPCTL_H
diff --git a/drivers/pinctrl/sunplus/sppctl_gpio.c b/drivers/pinctrl/sunplus/sppctl_gpio.c
new file mode 100644
index 0000000..31d11d6
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_gpio.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/seq_file.h>
+#include <linux/io.h>
+
+#include "sppctl_gpio_ops.h"
+#include "sppctl_gpio.h"
+
+__attribute((unused))
+static irqreturn_t gpio_int_0(int irq, void *data)
+{
+	pr_info("register gpio int0 trigger\n");
+	return IRQ_HANDLED;
+}
+
+int sppctl_gpio_new(struct platform_device *_pd, void *_datap)
+{
+	struct device_node *np = _pd->dev.of_node, *npi;
+	struct sppctlgpio_chip_t *pc = NULL;
+	struct gpio_chip *gchip = NULL;
+	int err = 0, i = 0, npins;
+	struct sppctl_pdata_t *_pctrlp = (struct sppctl_pdata_t *)_datap;
+
+	if (!np) {
+		KERR(&(_pd->dev), "invalid devicetree node\n");
+		return -EINVAL;
+	}
+
+	if (!of_device_is_available(np)) {
+		KERR(&(_pd->dev), "devicetree status is not available\n");
+		return -ENODEV;
+	}
+
+	// print_device_tree_node(np, 0);
+	for_each_child_of_node(np, npi) {
+		if (of_find_property(npi, "gpio-controller", NULL)) {
+			i = 1;
+			break;
+		}
+	}
+
+	if (of_find_property(np, "gpio-controller", NULL))
+		i = 1;
+	if (i == 0) {
+		KERR(&(_pd->dev), "is not gpio-controller\n");
+		return -ENODEV;
+	}
+
+	pc = devm_kzalloc(&(_pd->dev), sizeof(*pc), GFP_KERNEL);
+	if (!pc)
+		return -ENOMEM;
+	gchip = &(pc->chip);
+
+	pc->base0 = _pctrlp->base0;
+	pc->base1 = _pctrlp->base1;
+	pc->base2 = _pctrlp->base2;
+	_pctrlp->gpiod = pc;
+
+	gchip->label =             MNAME;
+	gchip->parent =            &(_pd->dev);
+	gchip->owner =             THIS_MODULE;
+	gchip->request =           gpiochip_generic_request; // place new calls there
+	gchip->free =              gpiochip_generic_free;
+	gchip->get_direction =     sppctlgpio_f_gdi;
+	gchip->direction_input =   sppctlgpio_f_sin;
+	gchip->direction_output =  sppctlgpio_f_sou;
+	gchip->get =               sppctlgpio_f_get;
+	gchip->set =               sppctlgpio_f_set;
+	gchip->set_config =        sppctlgpio_f_scf;
+	gchip->dbg_show =          sppctlgpio_f_dsh;
+	gchip->base =              0; // it is main platform GPIO controller
+	gchip->ngpio =             GPIS_listSZ;
+	gchip->names =             sppctlgpio_list_s;
+	gchip->can_sleep =         0;
+#if defined(CONFIG_OF_GPIO)
+	gchip->of_node =           np;
+#ifdef CONFIG_PINCTRL_SPPCTL
+	gchip->of_gpio_n_cells =   2;
+#endif
+#endif
+	gchip->to_irq =            sppctlgpio_i_map;
+
+	_pctrlp->gpio_range.npins = gchip->ngpio;
+	_pctrlp->gpio_range.base =  gchip->base;
+	_pctrlp->gpio_range.name =  gchip->label;
+	_pctrlp->gpio_range.gc =    gchip;
+
+	// FIXME: can't set pc globally
+	err = devm_gpiochip_add_data(&(_pd->dev), gchip, pc);
+	if (err < 0) {
+		KERR(&(_pd->dev), "gpiochip add failed\n");
+		return err;
+	}
+
+	npins = platform_irq_count(_pd);
+	for (i = 0; i < npins && i < SPPCTL_GPIO_IRQS; i++) {
+		pc->irq[i] = irq_of_parse_and_map(np, i);
+		KDBG(&(_pd->dev), "setting up irq#%d -> %d\n", i, pc->irq[i]);
+	}
+
+	spin_lock_init(&(pc->lock));
+
+	return 0;
+}
+
+int sppctl_gpio_del(struct platform_device *_pd, void *_datap)
+{
+	//struct sppctlgpio_chip_t *cp;
+
+	// FIXME: can't use globally now
+	//cp = platform_get_drvdata(_pd);
+	//if (cp == NULL)
+	//	return -ENODEV;
+	//gpiochip_remove(&(cp->chip));
+	// FIX: remove spinlock_t ?
+	return 0;
+}
diff --git a/drivers/pinctrl/sunplus/sppctl_gpio.h b/drivers/pinctrl/sunplus/sppctl_gpio.h
new file mode 100644
index 0000000..4708d17
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_gpio.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SPPCTL_GPIO_H
+#define SPPCTL_GPIO_H
+
+#define SPPCTL_GPIO_IRQS 8
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/stringify.h>
+#include "sppctl.h"
+
+struct sppctlgpio_chip_t {
+	spinlock_t lock;
+	struct gpio_chip chip;
+	void __iomem *base0;   // MASTER , OE , OUT , IN
+	void __iomem *base1;   // I_INV , O_INV , OD
+	void __iomem *base2;   // GPIO_FIRST
+	int irq[SPPCTL_GPIO_IRQS];
+};
+
+extern const char * const sppctlgpio_list_s[];
+extern const size_t GPIS_listSZ;
+
+int sppctl_gpio_new(struct platform_device *_pd, void *_datap);
+int sppctl_gpio_del(struct platform_device *_pd, void *_datap);
+
+#ifdef CONFIG_PINCTRL_SPPCTL
+#define D_PIS(x, y) "P" __stringify(x) "_0" __stringify(y)
+#else
+#define D_PIS(x) "GPIO" __stringify(x)
+#endif
+
+// FIRST: MUX=0, GPIO=1
+enum muxF_MG_t {
+	muxF_M = 0,
+	muxF_G = 1,
+	muxFKEEP = 2,
+};
+// MASTER: IOP=0,GPIO=1
+enum muxM_IG_t {
+	muxM_I = 0,
+	muxM_G = 1,
+	muxMKEEP = 2,
+};
+
+#endif // SPPCTL_GPIO_H
diff --git a/drivers/pinctrl/sunplus/sppctl_gpio_ops.c b/drivers/pinctrl/sunplus/sppctl_gpio_ops.c
new file mode 100644
index 0000000..9f68fb4
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_gpio_ops.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/seq_file.h>
+#include <linux/io.h>
+
+#include "sppctl_gpio.h"
+#include "sppctl_gpio_ops.h"
+
+#define SPPCTL_GPIO_OFF_GFR     0x00
+#define SPPCTL_GPIO_OFF_CTL     0x00
+#define SPPCTL_GPIO_OFF_OE      0x20
+#define SPPCTL_GPIO_OFF_OUT     0x40
+#define SPPCTL_GPIO_OFF_IN      0x60
+#define SPPCTL_GPIO_OFF_IINV    0x00
+#define SPPCTL_GPIO_OFF_OINV    0x20
+#define SPPCTL_GPIO_OFF_OD      0x40
+
+// (/16)*4
+#define R16_ROF(r)              (((r)>>4)<<2)
+#define R16_BOF(r)              ((r)%16)
+// (/32)*4
+#define R32_ROF(r)              (((r)>>5)<<2)
+#define R32_BOF(r)              ((r)%32)
+#define R32_VAL(r, boff)        (((r)>>(boff)) & BIT(0))
+
+// who is first: GPIO(1) | MUX(0)
+int sppctlgpio_u_gfrst(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base2 + SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+	//KINF(_c->parent, "u F r:%X = %d %px off:%d\n", r, R32_VAL(r,R32_BOF(_n)),
+	//	pc->base2, SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+
+	return R32_VAL(r, R32_BOF(_n));
+}
+
+// who is master: GPIO(1) | IOP(0)
+int sppctlgpio_u_magpi(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base0 + SPPCTL_GPIO_OFF_CTL + R16_ROF(_n));
+	//KINF(_c->parent, "u M r:%X = %d %px off:%d\n", r, R32_VAL(r,R16_BOF(_n)),
+	//	pc->base0, SPPCTL_GPIO_OFF_CTL + R16_ROF(_n));
+
+	return R32_VAL(r, R16_BOF(_n));
+}
+
+// set master: GPIO(1)|IOP(0), first:GPIO(1)|MUX(0)
+void sppctlgpio_u_magpi_set(struct gpio_chip *_c, unsigned int _n, enum muxF_MG_t _f,
+			    enum muxM_IG_t _m)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	// FIRST
+	if (_f != muxFKEEP) {
+		r = readl(pc->base2 + SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+		//KINF(_c->parent, "F r:%X %px off:%d\n", r, pc->base2,
+		//	SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+		if (_f != R32_VAL(r, R32_BOF(_n))) {
+			if (_f == muxF_G)
+				r |= BIT(R32_BOF(_n));
+			else
+				r &= ~BIT(R32_BOF(_n));
+			//KINF(_c->parent, "F w:%X\n", r);
+			writel(r, pc->base2 + SPPCTL_GPIO_OFF_GFR + R32_ROF(_n));
+		}
+	}
+
+	// MASTER
+	if (_m != muxMKEEP) {
+		r = (BIT(R16_BOF(_n))<<16);
+		if (_m == muxM_G)
+			r |= BIT(R16_BOF(_n));
+		//KINF(_c->parent, "M w:%X %px off:%d\n", r, pc->base0,
+		//	SPPCTL_GPIO_OFF_CTL + R16_ROF(_n));
+		writel(r, pc->base0 + SPPCTL_GPIO_OFF_CTL + R16_ROF(_n));
+	}
+}
+
+// is inv: INVERTED(1) | NORMAL(0)
+int sppctlgpio_u_isinv(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+	u16 inv_off = SPPCTL_GPIO_OFF_IINV;
+
+	if (sppctlgpio_f_gdi(_c, _n) == 0)
+		inv_off = SPPCTL_GPIO_OFF_OINV;
+
+	r = readl(pc->base1 + inv_off + R16_ROF(_n));
+
+	return R32_VAL(r, R16_BOF(_n));
+}
+
+void sppctlgpio_u_siinv(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+	u16 inv_off = SPPCTL_GPIO_OFF_IINV;
+
+	r = (BIT(R16_BOF(_n))<<16) | BIT(R16_BOF(_n));
+	writel(r, pc->base1 + inv_off + R16_ROF(_n));
+}
+
+void sppctlgpio_u_soinv(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+	u16 inv_off = SPPCTL_GPIO_OFF_OINV;
+
+	r = (BIT(R16_BOF(_n))<<16) | BIT(R16_BOF(_n));
+	writel(r, pc->base1 + inv_off + R16_ROF(_n));
+}
+
+// is open-drain: YES(1) | NON(0)
+int sppctlgpio_u_isodr(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base1 + SPPCTL_GPIO_OFF_OD + R16_ROF(_n));
+
+	return R32_VAL(r, R16_BOF(_n));
+}
+
+void sppctlgpio_u_seodr(struct gpio_chip *_c, unsigned int _n, unsigned int _v)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = (BIT(R16_BOF(_n))<<16) | ((_v & BIT(0)) << R16_BOF(_n));
+	writel(r, pc->base1 + SPPCTL_GPIO_OFF_OD + R16_ROF(_n));
+}
+
+// get dir: 0=out, 1=in, -E =err (-EINVAL for ex): OE inverted on ret
+int sppctlgpio_f_gdi(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base0 + SPPCTL_GPIO_OFF_OE + R16_ROF(_n));
+
+	return R32_VAL(r, R16_BOF(_n)) ^ BIT(0);
+}
+
+// set to input: 0:ok: OE=0
+int sppctlgpio_f_sin(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = (BIT(R16_BOF(_n))<<16);
+	writel(r, pc->base0 + SPPCTL_GPIO_OFF_OE + R16_ROF(_n));
+
+	return 0;
+}
+
+// set to output: 0:ok: OE=1,O=_v
+int sppctlgpio_f_sou(struct gpio_chip *_c, unsigned int _n, int _v)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = (BIT(R16_BOF(_n))<<16) | BIT(R16_BOF(_n));
+	writel(r, pc->base0 + SPPCTL_GPIO_OFF_OE + R16_ROF(_n));
+	if (_v < 0)
+		return 0;
+	r = (BIT(R16_BOF(_n))<<16) | ((_v & BIT(0)) << R16_BOF(_n));
+	writel(r, pc->base0 + SPPCTL_GPIO_OFF_OUT + R16_ROF(_n));
+
+	return 0;
+}
+
+// get value for signal: 0=low | 1=high | -err
+int sppctlgpio_f_get(struct gpio_chip *_c, unsigned int _n)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = readl(pc->base0 + SPPCTL_GPIO_OFF_IN + R32_ROF(_n));
+
+	return R32_VAL(r, R32_BOF(_n));
+}
+
+// OUT only: can't call set on IN pin: protected by gpio_chip layer
+void sppctlgpio_f_set(struct gpio_chip *_c, unsigned int _n, int _v)
+{
+	u32 r;
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	r = (BIT(R16_BOF(_n))<<16) | (_v & 0x0001) << R16_BOF(_n);
+	writel(r, pc->base0 + SPPCTL_GPIO_OFF_OUT + R16_ROF(_n));
+}
+
+// FIX: test in-depth
+int sppctlgpio_f_scf(struct gpio_chip *_c, unsigned int _n, unsigned long _conf)
+{
+	u32 r;
+	int ret = 0;
+	enum pin_config_param cp = pinconf_to_config_param(_conf);
+	u16 ca = pinconf_to_config_argument(_conf);
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	KDBG(_c->parent, "f_scf(%03d,%lX) p:%d a:%d\n", _n, _conf, cp, ca);
+	switch (cp) {
+	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		r = (BIT(R16_BOF(_n))<<16) | BIT(R16_BOF(_n));
+		writel(r, pc->base1 + SPPCTL_GPIO_OFF_OD + R16_ROF(_n));
+		break;
+
+	case PIN_CONFIG_INPUT_ENABLE:
+		KERR(_c->parent, "f_scf(%03d,%lX) input enable arg:%d\n", _n, _conf, ca);
+		break;
+
+	case PIN_CONFIG_OUTPUT:
+		ret = sppctlgpio_f_sou(_c, _n, 0);
+		break;
+
+	case PIN_CONFIG_PERSIST_STATE:
+		KDBG(_c->parent, "f_scf(%03d,%lX) not support pinconf:%d\n", _n, _conf, cp);
+		ret = -EOPNOTSUPP;
+		break;
+
+	default:
+		KDBG(_c->parent, "f_scf(%03d,%lX) unknown pinconf:%d\n", _n, _conf, cp);
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_DEBUG_FS
+void sppctlgpio_f_dsh(struct seq_file *_s, struct gpio_chip *_c)
+{
+	int i;
+	const char *label;
+
+	for (i = 0; i < _c->ngpio; i++) {
+		label = gpiochip_is_requested(_c, i);
+		if (!label)
+			label = "";
+
+		seq_printf(_s, " gpio-%03d (%-16.16s | %-16.16s)", i + _c->base,
+			   _c->names[i], label);
+		seq_printf(_s, " %c", sppctlgpio_f_gdi(_c, i) == 0 ? 'O' : 'I');
+		seq_printf(_s, ":%d", sppctlgpio_f_get(_c, i));
+		seq_printf(_s, " %s", (sppctlgpio_u_gfrst(_c, i) ? "gpi" : "mux"));
+		seq_printf(_s, " %s", (sppctlgpio_u_magpi(_c, i) ? "gpi" : "iop"));
+		seq_printf(_s, " %s", (sppctlgpio_u_isinv(_c, i) ? "inv" : "   "));
+		seq_printf(_s, " %s", (sppctlgpio_u_isodr(_c, i) ? "oDr" : ""));
+		seq_puts(_s, "\n");
+	}
+}
+#else
+#define sppctlgpio_f_dsh NULL
+#endif
+
+int sppctlgpio_i_map(struct gpio_chip *_c, unsigned int _off)
+{
+	struct sppctlgpio_chip_t *pc = (struct sppctlgpio_chip_t *)gpiochip_get_data(_c);
+
+	if (_off >= 8 && _off < 15)
+		return pc->irq[_off - 8];
+
+	return -ENXIO;
+}
diff --git a/drivers/pinctrl/sunplus/sppctl_gpio_ops.h b/drivers/pinctrl/sunplus/sppctl_gpio_ops.h
new file mode 100644
index 0000000..05928d4
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_gpio_ops.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * GPIO Driver for Sunplus/Tibbo SP7021 controller
+ * Copyright (C) 2020 Sunplus Tech./Tibbo Tech.
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SPPCTL_GPIO_OPS_H
+#define SPPCTL_GPIO_OPS_H
+
+#include "sppctl_gpio.h"
+
+// who is first: GPIO(1) | MUX(0)
+int sppctlgpio_u_gfrst(struct gpio_chip *_c, unsigned int _n);
+
+// who is master: GPIO(1) | IOP(0)
+int sppctlgpio_u_magpi(struct gpio_chip *_c, unsigned int _n);
+
+// set MASTER and FIRST
+void sppctlgpio_u_magpi_set(struct gpio_chip *_c, unsigned int _n,
+			    enum muxF_MG_t _f, enum muxM_IG_t _m);
+
+// is inv: INVERTED(1) | NORMAL(0)
+int sppctlgpio_u_isinv(struct gpio_chip *_c, unsigned int _n);
+// set (I|O)inv
+void sppctlgpio_u_siinv(struct gpio_chip *_c, unsigned int _n);
+void sppctlgpio_u_soinv(struct gpio_chip *_c, unsigned int _n);
+
+// is open-drain: YES(1) | NON(0)
+int sppctlgpio_u_isodr(struct gpio_chip *_c, unsigned int _n);
+void sppctlgpio_u_seodr(struct gpio_chip *_c, unsigned int _n, unsigned int _v);
+
+// get dir: 0=out, 1=in, -E =err (-EINVAL for ex): OE inverted on ret
+int sppctlgpio_f_gdi(struct gpio_chip *_c, unsigned int _n);
+
+// set to input: 0:ok: OE=0
+int sppctlgpio_f_sin(struct gpio_chip *_c, unsigned int _n);
+
+// set to output: 0:ok: OE=1,O=_v
+int sppctlgpio_f_sou(struct gpio_chip *_c, unsigned int _n, int _v);
+
+// get value for signal: 0=low | 1=high | -err
+int sppctlgpio_f_get(struct gpio_chip *_c, unsigned int _n);
+
+// OUT only: can't call set on IN pin: protected by gpio_chip layer
+void sppctlgpio_f_set(struct gpio_chip *_c, unsigned int _n, int _v);
+
+// FIX: test in-depth
+int sppctlgpio_f_scf(struct gpio_chip *_c, unsigned int _n, unsigned long _conf);
+
+#ifdef CONFIG_DEBUG_FS
+void sppctlgpio_f_dsh(struct seq_file *_s, struct gpio_chip *_c);
+#else
+#define sppctlgpio_f_dsh NULL
+#endif
+
+#ifdef CONFIG_OF_GPIO
+int sppctlgpio_xlate(struct gpio_chip *_c, const struct of_phandle_args *_a,
+		     u32 *_flags);
+#endif
+
+int sppctlgpio_i_map(struct gpio_chip *_c, unsigned int _off);
+
+#endif // SPPCTL_GPIO_OPS_H
diff --git a/drivers/pinctrl/sunplus/sppctl_pinctrl.c b/drivers/pinctrl/sunplus/sppctl_pinctrl.c
new file mode 100644
index 0000000..e1bace5
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_pinctrl.c
@@ -0,0 +1,593 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "../core.h"
+#include "../pinctrl-utils.h"
+#include "../devicetree.h"
+#include "sppctl_pinctrl.h"
+#include "sppctl_gpio_ops.h"
+
+#ifdef CONFIG_PINCTRL_SPPCTL
+#define SUPPORT_PINMUX
+#endif
+
+char const **unq_grps;
+size_t unq_grpsSZ;
+struct grp2fp_map_t *g2fp_maps;
+
+int stpctl_c_p_get(struct pinctrl_dev *_pd, unsigned int _pin, unsigned long *_cfg)
+{
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	unsigned int param = pinconf_to_config_param(*_cfg);
+	unsigned int arg = 0;
+
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+	switch (param) {
+	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		if (!sppctlgpio_u_isodr(&(pctrl->gpiod->chip), _pin))
+			return -EINVAL;
+		break;
+
+	case PIN_CONFIG_OUTPUT:
+		if (!sppctlgpio_u_gfrst(&(pctrl->gpiod->chip), _pin))
+			return -EINVAL;
+		if (!sppctlgpio_u_magpi(&(pctrl->gpiod->chip), _pin))
+			return -EINVAL;
+		if (sppctlgpio_f_gdi(&(pctrl->gpiod->chip), _pin) != 0)
+			return -EINVAL;
+		arg = sppctlgpio_f_get(&(pctrl->gpiod->chip), _pin);
+		break;
+
+	default:
+		//KINF(_pd->dev, "%s(%d) skipping:x%X\n", __FUNCTION__, _pin, param);
+		return -EOPNOTSUPP;
+	}
+	*_cfg = pinconf_to_config_packed(param, arg);
+
+	return 0;
+}
+
+int stpctl_c_p_set(struct pinctrl_dev *_pd, unsigned int _pin, unsigned long *_ca,
+		   unsigned int _clen)
+{
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	int i = 0;
+
+	KDBG(_pd->dev, "%s(%d,%ld,%d)\n", __func__, _pin, *_ca, _clen);
+	// special handling for IOP
+	if (_ca[i] == 0xFF) {
+		sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), _pin, muxF_G, muxM_I);
+		return 0;
+	}
+
+	for (i = 0; i < _clen; i++) {
+		if (_ca[i] & SPPCTL_PCTL_L_OUT) {
+			KDBG(_pd->dev, "%d:OUT\n", i);
+			sppctlgpio_f_sou(&(pctrl->gpiod->chip), _pin, 0);
+		}
+		if (_ca[i] & SPPCTL_PCTL_L_OU1) {
+			KDBG(_pd->dev, "%d:OU1\n", i);
+			sppctlgpio_f_sou(&(pctrl->gpiod->chip), _pin, 1);
+		}
+		if (_ca[i] & SPPCTL_PCTL_L_INV) {
+			KDBG(_pd->dev, "%d:INV\n", i);
+			sppctlgpio_u_siinv(&(pctrl->gpiod->chip), _pin);
+		}
+		if (_ca[i] & SPPCTL_PCTL_L_ONV) {
+			KDBG(_pd->dev, "%d:ONV\n", i);
+			sppctlgpio_u_soinv(&(pctrl->gpiod->chip), _pin);
+		}
+		if (_ca[i] & SPPCTL_PCTL_L_ODR) {
+			KDBG(_pd->dev, "%d:ODR\n", i);
+			sppctlgpio_u_seodr(&(pctrl->gpiod->chip), _pin, 1);
+		}
+		// FIXME: add pullup/pulldown, irq enable/disable
+	}
+
+	return 0;
+}
+
+int stpctl_c_g_get(struct pinctrl_dev *_pd, unsigned int _gid, unsigned long *_config)
+{
+	// KINF(_pd->dev, "%s(%d)\n", __FUNCTION__, _gid);
+	// FIXME: add data
+	return 0;
+}
+
+int stpctl_c_g_set(struct pinctrl_dev *_pd, unsigned int _gid, unsigned long *_configs,
+		   unsigned int _num_configs)
+{
+	// KINF(_pd->dev, "%s(%d,,%d)\n", __FUNCTION__, _gid, _num_configs);
+	// FIXME: delete ?
+	return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+void stpctl_c_d_show(struct pinctrl_dev *_pd, struct seq_file *s, unsigned int _off)
+{
+	// KINF(_pd->dev, "%s(%d)\n", __FUNCTION__, _off);
+	seq_printf(s, " %s", dev_name(_pd->dev));
+}
+
+void stpctl_c_d_group_show(struct pinctrl_dev *_pd, struct seq_file *s, unsigned int _gid)
+{
+	// group: freescale/pinctrl-imx.c, 448
+	// KINF(_pd->dev, "%s(%d)\n", __FUNCTION__, _gid);
+}
+
+void stpctl_c_d_config_show(struct pinctrl_dev *_pd, struct seq_file *s, unsigned long _config)
+{
+	// KINF(_pd->dev, "%s(%ld)\n", __FUNCTION__, _config);
+}
+#else
+#define stpctl_c_d_show NULL
+#define stpctl_c_d_group_show NULL
+#define stpctl_c_d_config_show NULL
+#endif
+
+static struct pinconf_ops sppctl_pconf_ops = {
+	.is_generic                 = true,
+	.pin_config_get             = stpctl_c_p_get,
+	.pin_config_set             = stpctl_c_p_set,
+	//.pin_config_group_get       = stpctl_c_g_get,
+	//.pin_config_group_set       = stpctl_c_g_set,
+	.pin_config_dbg_show        = stpctl_c_d_show,
+	.pin_config_group_dbg_show  = stpctl_c_d_group_show,
+	.pin_config_config_dbg_show = stpctl_c_d_config_show,
+};
+
+int stpctl_m_req(struct pinctrl_dev *_pd, unsigned int _pin)
+{
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+	return 0;
+}
+
+int stpctl_m_fre(struct pinctrl_dev *_pd, unsigned int _pin)
+{
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+	return 0;
+}
+
+int stpctl_m_f_cnt(struct pinctrl_dev *_pd)
+{
+	return list_funcsSZ;
+}
+
+const char *stpctl_m_f_nam(struct pinctrl_dev *_pd, unsigned int _fid)
+{
+	return list_funcs[_fid].name;
+}
+
+int stpctl_m_f_grp(struct pinctrl_dev *_pd, unsigned int _fid, const char * const **grps,
+		   unsigned int *_gnum)
+{
+	struct func_t *f = &(list_funcs[_fid]);
+
+	*_gnum = 0;
+	switch (f->freg) {
+	case fOFF_I:
+	case fOFF_0:   // gen GPIO/IOP: all groups = all pins
+		*_gnum = GPIS_listSZ;
+		*grps = sppctlgpio_list_s;
+		break;
+
+	case fOFF_M:   // pin-mux
+		*_gnum = PMUX_listSZ;
+		*grps = sppctlpmux_list_s;
+		break;
+
+	case fOFF_G:   // pin-group
+		if (!f->grps)
+			break;
+		*_gnum = f->gnum;
+		*grps = (const char * const *)f->grps_sa;
+		break;
+
+	default:
+		KERR(_pd->dev, "%s(_fid:%d) unknown fOFF %d\n", __func__, _fid, f->freg);
+		break;
+	}
+
+	KDBG(_pd->dev, "%s(_fid:%d) %d\n", __func__, _fid, *_gnum);
+	return 0;
+}
+
+int stpctl_m_mux(struct pinctrl_dev *_pd, unsigned int _fid, unsigned int _gid)
+{
+	int i = -1, j = -1;
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	struct func_t *f = &(list_funcs[_fid]);
+
+	struct grp2fp_map_t g2fpm = g2fp_maps[_gid];
+
+	KDBG(_pd->dev, "%s(fun:%d,grp:%d)\n", __func__, _fid, _gid);
+	switch (f->freg) {
+	case fOFF_0:   // GPIO. detouch from all funcs - ?
+		for (i = 0; i < list_funcsSZ; i++) {
+			if (list_funcs[i].freg != fOFF_M)
+				continue;
+			j++;
+			if (sppctl_fun_get(pctrl, j) != _gid)
+				continue;
+			sppctl_pin_set(pctrl, 0, j);
+		}
+		break;
+
+	case fOFF_M:   // MUX :
+		sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), _gid, muxF_M, muxMKEEP);
+		sppctl_pin_set(pctrl, (_gid == 0 ? _gid : _gid - 7), _fid - 2);    // pin, fun FIXME
+		break;
+
+	case fOFF_G:   // GROUP
+		for (i = 0; i < f->grps[g2fpm.g_idx].pnum; i++)
+			sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), f->grps[g2fpm.g_idx].pins[i],
+					       muxF_M, muxMKEEP);
+		sppctl_gmx_set(pctrl, f->roff, f->boff, f->blen, f->grps[g2fpm.g_idx].gval);
+		break;
+
+	case fOFF_I:   // IOP
+		sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), _gid, muxF_G, muxM_I);
+		break;
+
+	default:
+		KERR(_pd->dev, "%s(_fid:%d) unknown fOFF %d\n", __func__, _fid, f->freg);
+		break;
+	}
+
+	return 0;
+}
+
+int stpctl_m_gpio_req(struct pinctrl_dev *_pd, struct pinctrl_gpio_range *range, unsigned int _pin)
+{
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	struct pin_desc *pdesc;
+	int g_f, g_m;
+
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+	g_f = sppctlgpio_u_gfrst(&(pctrl->gpiod->chip), _pin);
+	g_m = sppctlgpio_u_magpi(&(pctrl->gpiod->chip), _pin);
+	if (g_f == muxF_G && g_m == muxM_G)
+		return 0;
+
+	pdesc = pin_desc_get(_pd, _pin);
+	// in non-gpio state: is it claimed already?
+	if (pdesc->mux_owner)
+		return -EACCES;
+
+	sppctlgpio_u_magpi_set(&(pctrl->gpiod->chip), _pin, muxF_G, muxM_G);
+	return 0;
+}
+
+void stpctl_m_gpio_fre(struct pinctrl_dev *_pd, struct pinctrl_gpio_range *range,
+		       unsigned int _pin)
+{
+	KDBG(_pd->dev, "%s(%d)\n", __func__, _pin);
+}
+int stpctl_m_gpio_sdir(struct pinctrl_dev *_pd, struct pinctrl_gpio_range *range,
+		       unsigned int _pin, bool _in)
+{
+	KDBG(_pd->dev, "%s(%d,%d)\n", __func__, _pin, _in);
+	return 0;
+}
+
+static const struct pinmux_ops sppctl_pinmux_ops = {
+	.request             = stpctl_m_req,
+	.free                = stpctl_m_fre,
+	.get_functions_count = stpctl_m_f_cnt,
+	.get_function_name   = stpctl_m_f_nam,
+	.get_function_groups = stpctl_m_f_grp,
+	.set_mux             = stpctl_m_mux,
+	.gpio_request_enable = stpctl_m_gpio_req,
+	.gpio_disable_free   = stpctl_m_gpio_fre,
+	.gpio_set_direction  = stpctl_m_gpio_sdir,
+	.strict              = 1
+};
+
+// all groups
+int stpctl_o_g_cnt(struct pinctrl_dev *_pd)
+{
+	return unq_grpsSZ;
+}
+
+const char *stpctl_o_g_nam(struct pinctrl_dev *_pd, unsigned int _gid)
+{
+	return unq_grps[_gid];
+}
+
+int stpctl_o_g_pins(struct pinctrl_dev *_pd, unsigned int _gid, const unsigned int **pins,
+		    unsigned int *num_pins)
+{
+	struct grp2fp_map_t g2fpm = g2fp_maps[_gid];
+	struct func_t *f = &(list_funcs[g2fpm.f_idx]);
+
+	KDBG(_pd->dev, "grp-pins g:%d f_idx:%d,g_idx:%d freg:%d...\n", _gid, g2fpm.f_idx,
+	     g2fpm.g_idx, f->freg);
+	*num_pins = 0;
+
+	// MUX | GPIO | IOP: 1 pin -> 1 group
+	if (f->freg != fOFF_G) {
+		*num_pins = 1;
+		*pins = &sppctlpins_G[_gid];
+		return 0;
+	}
+
+	// IOP (several pins at once in a group)
+	if (!f->grps)
+		return 0;
+	if (f->gnum < 1)
+		return 0;
+	*num_pins = f->grps[g2fpm.g_idx].pnum;
+	*pins = f->grps[g2fpm.g_idx].pins;
+
+	return 0;
+}
+
+// /sys/kernel/debug/pinctrl/sppctl/pins add: gpio_first and ctrl_sel
+#ifdef CONFIG_DEBUG_FS
+void stpctl_o_show(struct pinctrl_dev *_pd, struct seq_file *_s, unsigned int _n)
+{
+	struct sppctl_pdata_t *p = pinctrl_dev_get_drvdata(_pd);
+	const char *tmpp;
+	uint8_t g_f, g_m;
+
+	seq_printf(_s, "%s", dev_name(_pd->dev));
+	g_f = sppctlgpio_u_gfrst(&(p->gpiod->chip), _n);
+	g_m = sppctlgpio_u_magpi(&(p->gpiod->chip), _n);
+
+	tmpp = "?";
+	if (g_f &&  g_m)
+		tmpp = "GPIO";
+	if (g_f && !g_m)
+		tmpp = " IOP";
+	if (!g_f)
+		tmpp = " MUX";
+	seq_printf(_s, " %s", tmpp);
+}
+#else
+#define stpctl_ops_show NULL
+#endif
+
+int stpctl_o_n2map(struct pinctrl_dev *_pd, struct device_node *_dn, struct pinctrl_map **_map,
+		   unsigned int *_nm)
+{
+	struct sppctl_pdata_t *pctrl = pinctrl_dev_get_drvdata(_pd);
+	struct device_node *parent;
+	u32 dt_pin, dt_fun;
+	u8 p_p, p_g, p_f, p_l;
+	unsigned long *configs;
+	int i, size = 0;
+	const __be32 *list = of_get_property(_dn, "pins", &size);
+	struct property *prop;
+	const char *s_f, *s_g;
+	int nmG = of_property_count_strings(_dn, "groups");
+	struct func_t *f = NULL;
+
+	//print_device_tree_node(_dn, 0);
+	if (nmG <= 0)
+		nmG = 0;
+
+	parent = of_get_parent(_dn);
+	*_nm = size/sizeof(*list);
+
+	// Check if out of range or invalid?
+	for (i = 0; i < (*_nm); i++) {
+		dt_pin = be32_to_cpu(list[i]);
+		p_p = SPPCTL_PCTLD_P(dt_pin);
+		p_g = SPPCTL_PCTLD_G(dt_pin);
+
+		if ((p_p >= sppctlpins_allSZ)
+#ifndef SUPPORT_PINMUX
+			|| (p_g == SPPCTL_PCTL_G_PMUX)
+#endif
+		) {
+			KDBG(_pd->dev, "Invalid pin property at index %d (0x%08x)\n", i, dt_pin);
+			return -EINVAL;
+		}
+	}
+
+	*_map = kcalloc(*_nm + nmG, sizeof(**_map), GFP_KERNEL);
+	for (i = 0; i < (*_nm); i++) {
+		dt_pin = be32_to_cpu(list[i]);
+		p_p = SPPCTL_PCTLD_P(dt_pin);
+		p_g = SPPCTL_PCTLD_G(dt_pin);
+		p_f = SPPCTL_PCTLD_F(dt_pin);
+		p_l = SPPCTL_PCTLD_L(dt_pin);
+		(*_map)[i].name = parent->name;
+		KDBG(_pd->dev, "map [%d]=%08x p=%d g=%d f=%d l=%d\n", i, dt_pin, p_p, p_g,
+		     p_f, p_l);
+
+		if (p_g == SPPCTL_PCTL_G_GPIO) {
+			// look into parse_dt_cfg(),
+			(*_map)[i].type = PIN_MAP_TYPE_CONFIGS_PIN;
+			(*_map)[i].data.configs.num_configs = 1;
+			(*_map)[i].data.configs.group_or_pin = pin_get_name(_pd, p_p);
+			configs = kcalloc(1, sizeof(*configs), GFP_KERNEL);
+			*configs = p_l;
+			(*_map)[i].data.configs.configs = configs;
+
+			KDBG(_pd->dev, "%s(%d) = x%X\n", (*_map)[i].data.configs.group_or_pin,
+			     p_p, p_l);
+		} else if (p_g == SPPCTL_PCTL_G_IOPP) {
+			(*_map)[i].type = PIN_MAP_TYPE_CONFIGS_PIN;
+			(*_map)[i].data.configs.num_configs = 1;
+			(*_map)[i].data.configs.group_or_pin = pin_get_name(_pd, p_p);
+			configs = kcalloc(1, sizeof(*configs), GFP_KERNEL);
+			*configs = 0xFF;
+			(*_map)[i].data.configs.configs = configs;
+
+			KDBG(_pd->dev, "%s(%d) = x%X\n", (*_map)[i].data.configs.group_or_pin,
+			     p_p, p_l);
+		} else {
+			(*_map)[i].type = PIN_MAP_TYPE_MUX_GROUP;
+			(*_map)[i].data.mux.function = list_funcs[p_f].name;
+			(*_map)[i].data.mux.group = pin_get_name(_pd, p_p);
+
+			KDBG(_pd->dev, "f->p: %s(%d)->%s(%d)\n", (*_map)[i].data.mux.function,
+			     p_f, (*_map)[i].data.mux.group, p_p);
+		}
+	}
+
+	// handle pin-group function
+	if (nmG > 0 && of_property_read_string(_dn, "function", &s_f) == 0) {
+		KDBG(_pd->dev, "found func: %s\n", s_f);
+		of_property_for_each_string(_dn, "groups", prop, s_g) {
+			KDBG(_pd->dev, " %s: %s\n", s_f, s_g);
+			(*_map)[*_nm].type = PIN_MAP_TYPE_MUX_GROUP;
+			(*_map)[*_nm].data.mux.function = s_f;
+			(*_map)[*_nm].data.mux.group = s_g;
+			KDBG(_pd->dev, "f->g: %s->%s\n", (*_map)[*_nm].data.mux.function,
+			     (*_map)[*_nm].data.mux.group);
+			(*_nm)++;
+		}
+	}
+
+	// handle zero function
+	list = of_get_property(_dn, "zero_func", &size);
+	if (list) {
+		for (i = 0; i < size/sizeof(*list); i++) {
+			dt_fun = be32_to_cpu(list[i]);
+			if (dt_fun >= list_funcsSZ) {
+				KERR(_pd->dev, "zero func %d out of range\n", dt_fun);
+				continue;
+			}
+
+			f = &(list_funcs[dt_fun]);
+			switch (f->freg) {
+			case fOFF_M:
+				KDBG(_pd->dev, "zero func: %d (%s)\n", dt_fun, f->name);
+				sppctl_pin_set(pctrl, 0, dt_fun - 2);
+				break;
+
+			case fOFF_G:
+				KDBG(_pd->dev, "zero group: %d (%s)\n", dt_fun, f->name);
+				sppctl_gmx_set(pctrl, f->roff, f->boff, f->blen, 0);
+				break;
+
+			default:
+				KERR(_pd->dev, "wrong zero group: %d (%s)\n", dt_fun, f->name);
+				break;
+			}
+		}
+	}
+
+	of_node_put(parent);
+	KDBG(_pd->dev, "%d pins mapped\n", *_nm);
+	return 0;
+}
+
+void stpctl_o_mfre(struct pinctrl_dev *_pd, struct pinctrl_map *_map, unsigned int num_maps)
+{
+	//KINF(_pd->dev, "%s(%d)\n", __FUNCTION__, num_maps);
+	// FIXME: test
+	pinctrl_utils_free_map(_pd, _map, num_maps);
+}
+
+static const struct pinctrl_ops sppctl_pctl_ops = {
+	.get_groups_count = stpctl_o_g_cnt,
+	.get_group_name   = stpctl_o_g_nam,
+	.get_group_pins   = stpctl_o_g_pins,
+#ifdef CONFIG_DEBUG_FS
+	.pin_dbg_show     = stpctl_o_show,
+#endif
+	.dt_node_to_map   = stpctl_o_n2map,
+	.dt_free_map      = stpctl_o_mfre,
+};
+
+// creates unq_grps[] uniq group names array char *
+// sets unq_grpsSZ
+// creates XXX[group_idx]{func_idx, pins_idx}
+void group_groups(struct platform_device *_pd)
+{
+	int i, k, j = 0;
+
+	// fill array of all groups
+	unq_grps = NULL;
+	unq_grpsSZ = GPIS_listSZ;
+
+	// calc unique group names array size
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg != fOFF_G)
+			continue;
+		unq_grpsSZ += list_funcs[i].gnum;
+	}
+
+	// fill up unique group names array
+	unq_grps = devm_kzalloc(&(_pd->dev), (unq_grpsSZ + 1)*sizeof(char *), GFP_KERNEL);
+	g2fp_maps = devm_kzalloc(&(_pd->dev), (unq_grpsSZ + 1)*sizeof(struct grp2fp_map_t),
+				 GFP_KERNEL);
+
+	// groups == pins
+	j = 0;
+	for (i = 0; i < GPIS_listSZ; i++) {
+		unq_grps[i] = sppctlgpio_list_s[i];
+		g2fp_maps[i].f_idx = 0;
+		g2fp_maps[i].g_idx = i;
+	}
+	j = GPIS_listSZ;
+
+	// +IOP groups
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg != fOFF_G)
+			continue;
+
+		for (k = 0; k < list_funcs[i].gnum; k++) {
+			list_funcs[i].grps_sa[k] = (char *)list_funcs[i].grps[k].name;
+			unq_grps[j] = list_funcs[i].grps[k].name;
+			g2fp_maps[j].f_idx = i;
+			g2fp_maps[j].g_idx = k;
+			j++;
+		}
+	}
+	KINF(&(_pd->dev), "funcs: %zd unq_grps: %zd\n", list_funcsSZ, unq_grpsSZ);
+}
+
+// ---------- main (exported) functions
+int sppctl_pinctrl_init(struct platform_device *_pd)
+{
+	int err;
+	struct device *dev = &_pd->dev;
+	struct device_node *np = of_node_get(dev->of_node);
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+
+	// init pdesc
+	_p->pdesc.owner = THIS_MODULE;
+	_p->pdesc.name = dev_name(&(_pd->dev));
+	_p->pdesc.pins = &(sppctlpins_all[0]);
+	_p->pdesc.npins = sppctlpins_allSZ;
+	_p->pdesc.pctlops = &sppctl_pctl_ops;
+	_p->pdesc.confops = &sppctl_pconf_ops;
+	_p->pdesc.pmxops = &sppctl_pinmux_ops;
+
+	group_groups(_pd);
+
+	err = devm_pinctrl_register_and_init(&(_pd->dev), &(_p->pdesc), _p, &(_p->pcdp));
+	if (err) {
+		KERR(&(_pd->dev), "Failed to register\n");
+		of_node_put(np);
+		return err;
+	}
+
+	pinctrl_enable(_p->pcdp);
+	return 0;
+}
+
+void sppctl_pinctrl_clea(struct platform_device *_pd)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+
+	devm_pinctrl_unregister(&(_pd->dev), _p->pcdp);
+}
diff --git a/drivers/pinctrl/sunplus/sppctl_pinctrl.h b/drivers/pinctrl/sunplus/sppctl_pinctrl.h
new file mode 100644
index 0000000..a634c41
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_pinctrl.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#ifndef SPPCTL_PINCTRL_H
+#define SPPCTL_PINCTRL_H
+
+#include "sppctl.h"
+
+
+int sppctl_pinctrl_init(struct platform_device *_pdev);
+void sppctl_pinctrl_clea(struct platform_device *_pdev);
+
+#define D(x, y) ((x)*8+(y))
+
+extern const struct pinctrl_pin_desc sppctlpins_all[];
+extern const size_t sppctlpins_allSZ;
+extern const unsigned int sppctlpins_G[];
+
+#endif // SPPCTL_PINCTRL_H
diff --git a/drivers/pinctrl/sunplus/sppctl_sysfs.c b/drivers/pinctrl/sunplus/sppctl_sysfs.c
new file mode 100644
index 0000000..7fe54bb
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_sysfs.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "sppctl_sysfs.h"
+#include "sppctl_gpio_ops.h"
+#include "sppctl_pinctrl.h"
+
+
+static ssize_t sppctl_sop_name_R(struct device *_d, struct device_attribute *_a, char *_b)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	return sprintf(_b, "%s\n", _p->name);
+}
+
+static ssize_t sppctl_sop_dbgi_R(struct device *_d, struct device_attribute *_a, char *_b)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	return sprintf(_b, "%d\n", _p->debug);
+}
+
+static ssize_t sppctl_sop_dbgi_W(struct device *_d, struct device_attribute *_a, const char *_b,
+				 size_t _c)
+{
+	int x;
+
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	if (kstrtoint(_b, 10, &x) < 0)
+		return -EIO;
+	_p->debug = x;
+
+	return _c;
+}
+
+static ssize_t sppctl_sop_fwname_R(struct device *_d, struct device_attribute *_a, char *_b)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	return sprintf(_b, "%s", _p->fwname);
+}
+
+static ssize_t sppctl_sop_fwname_W(struct device *_d, struct device_attribute *_a, const char *_b,
+				   size_t _c)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_d->platform_data;
+
+	strcpy(_p->fwname, _b);
+	if (_p->fwname[strlen(_p->fwname)-1] == 0x0A)
+		_p->fwname[strlen(_p->fwname)-1] = 0;
+	sppctl_loadfw(_d, _p->fwname);
+
+	return _c;
+}
+
+static ssize_t sppctl_sop_list_muxes_R(struct file *filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t off, size_t count)
+{
+	int i = -1, ret = 0, pos = off;
+	const char *tmpp;
+	struct sppctl_pdata_t *_p = NULL;
+	struct device *_pdev = container_of(_k, struct device, kobj);
+
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg == fOFF_0)
+			continue;
+		if (list_funcs[i].freg == fOFF_I)
+			continue;
+		tmpp = list_funcs[i].name;
+		if (pos > 0) {
+			pos -= (strlen(tmpp) + 1);
+			continue;
+		}
+		sprintf(_b + ret, "%s\n", tmpp);
+		ret += strlen(tmpp) + 1;
+		if (ret > SPPCTL_MAX_BUF - SPPCTL_MAX_NAM)
+			break;
+	}
+
+	return ret;
+}
+
+static ssize_t sppctl_sop_txt_map_R(struct file *filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t off, size_t count)
+{
+	int i = -1, j = 0, ret = 0, pos = off;
+	char tmps[SPPCTL_MAX_NAM + 3];
+	uint8_t pin = 0;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+	struct device *_pdev = container_of(_k, struct device, kobj);
+
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	for (i = 0; i < list_funcsSZ; i++) {
+		f = &(list_funcs[i]);
+		pin = 0;
+		if (f->freg == fOFF_0)
+			continue;
+		if (f->freg == fOFF_I)
+			continue;
+		memset(tmps, 0, SPPCTL_MAX_NAM + 3);
+
+		// muxable pins are P1_xx, stored -7, absolute idx = +7
+		pin = sppctl_fun_get(_p, j++);
+		if (f->freg == fOFF_M && pin > 0)
+			pin += 7;
+		if (f->freg == fOFF_G)
+			pin = sppctl_gmx_get(_p, f->roff, f->boff, f->blen);
+		sprintf(tmps, "%03d %s", pin, f->name);
+
+		if (pos > 0) {
+			pos -= (strlen(tmps) + 1);
+			continue;
+		}
+		sprintf(_b + ret, "%s\n", tmps);
+		ret += strlen(tmps) + 1;
+		if (ret > SPPCTL_MAX_BUF - SPPCTL_MAX_NAM)
+			break;
+	}
+
+	return ret;
+}
+
+static ssize_t sppctl_sop_func_R(struct file *_filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t _off, size_t _count)
+{
+	struct device *_pdev = NULL;
+	struct sppctl_sdata_t *sdp = NULL;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+
+	if (_off > 0)
+		return 0;
+
+	_pdev = container_of(_k, struct device, kobj);
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	sdp = (struct sppctl_sdata_t *)_a->private;
+	if (!sdp)
+		return -ENXIO;
+
+	f = &(list_funcs[sdp->i]);
+	if (f->freg == fOFF_M)
+		_b[0] = sppctl_fun_get(_p, sdp->ridx);
+	if (f->freg == fOFF_G)
+		_b[0] = sppctl_gmx_get(_p, f->roff, f->boff, f->blen);
+	_b[1] = 0x00;
+	if (_p->debug)
+		KDBG(_pdev, "%s(%s,i:%d) _b:%d\n", __func__, _a->attr.name, sdp->ridx, _b[0]);
+
+	return 1;
+}
+
+static ssize_t sppctl_sop_func_W(struct file *_filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t _off, size_t _count)
+{
+	struct device *_pdev = NULL;
+	struct sppctl_sdata_t *sdp = NULL;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+
+	if (_off > 0)
+		return 0;
+
+	_pdev = container_of(_k, struct device, kobj);
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	sdp = (struct sppctl_sdata_t *)_a->private;
+	if (!sdp)
+		return -ENXIO;
+
+	f = &(list_funcs[sdp->i]);
+	// for mux it should be PIN-7, case muxable pins start from 8'th
+	if (f->freg == fOFF_M)
+		sppctl_pin_set(_p, (_b[0] < 8 ? 0 : _b[0] - 7), sdp->ridx);
+	if (f->freg == fOFF_G)
+		sppctl_gmx_set(_p, f->roff, f->boff, f->blen, _b[0]);
+	if (_p->debug)
+		KDBG(_pdev, "%s(%s,i:%d) _b:%d\n", __func__, _a->attr.name, sdp->ridx, _b[0]);
+
+	return _count;
+}
+
+static ssize_t sppctl_sop_fw_R(struct file *filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t _off, size_t _count)
+{
+	int i = 0, j = 0, ret = 0, pos = _off;
+	uint8_t pin = 0;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+	struct device *_pdev = container_of(_k, struct device, kobj);
+
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	for (i = 0; i < list_funcsSZ && ret < _count; i++) {
+		f = &(list_funcs[i]);
+		if (f->freg == fOFF_0)
+			continue;
+		if (f->freg == fOFF_I)
+			continue;
+		if (f->freg == fOFF_M)
+			pin = sppctl_fun_get(_p, j++);
+		if (f->freg == fOFF_G)
+			pin = sppctl_gmx_get(_p, f->roff, f->boff, f->blen);
+		if (pos > 0) {
+			pos -= sizeof(pin);
+			continue;
+		}
+		_b[ret] = pin;
+		ret += sizeof(pin);
+		if (ret > SPPCTL_MAX_BUF - SPPCTL_MAX_NAM)
+			break;
+	}
+
+	return ret;
+}
+
+static ssize_t sppctl_sop_fw_W(struct file *filp, struct kobject *_k,
+	struct bin_attribute *_a, char *_b, loff_t _off, size_t _count)
+{
+	int i = 0, j = 0, pos = 0;
+	struct sppctl_pdata_t *_p = NULL;
+	struct func_t *f;
+	struct device *_pdev = container_of(_k, struct device, kobj);
+
+	if (_off + _count < (list_funcsSZ - 2))
+		KINF(_pdev, "%s() fw size %zd < %zd\n", __func__, _count, list_funcsSZ);
+
+	if (!_pdev)
+		return -ENXIO;
+
+	_p = (struct sppctl_pdata_t *)_pdev->platform_data;
+	if (!_p)
+		return -ENXIO;
+
+	for (; i < list_funcsSZ && pos < _count; i++) {
+		f = &(list_funcs[i]);
+		if (f->freg == fOFF_0)
+			continue;
+		if (f->freg == fOFF_I)
+			continue;
+		if (j < _off) {
+			j++;
+			continue;
+		}
+
+		if (f->freg == fOFF_M)
+			sppctl_pin_set(_p, _b[pos], j++);
+		if (f->freg == fOFF_G)
+			sppctl_gmx_set(_p, f->roff, f->boff, f->blen, _b[pos]);
+
+		pos++;
+	}
+
+	return pos;
+}
+
+static struct device_attribute sppctl_sysfs_attrsD[] = {
+	__ATTR(name,   0444, sppctl_sop_name_R,   NULL),
+	__ATTR(dbgi,   0644, sppctl_sop_dbgi_R,   sppctl_sop_dbgi_W),
+	__ATTR(fwname, 0644, sppctl_sop_fwname_R, sppctl_sop_fwname_W),
+};
+
+static struct bin_attribute sppctl_sysfs_attrsB[] = {
+	__BIN_ATTR(list_muxes, 0444, sppctl_sop_list_muxes_R, NULL,            SPPCTL_MAX_BUF),
+	__BIN_ATTR(txt_map,    0444, sppctl_sop_txt_map_R,    NULL,            SPPCTL_MAX_BUF),
+	__BIN_ATTR(fw,         0644, sppctl_sop_fw_R,         sppctl_sop_fw_W, SPPCTL_MAX_BUF),
+};
+
+struct bin_attribute *sppctl_sysfs_Fap;
+
+// ---------- main (exported) functions
+void sppctl_sysfs_init(struct platform_device *_pd)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+	struct sppctl_sdata_t *sdp = NULL;
+	int i, ret, ridx = 0;
+	const char *tmpp;
+
+	for (i = 0; i < ARRAY_SIZE(sppctl_sysfs_attrsD); i++) {
+		ret = device_create_file(&(_pd->dev), &sppctl_sysfs_attrsD[i]);
+		if (ret)
+			KERR(&(_pd->dev), "createD[%d] error\n", i);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(sppctl_sysfs_attrsB); i++) {
+		ret = device_create_bin_file(&(_pd->dev), &sppctl_sysfs_attrsB[i]);
+		if (ret)
+			KERR(&(_pd->dev), "createB[%d] error\n", i);
+	}
+
+	i = -1;
+	sppctl_sysfs_Fap = kcalloc(list_funcsSZ, sizeof(struct bin_attribute), GFP_KERNEL);
+	sdp = kcalloc(list_funcsSZ, sizeof(struct sppctl_sdata_t), GFP_KERNEL);
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg == fOFF_0)
+			continue;
+		if (list_funcs[i].freg == fOFF_I)
+			continue;
+
+		tmpp = list_funcs[i].name;
+		sdp[i].i = i;
+		sdp[i].ridx = ridx++;
+		sdp[i].pdata = _p;
+
+		sysfs_bin_attr_init(sppctl_sysfs_Fap[i]);
+		sppctl_sysfs_Fap[i].attr.name = tmpp;
+		sppctl_sysfs_Fap[i].attr.mode = 0644;
+		sppctl_sysfs_Fap[i].read  = sppctl_sop_func_R;
+		sppctl_sysfs_Fap[i].write = sppctl_sop_func_W;
+		sppctl_sysfs_Fap[i].size = SPPCTL_MAX_BUF;
+		sppctl_sysfs_Fap[i].private = &(sdp[i]);
+		ret = device_create_bin_file(&(_pd->dev), &(sppctl_sysfs_Fap[i]));
+
+		if (ret)
+			KERR(&(_pd->dev), "createF[%d,%s] error\n", i, tmpp);
+	}
+	_p->sysfs_sdp = sdp;
+}
+
+void sppctl_sysfs_clean(struct platform_device *_pd)
+{
+	struct sppctl_pdata_t *_p = (struct sppctl_pdata_t *)_pd->dev.platform_data;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(sppctl_sysfs_attrsD); i++)
+		device_remove_file(&(_pd->dev), &sppctl_sysfs_attrsD[i]);
+	for (i = 0; i < ARRAY_SIZE(sppctl_sysfs_attrsB); i++)
+		device_remove_bin_file(&(_pd->dev), &sppctl_sysfs_attrsB[i]);
+
+	i = -1;
+	for (i = 0; i < list_funcsSZ; i++) {
+		if (list_funcs[i].freg == fOFF_0)
+			continue;
+		if (list_funcs[i].freg == fOFF_I)
+			continue;
+		device_remove_bin_file(&(_pd->dev), &(sppctl_sysfs_Fap[i]));
+	}
+
+	kfree(sppctl_sysfs_Fap);
+	kfree(_p->sysfs_sdp);
+}
diff --git a/drivers/pinctrl/sunplus/sppctl_sysfs.h b/drivers/pinctrl/sunplus/sppctl_sysfs.h
new file mode 100644
index 0000000..f37b8cf
--- /dev/null
+++ b/drivers/pinctrl/sunplus/sppctl_sysfs.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux controller driver.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#ifndef SPPCTL_SYSFS_H
+#define SPPCTL_SYSFS_H
+
+#include "sppctl.h"
+
+
+struct sppctl_sdata_t {
+	uint8_t i;
+	uint8_t ridx;
+	struct sppctl_pdata_t *pdata;
+};
+
+void sppctl_sysfs_init(struct platform_device *_pdev);
+void sppctl_sysfs_clean(struct platform_device *_pdev);
+
+#endif // SPPCTL_SYSFS_H
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 2/3] dt-bindings: pinctrl: Add dt-bindings for Sunplus SP7021
  2021-11-01  8:11 ` [PATCH v2 0/3] This is a patch series for pinctrl driver for Sunplus SP7021 SoC Wells Lu
  2021-11-01  8:11   ` [PATCH v2 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
@ 2021-11-01  8:11   ` Wells Lu
  2021-11-12 15:37     ` Rob Herring
  2021-11-01  8:11   ` [PATCH v2 3/3] devicetree: bindings: pinctrl: Add bindings doc " Wells Lu
  2 siblings, 1 reply; 19+ messages in thread
From: Wells Lu @ 2021-11-01  8:11 UTC (permalink / raw)
  To: linus.walleij, linux-gpio, linux-kernel, robh+dt, devicetree
  Cc: qinjian, dvorkin, Wells Lu

Add dt-bindings header files for Sunplus SP7021 SoC.

Signed-off-by: Wells Lu <wells.lu@sunplus.com>
---
Changes in v2:
 - Added more 'defines' in dt-bindings header files (forgot to add in v1).

 MAINTAINERS                                 |   1 +
 include/dt-bindings/pinctrl/sppctl-sp7021.h | 171 ++++++++++++++++++++++++++++
 include/dt-bindings/pinctrl/sppctl.h        |  40 +++++++
 3 files changed, 212 insertions(+)
 create mode 100644 include/dt-bindings/pinctrl/sppctl-sp7021.h
 create mode 100644 include/dt-bindings/pinctrl/sppctl.h

diff --git a/MAINTAINERS b/MAINTAINERS
index fd82c77..da6378f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14873,6 +14873,7 @@ L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
 S:	Maintained
 W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
 F:	drivers/pinctrl/sunplus/
+F:	include/dt-bindings/pinctrl/sppctl*
 
 PKTCDVD DRIVER
 M:	linux-block@vger.kernel.org
diff --git a/include/dt-bindings/pinctrl/sppctl-sp7021.h b/include/dt-bindings/pinctrl/sppctl-sp7021.h
new file mode 100644
index 0000000..4e07d03
--- /dev/null
+++ b/include/dt-bindings/pinctrl/sppctl-sp7021.h
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux pinctrl bindings.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ */
+
+#ifndef _DT_BINDINGS_PINCTRL_SPPCTL_SP7021_H
+#define _DT_BINDINGS_PINCTRL_SPPCTL_SP7021_H
+
+#include <dt-bindings/pinctrl/sppctl.h>
+
+#define MUXF_GPIO                       0
+#define MUXF_IOP                        1
+#define MUXF_L2SW_CLK_OUT               2
+#define MUXF_L2SW_MAC_SMI_MDC           3
+#define MUXF_L2SW_LED_FLASH0            4
+#define MUXF_L2SW_LED_FLASH1            5
+#define MUXF_L2SW_LED_ON0               6
+#define MUXF_L2SW_LED_ON1               7
+#define MUXF_L2SW_MAC_SMI_MDIO          8
+#define MUXF_L2SW_P0_MAC_RMII_TXEN      9
+#define MUXF_L2SW_P0_MAC_RMII_TXD0      10
+#define MUXF_L2SW_P0_MAC_RMII_TXD1      11
+#define MUXF_L2SW_P0_MAC_RMII_CRSDV     12
+#define MUXF_L2SW_P0_MAC_RMII_RXD0      13
+#define MUXF_L2SW_P0_MAC_RMII_RXD1      14
+#define MUXF_L2SW_P0_MAC_RMII_RXER      15
+#define MUXF_L2SW_P1_MAC_RMII_TXEN      16
+#define MUXF_L2SW_P1_MAC_RMII_TXD0      17
+#define MUXF_L2SW_P1_MAC_RMII_TXD1      18
+#define MUXF_L2SW_P1_MAC_RMII_CRSDV     19
+#define MUXF_L2SW_P1_MAC_RMII_RXD0      20
+#define MUXF_L2SW_P1_MAC_RMII_RXD1      21
+#define MUXF_L2SW_P1_MAC_RMII_RXER      22
+#define MUXF_DAISY_MODE                 23
+#define MUXF_SDIO_CLK                   24
+#define MUXF_SDIO_CMD                   25
+#define MUXF_SDIO_D0                    26
+#define MUXF_SDIO_D1                    27
+#define MUXF_SDIO_D2                    28
+#define MUXF_SDIO_D3                    29
+#define MUXF_PWM0                       30
+#define MUXF_PWM1                       31
+#define MUXF_PWM2                       32
+#define MUXF_PWM3                       33
+#define MUXF_PWM4                       34
+#define MUXF_PWM5                       35
+#define MUXF_PWM6                       36
+#define MUXF_PWM7                       37
+#define MUXF_ICM0_D                     38
+#define MUXF_ICM1_D                     39
+#define MUXF_ICM2_D                     40
+#define MUXF_ICM3_D                     41
+#define MUXF_ICM0_CLK                   42
+#define MUXF_ICM1_CLK                   43
+#define MUXF_ICM2_CLK                   44
+#define MUXF_ICM3_CLK                   45
+#define MUXF_SPIM0_INT                  46
+#define MUXF_SPIM0_CLK                  47
+#define MUXF_SPIM0_EN                   48
+#define MUXF_SPIM0_DO                   49
+#define MUXF_SPIM0_DI                   50
+#define MUXF_SPIM1_INT                  51
+#define MUXF_SPIM1_CLK                  52
+#define MUXF_SPIM1_EN                   53
+#define MUXF_SPIM1_DO                   54
+#define MUXF_SPIM1_DI                   55
+#define MUXF_SPIM2_INT                  56
+#define MUXF_SPIM2_CLK                  57
+#define MUXF_SPIM2_EN                   58
+#define MUXF_SPIM2_DO                   59
+#define MUXF_SPIM2_DI                   60
+#define MUXF_SPIM3_INT                  61
+#define MUXF_SPIM3_CLK                  62
+#define MUXF_SPIM3_EN                   63
+#define MUXF_SPIM3_DO                   64
+#define MUXF_SPIM3_DI                   65
+#define MUXF_SPI0S_INT                  66
+#define MUXF_SPI0S_CLK                  67
+#define MUXF_SPI0S_EN                   68
+#define MUXF_SPI0S_DO                   69
+#define MUXF_SPI0S_DI                   70
+#define MUXF_SPI1S_INT                  71
+#define MUXF_SPI1S_CLK                  72
+#define MUXF_SPI1S_EN                   73
+#define MUXF_SPI1S_DO                   74
+#define MUXF_SPI1S_DI                   75
+#define MUXF_SPI2S_INT                  76
+#define MUXF_SPI2S_CLK                  77
+#define MUXF_SPI2S_EN                   78
+#define MUXF_SPI2S_DO                   79
+#define MUXF_SPI2S_DI                   80
+#define MUXF_SPI3S_INT                  81
+#define MUXF_SPI3S_CLK                  82
+#define MUXF_SPI3S_EN                   83
+#define MUXF_SPI3S_DO                   84
+#define MUXF_SPI3S_DI                   85
+#define MUXF_I2CM0_CLK                  86
+#define MUXF_I2CM0_DAT                  87
+#define MUXF_I2CM1_CLK                  88
+#define MUXF_I2CM1_DAT                  89
+#define MUXF_I2CM2_CLK                  90
+#define MUXF_I2CM2_DAT                  91
+#define MUXF_I2CM3_CLK                  92
+#define MUXF_I2CM3_DAT                  93
+#define MUXF_UA1_TX                     94
+#define MUXF_UA1_RX                     95
+#define MUXF_UA1_CTS                    96
+#define MUXF_UA1_RTS                    97
+#define MUXF_UA2_TX                     98
+#define MUXF_UA2_RX                     99
+#define MUXF_UA2_CTS                    100
+#define MUXF_UA2_RTS                    101
+#define MUXF_UA3_TX                     102
+#define MUXF_UA3_RX                     103
+#define MUXF_UA3_CTS                    104
+#define MUXF_UA3_RTS                    105
+#define MUXF_UA4_TX                     106
+#define MUXF_UA4_RX                     107
+#define MUXF_UA4_CTS                    108
+#define MUXF_UA4_RTS                    109
+#define MUXF_TIMER0_INT                 110
+#define MUXF_TIMER1_INT                 111
+#define MUXF_TIMER2_INT                 112
+#define MUXF_TIMER3_INT                 113
+#define MUXF_GPIO_INT0                  114
+#define MUXF_GPIO_INT1                  115
+#define MUXF_GPIO_INT2                  116
+#define MUXF_GPIO_INT3                  117
+#define MUXF_GPIO_INT4                  118
+#define MUXF_GPIO_INT5                  119
+#define MUXF_GPIO_INT6                  120
+#define MUXF_GPIO_INT7                  121
+
+#define GROP_SPI_FLASH                  122
+#define GROP_SPI_FLASH_4BIT             123
+#define GROP_SPI_NAND                   124
+#define GROP_CARD0_EMMC                 125
+#define GROP_SD_CARD                    126
+#define GROP_UA0                        127
+#define GROP_ACHIP_DEBUG                128
+#define GROP_ACHIP_UA2AXI               129
+#define GROP_FPGA_IFX                   130
+#define GROP_HDMI_TX                    131
+#define GROP_AUD_EXT_ADC_IFX0           132
+#define GROP_AUD_EXT_DAC_IFX0           133
+#define GROP_SPDIF_RX                   134
+#define GROP_SPDIF_TX                   135
+#define GROP_TDMTX_IFX0                 136
+#define GROP_TDMRX_IFX0                 137
+#define GROP_PDMRX_IFX0                 138
+#define GROP_PCM_IEC_TX                 139
+#define GROP_LCDIF                      140
+#define GROP_DVD_DSP_DEBUG              141
+#define GROP_I2C_DEBUG                  142
+#define GROP_I2C_SLAVE                  143
+#define GROP_WAKEUP                     144
+#define GROP_UART2AXI                   145
+#define GROP_USB0_I2C                   146
+#define GROP_USB1_I2C                   147
+#define GROP_USB0_OTG                   148
+#define GROP_USB1_OTG                   149
+#define GROP_UPHY0_DEBUG                150
+#define GROP_UPHY1_DEBUG                151
+#define GROP_UPHY0_EXT                  152
+#define GROP_PROBE_PORT                 153
+#define GROP_ANA_I2C_IF                 154
+#define GROP_ANA_TEST_IF                155
+
+#endif
diff --git a/include/dt-bindings/pinctrl/sppctl.h b/include/dt-bindings/pinctrl/sppctl.h
new file mode 100644
index 0000000..3e82989
--- /dev/null
+++ b/include/dt-bindings/pinctrl/sppctl.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SP7021 pinmux pinctrl bindings.
+ * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
+ * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
+ */
+
+#ifndef _DT_BINDINGS_PINCTRL_SPPCTL_H
+#define _DT_BINDINGS_PINCTRL_SPPCTL_H
+
+#define IOP_G_MASTE             (0x01<<0)
+#define IOP_G_FIRST             (0x01<<1)
+
+#define SPPCTL_PCTL_G_PMUX      (0x00|IOP_G_MASTE)
+#define SPPCTL_PCTL_G_GPIO      (IOP_G_FIRST|IOP_G_MASTE)
+#define SPPCTL_PCTL_G_IOPP      (IOP_G_FIRST|0x00)
+
+#define SPPCTL_PCTL_L_OUT       (0x01<<0)
+#define SPPCTL_PCTL_L_OU1       (0x01<<1)
+#define SPPCTL_PCTL_L_INV       (0x01<<2)
+#define SPPCTL_PCTL_L_ONV       (0x01<<3)
+#define SPPCTL_PCTL_L_ODR       (0x01<<4)
+
+#define SPPCTL_PCTLE_P(v)       ((v)<<24)
+#define SPPCTL_PCTLE_G(v)       ((v)<<16)
+#define SPPCTL_PCTLE_F(v)       ((v)<<8)
+#define SPPCTL_PCTLE_L(v)       ((v)<<0)
+
+#define SPPCTL_PCTLD_P(v)       (((v)>>24) & 0xFF)
+#define SPPCTL_PCTLD_G(v)       (((v)>>16) & 0xFF)
+#define SPPCTL_PCTLD_F(v)       (((v) >> 8) & 0xFF)
+#define SPPCTL_PCTLD_L(v)       (((v) >> 0) & 0xFF)
+
+/*
+ * pack into 32-bit value:
+ * pin#{8bit}, typ{8bit}, function{8bit}, flags{8bit}
+ */
+#define SPPCTL_IOPAD(pin, typ, fun, fls) (((pin)<<24)|((typ)<<16)|((fun)<<8)|(fls))
+
+#endif
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v2 3/3] devicetree: bindings: pinctrl: Add bindings doc for Sunplus SP7021.
  2021-11-01  8:11 ` [PATCH v2 0/3] This is a patch series for pinctrl driver for Sunplus SP7021 SoC Wells Lu
  2021-11-01  8:11   ` [PATCH v2 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
  2021-11-01  8:11   ` [PATCH v2 2/3] dt-bindings: pinctrl: Add dt-bindings " Wells Lu
@ 2021-11-01  8:11   ` Wells Lu
  2021-11-12 15:40     ` Rob Herring
  2 siblings, 1 reply; 19+ messages in thread
From: Wells Lu @ 2021-11-01  8:11 UTC (permalink / raw)
  To: linus.walleij, linux-gpio, linux-kernel, robh+dt, devicetree
  Cc: qinjian, dvorkin, Wells Lu

Add bindings documentation for Sunplus SP7021.

Signed-off-by: Wells Lu <wells.lu@sunplus.com>
---
Changes in v2:
 - None

 .../bindings/pinctrl/sunplus,sp7021-pinctrl.yaml   | 277 +++++++++++++++++++++
 MAINTAINERS                                        |   1 +
 2 files changed, 278 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml

diff --git a/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
new file mode 100644
index 0000000..7cfa0ce
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
@@ -0,0 +1,277 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pinctrl/sunplus,sp7021-pinctrl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 Pin Controller Device Tree Bindings
+
+maintainers:
+  - Dvorkin Dmitry <dvorkin@tibbo.com>
+  - Wells Lu <wells.lu@sunplus.com>
+
+description: |
+  The Sunplus SP7021 pin controller is used to control SoC pins. Please
+  refer to pinctrl-bindings.txt in this directory for details of the common
+  pinctrl bindings used by client devices.
+
+  Refer to https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/pages/
+  1443495991/How+to+setup+pins+of+SP7021+in+device-tree+source
+
+  The device node of pin controller of Sunplus SP7021 has following
+  properties.
+
+properties:
+  compatible:
+    const: sunplus,sp7021-pctl
+
+  gpio-controller: true
+
+  '#gpio-cells':
+    const: 2
+
+  reg:
+    items:
+      - description: Base address and length of the MOON2 registers.
+      - description: Base address and length of the GPIOXT registers.
+      - description: Base address and length of the GPIOXT2 registers.
+      - description: Base address and length of the FIRST registers.
+      - description: Base address and length of the MOON1 registers.
+
+  clocks:
+    maxItems: 1
+
+  resets:
+    maxItems: 1
+
+patternProperties:
+  '^.*$':
+    if:
+      type: object
+    then:
+      description: |
+        A pinctrl node should contain at least one subnodes representing the
+        pins or function-pins group available on the machine. Each subnode
+        will list the pins it needs, and how they should be configured.
+
+        Pinctrl node's client devices use subnodes for desired pin
+        configuration. Client device subnodes use below standard properties.
+
+      properties:
+        pins:
+          description: |
+            Define pins which are used by pinctrl node's client device.
+
+            It consists of one or more integers which represents the config
+            setting for corresponding pin. Please use macro SPPCTL_IOPAD to
+            define the integers for pins.
+
+            The first argument of the macro is pin number, the second is pin
+            type, the third is type of GPIO, the last is default output state
+            of GPIO.
+          $ref: /schemas/types.yaml#/definitions/uint32-array
+
+        function:
+          description: |
+            Define pin-function which is used by pinctrl node's client device.
+            The name should be one of string in the following enumeration.
+          $ref: "/schemas/types.yaml#/definitions/string"
+          enum: [ SPI_FLASH, SPI_FLASH_4BIT, SPI_NAND, CARD0_EMMC, SD_CARD,
+                  UA0, FPGA_IFX, HDMI_TX, LCDIF, USB0_OTG, USB1_OTG ]
+
+        groups:
+          description: |
+            Define pin-group in a specified pin-function.
+            The name should be one of string in the following enumeration.
+          $ref: "/schemas/types.yaml#/definitions/string"
+          enum: [ SPI_FLASH1, SPI_FLASH2, SPI_FLASH_4BIT1, SPI_FLASH_4BIT2,
+                  SPI_NAND, CARD0_EMMC, SD_CARD, UA0, FPGA_IFX, HDMI_TX1,
+                  HDMI_TX2, HDMI_TX3, LCDIF, USB0_OTG, USB1_OTG ]
+
+        zero_func:
+          description: |
+            Disabled pins which are not used by pinctrl node's client device.
+          $ref: /schemas/types.yaml#/definitions/uint32-array
+
+      additionalProperties: false
+
+      allOf:
+        - if:
+            properties:
+              function:
+                enum:
+                  - SPI_FLASH
+          then:
+            properties:
+              groups:
+                enum:
+                  - SPI_FLASH1
+                  - SPI_FLASH2
+        - if:
+            properties:
+              function:
+                enum:
+                  - SPI_FLASH_4BIT
+          then:
+            properties:
+              groups:
+                enum:
+                  - SPI_FLASH_4BIT1
+                  - SPI_FLASH_4BIT2
+        - if:
+            properties:
+              function:
+                enum:
+                  - SPI_NAND
+          then:
+            properties:
+              groups:
+                enum:
+                  - SPI_NAND
+        - if:
+            properties:
+              function:
+                enum:
+                  - CARD0_EMMC
+          then:
+            properties:
+              groups:
+                enum:
+                  - CARD0_EMMC
+        - if:
+            properties:
+              function:
+                enum:
+                  - SD_CARD
+          then:
+            properties:
+              groups:
+                enum:
+                  - SD_CARD
+        - if:
+            properties:
+              function:
+                enum:
+                  - UA0
+          then:
+            properties:
+              groups:
+                enum:
+                  - UA0
+        - if:
+            properties:
+              function:
+                enum:
+                  - FPGA_IFX
+          then:
+            properties:
+              groups:
+                enum:
+                  - FPGA_IFX
+        - if:
+            properties:
+              function:
+                enum:
+                  - HDMI_TX
+          then:
+            properties:
+              groups:
+                enum:
+                  - HDMI_TX1
+                  - HDMI_TX2
+                  - HDMI_TX3
+        - if:
+            properties:
+              function:
+                enum:
+                  - LCDIF
+          then:
+            properties:
+              groups:
+                enum:
+                  - LCDIF
+        - if:
+            properties:
+              function:
+                enum:
+                  - USB0_OTG
+          then:
+            properties:
+              groups:
+                enum:
+                  - USB0_OTG
+        - if:
+            properties:
+              function:
+                enum:
+                  - USB1_OTG
+          then:
+            properties:
+              groups:
+                enum:
+                  - USB1_OTG
+
+required:
+  - compatible
+  - reg
+  - "#gpio-cells"
+  - gpio-controller
+  - clocks
+  - resets
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/sp-sp7021.h>
+    #include <dt-bindings/reset/sp-sp7021.h>
+    #include <dt-bindings/pinctrl/sppctl-sp7021.h>
+
+    pctl: pctl@9C000100 {
+        compatible = "sunplus,sp7021-pctl";
+        reg = <0x9C000100 0x100>, <0x9C000300 0x80>, <0x9C000380 0x80>,
+              <0x9C0032e4 0x1C>, <0x9C000080 0x20>;
+        gpio-controller;
+        #gpio-cells = <2>;
+        clocks = <&clkc GPIO>;
+        resets = <&rstc RST_GPIO>;
+
+        pins_uart0: pins_uart0 {
+            function = "UA0";
+            groups = "UA0";
+        };
+
+        pins_uart1: pins_uart1 {
+            pins = <
+                SPPCTL_IOPAD(11,SPPCTL_PCTL_G_PMUX,MUXF_UA1_TX,0)
+                SPPCTL_IOPAD(10,SPPCTL_PCTL_G_PMUX,MUXF_UA1_RX,0)
+                SPPCTL_IOPAD(7,SPPCTL_PCTL_G_GPIO,0,SPPCTL_PCTL_L_OUT)
+            >;
+        };
+
+        emmc_mux: emmc_mux {
+            function = "CARD0_EMMC";
+            groups = "CARD0_EMMC";
+        };
+
+        mmc1_mux: mmc1_mux {
+            function = "SD_CARD";
+            groups = "SD_CARD";
+            pins = < SPPCTL_IOPAD(91,SPPCTL_PCTL_G_GPIO,0,0) >;
+        };
+
+        hdmi_A_tx1: hdmi_A_tx1_pins {
+            function = "HDMI_TX";
+            groups = "HDMI_TX1";
+        };
+        hdmi_A_tx2: hdmi_A_tx2_pins {
+            function = "HDMI_TX";
+            groups = "HDMI_TX2";
+        };
+        hdmi_A_tx3: hdmi_A_tx3_pins {
+            function = "HDMI_TX";
+            groups = "HDMI_TX3";
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index da6378f..11835e7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14872,6 +14872,7 @@ M:	Wells Lu <wells.lu@sunplus.com>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
 S:	Maintained
 W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+F:	Documentation/devicetree/bindings/pinctrl/sunplus,*
 F:	drivers/pinctrl/sunplus/
 F:	include/dt-bindings/pinctrl/sppctl*
 
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [PATCH 3/3] devicetree: bindings: pinctrl: Add bindings doc for Sunplus SP7021.
  2021-10-27  8:55 ` [PATCH 3/3] devicetree: bindings: pinctrl: Add bindings doc " Wells Lu
@ 2021-11-09  3:59   ` Linus Walleij
       [not found]     ` <f315d79da3e742b4a4ec0131d6035046@sphcmbx02.sunplus.com.tw>
  0 siblings, 1 reply; 19+ messages in thread
From: Linus Walleij @ 2021-11-09  3:59 UTC (permalink / raw)
  To: Wells Lu
  Cc: linux-gpio, linux-kernel, robh+dt, devicetree, qinjian, dvorkin,
	Wells Lu

Hi Wells Lu,

thanks for your patch!

On Wed, Oct 27, 2021 at 10:55 AM Wells Lu <wellslutw@gmail.com> wrote:

> +      properties:
> +        pins:
> +          description: |
> +            Define pins which are used by pinctrl node's client device.
> +
> +            It consists of one or more integers which represents the config
> +            setting for corresponding pin. Please use macro SPPCTL_IOPAD to
> +            define the integers for pins.
> +
> +            The first argument of the macro is pin number, the second is pin
> +            type, the third is type of GPIO, the last is default output state
> +            of GPIO.
> +          $ref: /schemas/types.yaml#/definitions/uint32-array
> +
> +        function:
> +          description: |
> +            Define pin-function which is used by pinctrl node's client device.
> +            The name should be one of string in the following enumeration.
> +          $ref: "/schemas/types.yaml#/definitions/string"
> +          enum: [ SPI_FLASH, SPI_FLASH_4BIT, SPI_NAND, CARD0_EMMC, SD_CARD,
> +                  UA0, FPGA_IFX, HDMI_TX, LCDIF, USB0_OTG, USB1_OTG ]
> +
> +        groups:
> +          description: |
> +            Define pin-group in a specified pin-function.
> +            The name should be one of string in the following enumeration.
> +          $ref: "/schemas/types.yaml#/definitions/string"
> +          enum: [ SPI_FLASH1, SPI_FLASH2, SPI_FLASH_4BIT1, SPI_FLASH_4BIT2,
> +                  SPI_NAND, CARD0_EMMC, SD_CARD, UA0, FPGA_IFX, HDMI_TX1,
> +                  HDMI_TX2, HDMI_TX3, LCDIF, USB0_OTG, USB1_OTG ]

Is it possible to use
Documentation/devicetree/bindings/pinctrl/pinmux-node.yaml
for this like other drivers do?

> +        zero_func:
> +          description: |
> +            Disabled pins which are not used by pinctrl node's client device.
> +          $ref: /schemas/types.yaml#/definitions/uint32-array

I have never seen this before. Can't you just use pin control hogs
for this so the pin controller just take care of these pins?

> +      allOf:
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - SPI_FLASH
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - SPI_FLASH1
> +                  - SPI_FLASH2
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - SPI_FLASH_4BIT
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - SPI_FLASH_4BIT1
> +                  - SPI_FLASH_4BIT2
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - SPI_NAND
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - SPI_NAND
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - CARD0_EMMC
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - CARD0_EMMC
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - SD_CARD
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - SD_CARD
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - UA0
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - UA0
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - FPGA_IFX
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - FPGA_IFX
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - HDMI_TX
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - HDMI_TX1
> +                  - HDMI_TX2
> +                  - HDMI_TX3
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - LCDIF
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - LCDIF
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - USB0_OTG
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - USB0_OTG
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - USB1_OTG
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - USB1_OTG

This looks complex to me, I need feedback from bindings people
on this.

> +        pins_uart0: pins_uart0 {
> +            function = "UA0";
> +            groups = "UA0";
> +        };
> +
> +        pins_uart1: pins_uart1 {
> +            pins = <
> +                SPPCTL_IOPAD(11,SPPCTL_PCTL_G_PMUX,MUXF_UA1_TX,0)
> +                SPPCTL_IOPAD(10,SPPCTL_PCTL_G_PMUX,MUXF_UA1_RX,0)
> +                SPPCTL_IOPAD(7,SPPCTL_PCTL_G_GPIO,0,SPPCTL_PCTL_L_OUT)
> +            >;
> +        };

This first looks like two ways to do the same thing?
UART0 uses strings for group + function and uart1 control
individual pins.

Is it possible to just do it one way?

I think the pins = <...> scheme includes also multiplexing settings
and then it should be named pinmux = <...>:

Please read
Documentation/devicetree/bindings/pinctrl/pinmux-node.yaml
closely.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 1/3] pinctrl: Add driver for Sunplus SP7021
  2021-11-01  8:11   ` [PATCH v2 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
@ 2021-11-09  4:30     ` Linus Walleij
  2021-11-17  8:35       ` Wells Lu 呂芳騰
  0 siblings, 1 reply; 19+ messages in thread
From: Linus Walleij @ 2021-11-09  4:30 UTC (permalink / raw)
  To: Wells Lu
  Cc: linux-gpio, linux-kernel, robh+dt, devicetree, qinjian, dvorkin,
	Wells Lu

Hi Wells Lu,

thanks for your patch!

This driver needs a bit of work, I will point out some things and I
think it will be quite different if we also change the bindings.

On Mon, Nov 1, 2021 at 9:11 AM Wells Lu <wellslutw@gmail.com> wrote:

> +config PINCTRL_SPPCTL
> +       bool "Sunplus SP7021 pinmux and GPIO driver"
> +       depends on SOC_SP7021
> +       depends on OF && HAS_IOMEM
> +       select PINMUX
> +       select GENERIC_PINCTRL_GROUPS
> +       select GENERIC_PINMUX_FUNCTIONS
> +       select PINCONF
> +       select GENERIC_PINCONF
> +       select OF_GPIO
> +       select GPIOLIB
> +       select GPIO_SYSFS

Don't do this, sysfs is deprecated.

> +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl.o
> +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_pinctrl.o
> +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_sysfs.o
> +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_gpio_ops.o
> +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_gpio.o
> +obj-$(CONFIG_PINCTRL_SPPCTL) += pinctrl_inf_sp7021.o
> +obj-$(CONFIG_PINCTRL_SPPCTL) += gpio_inf_sp7021.o

This multitide of files makes this a bit hard to read and review,
usually pin controllers are in one-two files for a single SoC.

> + * 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; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * 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.

Drop this boilerplate on all files and just use the SPDX tag.

> +const size_t GPIS_listSZ = sizeof(sppctlgpio_list_s)/sizeof(*(sppctlgpio_list_s));

Use only lowercase in variable names.
This looks like a reimplementation of ARRAY_SIZE(),
replace with that if this is the case.

> +const size_t sppctlpins_allSZ = ARRAY_SIZE(sppctlpins_all);

Instead of defining consts for random sizes like this,
just inline ARRAY_SIZE() where you use it.

> +// gpio: is defined in gpio_inf_sp7021.c
> +const size_t PMUX_listSZ = sizeof(sppctlpmux_list_s)/sizeof(*(sppctlpmux_list_s));

Same comment as above. Etc.

> +/* CEC pin is not used. Release it for others. */
> +//static const unsigned int pins_hdmi1[] = { D(10, 6), D(10, 7), D(12, 2), D(12, 1) };
> +//static const unsigned int pins_hdmi2[] = { D(8, 3), D(8, 4), D(8, 5), D(8, 6) };
> +//static const unsigned int pins_hdmi3[] = { D(7, 4), D(7, 5), D(7, 6), D(7, 7) };

Don't leave commented-out code in the driver. Delete
all this stuff.

> +void print_device_tree_node(struct device_node *node, int depth)
> +{
> +       int i = 0;
> +       struct device_node *child;
> +       struct property    *properties;
> +       char                indent[255] = "";
> +
> +       for (i = 0; i < depth * 3; i++)
> +               indent[i] = ' ';
> +       indent[i] = '\0';
> +
> +       ++depth;
> +       if (depth == 1) {
> +               pr_info("%s{ name = %s\n", indent, node->name);
> +               for (properties = node->properties; properties != NULL;
> +                       properties = properties->next)
> +                       pr_info("%s  %s (%d)\n", indent, properties->name, properties->length);
> +               pr_info("%s}\n", indent);
> +       }
> +
> +       for_each_child_of_node(node, child) {
> +               pr_info("%s{ name = %s\n", indent, child->name);
> +               for (properties = child->properties; properties != NULL;
> +                       properties = properties->next)
> +                       pr_info("%s  %s (%d)\n", indent, properties->name, properties->length);
> +               print_device_tree_node(child, depth);
> +               pr_info("%s}\n", indent);
> +       }
> +}

This kind of debugging code should be deleted or use what
is in the device tree core.

> +void sppctl_gmx_set(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff, uint8_t _bsiz,
> +                   uint8_t _rval)
> +{
> +       uint32_t *r;

Don't use uint8_t or uint16_t or uint32_t, use the kernel
short forms u8, u16 or u32, simply.

Don't start any variable names with _underscore, it i a
big confusion for the head because it has ambigous
semantics.

Try to find concise descriptive variable names.

> +       struct sppctl_reg_t x = { .m = (~(~0 << _bsiz)) << _boff,
> +                                 .v = ((uint16_t)_rval) << _boff };
> +
> +       if (_p->debug > 1)
> +               KDBG(_p->pcdp->dev, "%s(x%X,x%X,x%X,x%X) m:x%X v:x%X\n",
> +                    __func__, _roff, _boff, _bsiz, _rval, x.m, x.v);

Do not reinvent kernel debugging use the dev_dbg() macro.

> +       r = (uint32_t *)&x;

Try to avoid casting like this. It is usually a sign that something is wrong.

> +       if (_fun % 2 == 0)
> +               ;
> +       else {
> +               x.v <<= 8;
> +               x.m <<= 8;
> +       }

This is code that is incredibly terse and deviant from the kernels
general style. Please read a few other pin control drivers and
familiarize with how these drivers usually look.

> +uint8_t sppctl_fun_get(struct sppctl_pdata_t *_p,  uint8_t _fun)
> +{
> +       uint8_t pin = 0x00;
> +       uint8_t func = (_fun >> 1) << 2;

This looks like shting to get rid of bit 0.
Just use bitwise logic instead.

> +       ret = request_firmware_nowait(THIS_MODULE, true, _fwname, _dev, GFP_KERNEL, p,
> +                                     sppctl_fwload_cb);

So this pin controller needs a firmware? That is the first time
I have ever seen that. Please add comments describing what this
firmware is and what it does, also explain it in the commit
message.

> +int sppctl_pctl_resmap(struct platform_device *_pd, struct sppctl_pdata_t *_pc)
> +{
> +       struct resource *rp;
> +
> +       // resF
> +       rp = platform_get_resource(_pd, IORESOURCE_MEM, 0);
> +       if (IS_ERR(rp)) {
> +               KERR(&(_pd->dev), "%s get res#F ERR\n", __func__);
> +               return PTR_ERR(rp);
> +       }
> +       KDBG(&(_pd->dev), "mres #F:%p\n", rp);

Thes resF etc are very terse and hard to understand. It seems written
by someone who knows everything of what they are doing but with
very little interest to explain it to others. Code readability is important.

> +static struct platform_driver sppctl_driver = {
> +       .driver = {
> +               .name           = MNAME,

Don't abbreviate so compulsively.
SP7021_MODULE_NAME is fine.

> +static int __init sppctl_drv_reg(void)
> +{
> +       return platform_driver_register(&sppctl_driver);
> +}
> +postcore_initcall(sppctl_drv_reg);

Why do you need a postcore_initcall()?

> +MODULE_AUTHOR(M_AUT1);
> +MODULE_AUTHOR(M_AUT2);
> +MODULE_DESCRIPTION(M_NAM);
> +MODULE_LICENSE(M_LIC);

Just inline the strings, all other drivers do.

> +#define MNAME "sppctl"
> +#define M_LIC "GPL v2"
> +#define M_AUT1 "Dvorkin Dmitry <dvorkin@tibbo.com>"
> +#define M_AUT2 "Wells Lu <wells.lu@sunplus.com>"
> +#define M_NAM "SP7021 PinCtl"
> +#define M_ORG "Sunplus/Tibbo Tech."
> +#define M_CPR "(C) 2020"

This is too much and too abbreviated names, just use
the strings directly in the macros.

> +#include <linux/version.h>

Why?

> +#include <linux/of_gpio.h>

Never use this include in new code. It is legacy.

> +#define SPPCTL_MAX_NAM 64
> +#define SPPCTL_MAX_BUF PAGE_SIZE
> +
> +#define KINF(pd, fmt, args...) \
> +       do { \
> +               if ((pd) != NULL) \
> +                       dev_info((pd), fmt, ##args); \
> +               else \
> +                       pr_info(MNAME ": " fmt, ##args); \
> +       } while (0)
> +#define KERR(pd, fmt, args...) \
> +       do { \
> +               if ((pd) != NULL) \
> +                       dev_info((pd), fmt, ##args); \
> +               else \
> +                       pr_err(MNAME ": " fmt, ##args); \
> +       } while (0)
> +#ifdef CONFIG_PINCTRL_SPPCTL_DEBUG
> +#define KDBG(pd, fmt, args...) \
> +       do { \
> +               if ((pd) != NULL) \
> +                       dev_info((pd), fmt, ##args); \
> +               else \
> +                       pr_debug(MNAME ": " fmt, ##args); \
> +       } while (0)
> +#else
> +#define KDBG(pd, fmt, args...)
> +#endif

Don't reimplement kernel debugging use dev_dbg(), dev_info()
dev_err() etc directly. I don't see why you need
CONFIG_PINCTRL_SPPCTL_DEBUG at all, if you absolutely
want to control debugging for these files only just use
this in your Makefile

subdir-ccflags-$(CONFIG_PINCTRL_SPPCTL_DEBUG)  := -DDEBUG

This will turn on/off the output from dev_dbg().

> +struct sppctl_pdata_t {
> +       char name[SPPCTL_MAX_NAM];
> +       uint8_t debug;

Don't use u8 for things like this use bool.

> +       char fwname[SPPCTL_MAX_NAM];
> +       void *sysfs_sdp;
> +       void __iomem *baseF;    // functions
> +       void __iomem *base0;    // MASTER , OE , OUT , IN
> +       void __iomem *base1;    // I_INV , O_INV , OD
> +       void __iomem *base2;    // GPIO_FIRST
> +       void __iomem *baseI;    // IOP
> +       // pinctrl-related
> +       struct pinctrl_desc pdesc;
> +       struct pinctrl_dev *pcdp;
> +       struct pinctrl_gpio_range gpio_range;
> +       struct sppctlgpio_chip_t *gpiod;

*gpiod is a bad name because we use it quite a lot
in the kernel for GPIO descriptors.

> +struct sppctl_reg_t {
> +       uint16_t v;     // value part
> +       uint16_t m;     // mask part
> +};

These are not types (no typedef) so don't add *_t
suffixes, just drop those everywhere.

> +       const char * const name;
> +       const uint8_t gval;             // value for register
> +       const unsigned * const pins;    // list of pins
> +       const unsigned int pnum;        // number of pins

Use kerneldoc to document struct members.

There will be many more comments but work on these things
to begin with!

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 2/3] dt-bindings: pinctrl: Add dt-bindings for Sunplus SP7021
  2021-11-01  8:11   ` [PATCH v2 2/3] dt-bindings: pinctrl: Add dt-bindings " Wells Lu
@ 2021-11-12 15:37     ` Rob Herring
  2021-11-18  9:03       ` Wells Lu 呂芳騰
  0 siblings, 1 reply; 19+ messages in thread
From: Rob Herring @ 2021-11-12 15:37 UTC (permalink / raw)
  To: Wells Lu
  Cc: linus.walleij, linux-gpio, linux-kernel, devicetree, qinjian,
	dvorkin, Wells Lu

On Mon, Nov 01, 2021 at 04:11:16PM +0800, Wells Lu wrote:
> Add dt-bindings header files for Sunplus SP7021 SoC.
> 
> Signed-off-by: Wells Lu <wells.lu@sunplus.com>
> ---
> Changes in v2:
>  - Added more 'defines' in dt-bindings header files (forgot to add in v1).
> 
>  MAINTAINERS                                 |   1 +
>  include/dt-bindings/pinctrl/sppctl-sp7021.h | 171 ++++++++++++++++++++++++++++
>  include/dt-bindings/pinctrl/sppctl.h        |  40 +++++++
>  3 files changed, 212 insertions(+)
>  create mode 100644 include/dt-bindings/pinctrl/sppctl-sp7021.h
>  create mode 100644 include/dt-bindings/pinctrl/sppctl.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index fd82c77..da6378f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14873,6 +14873,7 @@ L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
>  S:	Maintained
>  W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
>  F:	drivers/pinctrl/sunplus/
> +F:	include/dt-bindings/pinctrl/sppctl*
>  
>  PKTCDVD DRIVER
>  M:	linux-block@vger.kernel.org
> diff --git a/include/dt-bindings/pinctrl/sppctl-sp7021.h b/include/dt-bindings/pinctrl/sppctl-sp7021.h
> new file mode 100644
> index 0000000..4e07d03
> --- /dev/null
> +++ b/include/dt-bindings/pinctrl/sppctl-sp7021.h
> @@ -0,0 +1,171 @@
> +/* SPDX-License-Identifier: GPL-2.0 */

Care about OS other than Linux? Should be dual licensed.

> +/*
> + * SP7021 pinmux pinctrl bindings.
> + * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
> + * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
> + */
> +
> +#ifndef _DT_BINDINGS_PINCTRL_SPPCTL_SP7021_H
> +#define _DT_BINDINGS_PINCTRL_SPPCTL_SP7021_H
> +
> +#include <dt-bindings/pinctrl/sppctl.h>
> +
> +#define MUXF_GPIO                       0

Where do these numbers come from? Hopefully they correspond to register 
offsets and aren't made up. A comment either way here would help.

> +#define MUXF_IOP                        1
> +#define MUXF_L2SW_CLK_OUT               2
> +#define MUXF_L2SW_MAC_SMI_MDC           3
> +#define MUXF_L2SW_LED_FLASH0            4
> +#define MUXF_L2SW_LED_FLASH1            5
> +#define MUXF_L2SW_LED_ON0               6
> +#define MUXF_L2SW_LED_ON1               7
> +#define MUXF_L2SW_MAC_SMI_MDIO          8
> +#define MUXF_L2SW_P0_MAC_RMII_TXEN      9
> +#define MUXF_L2SW_P0_MAC_RMII_TXD0      10
> +#define MUXF_L2SW_P0_MAC_RMII_TXD1      11
> +#define MUXF_L2SW_P0_MAC_RMII_CRSDV     12
> +#define MUXF_L2SW_P0_MAC_RMII_RXD0      13
> +#define MUXF_L2SW_P0_MAC_RMII_RXD1      14
> +#define MUXF_L2SW_P0_MAC_RMII_RXER      15
> +#define MUXF_L2SW_P1_MAC_RMII_TXEN      16
> +#define MUXF_L2SW_P1_MAC_RMII_TXD0      17
> +#define MUXF_L2SW_P1_MAC_RMII_TXD1      18
> +#define MUXF_L2SW_P1_MAC_RMII_CRSDV     19
> +#define MUXF_L2SW_P1_MAC_RMII_RXD0      20
> +#define MUXF_L2SW_P1_MAC_RMII_RXD1      21
> +#define MUXF_L2SW_P1_MAC_RMII_RXER      22
> +#define MUXF_DAISY_MODE                 23
> +#define MUXF_SDIO_CLK                   24
> +#define MUXF_SDIO_CMD                   25
> +#define MUXF_SDIO_D0                    26
> +#define MUXF_SDIO_D1                    27
> +#define MUXF_SDIO_D2                    28
> +#define MUXF_SDIO_D3                    29
> +#define MUXF_PWM0                       30
> +#define MUXF_PWM1                       31
> +#define MUXF_PWM2                       32
> +#define MUXF_PWM3                       33
> +#define MUXF_PWM4                       34
> +#define MUXF_PWM5                       35
> +#define MUXF_PWM6                       36
> +#define MUXF_PWM7                       37
> +#define MUXF_ICM0_D                     38
> +#define MUXF_ICM1_D                     39
> +#define MUXF_ICM2_D                     40
> +#define MUXF_ICM3_D                     41
> +#define MUXF_ICM0_CLK                   42
> +#define MUXF_ICM1_CLK                   43
> +#define MUXF_ICM2_CLK                   44
> +#define MUXF_ICM3_CLK                   45
> +#define MUXF_SPIM0_INT                  46
> +#define MUXF_SPIM0_CLK                  47
> +#define MUXF_SPIM0_EN                   48
> +#define MUXF_SPIM0_DO                   49
> +#define MUXF_SPIM0_DI                   50
> +#define MUXF_SPIM1_INT                  51
> +#define MUXF_SPIM1_CLK                  52
> +#define MUXF_SPIM1_EN                   53
> +#define MUXF_SPIM1_DO                   54
> +#define MUXF_SPIM1_DI                   55
> +#define MUXF_SPIM2_INT                  56
> +#define MUXF_SPIM2_CLK                  57
> +#define MUXF_SPIM2_EN                   58
> +#define MUXF_SPIM2_DO                   59
> +#define MUXF_SPIM2_DI                   60
> +#define MUXF_SPIM3_INT                  61
> +#define MUXF_SPIM3_CLK                  62
> +#define MUXF_SPIM3_EN                   63
> +#define MUXF_SPIM3_DO                   64
> +#define MUXF_SPIM3_DI                   65
> +#define MUXF_SPI0S_INT                  66
> +#define MUXF_SPI0S_CLK                  67
> +#define MUXF_SPI0S_EN                   68
> +#define MUXF_SPI0S_DO                   69
> +#define MUXF_SPI0S_DI                   70
> +#define MUXF_SPI1S_INT                  71
> +#define MUXF_SPI1S_CLK                  72
> +#define MUXF_SPI1S_EN                   73
> +#define MUXF_SPI1S_DO                   74
> +#define MUXF_SPI1S_DI                   75
> +#define MUXF_SPI2S_INT                  76
> +#define MUXF_SPI2S_CLK                  77
> +#define MUXF_SPI2S_EN                   78
> +#define MUXF_SPI2S_DO                   79
> +#define MUXF_SPI2S_DI                   80
> +#define MUXF_SPI3S_INT                  81
> +#define MUXF_SPI3S_CLK                  82
> +#define MUXF_SPI3S_EN                   83
> +#define MUXF_SPI3S_DO                   84
> +#define MUXF_SPI3S_DI                   85
> +#define MUXF_I2CM0_CLK                  86
> +#define MUXF_I2CM0_DAT                  87
> +#define MUXF_I2CM1_CLK                  88
> +#define MUXF_I2CM1_DAT                  89
> +#define MUXF_I2CM2_CLK                  90
> +#define MUXF_I2CM2_DAT                  91
> +#define MUXF_I2CM3_CLK                  92
> +#define MUXF_I2CM3_DAT                  93
> +#define MUXF_UA1_TX                     94
> +#define MUXF_UA1_RX                     95
> +#define MUXF_UA1_CTS                    96
> +#define MUXF_UA1_RTS                    97
> +#define MUXF_UA2_TX                     98
> +#define MUXF_UA2_RX                     99
> +#define MUXF_UA2_CTS                    100
> +#define MUXF_UA2_RTS                    101
> +#define MUXF_UA3_TX                     102
> +#define MUXF_UA3_RX                     103
> +#define MUXF_UA3_CTS                    104
> +#define MUXF_UA3_RTS                    105
> +#define MUXF_UA4_TX                     106
> +#define MUXF_UA4_RX                     107
> +#define MUXF_UA4_CTS                    108
> +#define MUXF_UA4_RTS                    109
> +#define MUXF_TIMER0_INT                 110
> +#define MUXF_TIMER1_INT                 111
> +#define MUXF_TIMER2_INT                 112
> +#define MUXF_TIMER3_INT                 113
> +#define MUXF_GPIO_INT0                  114
> +#define MUXF_GPIO_INT1                  115
> +#define MUXF_GPIO_INT2                  116
> +#define MUXF_GPIO_INT3                  117
> +#define MUXF_GPIO_INT4                  118
> +#define MUXF_GPIO_INT5                  119
> +#define MUXF_GPIO_INT6                  120
> +#define MUXF_GPIO_INT7                  121
> +
> +#define GROP_SPI_FLASH                  122
> +#define GROP_SPI_FLASH_4BIT             123
> +#define GROP_SPI_NAND                   124
> +#define GROP_CARD0_EMMC                 125
> +#define GROP_SD_CARD                    126
> +#define GROP_UA0                        127
> +#define GROP_ACHIP_DEBUG                128
> +#define GROP_ACHIP_UA2AXI               129
> +#define GROP_FPGA_IFX                   130
> +#define GROP_HDMI_TX                    131
> +#define GROP_AUD_EXT_ADC_IFX0           132
> +#define GROP_AUD_EXT_DAC_IFX0           133
> +#define GROP_SPDIF_RX                   134
> +#define GROP_SPDIF_TX                   135
> +#define GROP_TDMTX_IFX0                 136
> +#define GROP_TDMRX_IFX0                 137
> +#define GROP_PDMRX_IFX0                 138
> +#define GROP_PCM_IEC_TX                 139
> +#define GROP_LCDIF                      140
> +#define GROP_DVD_DSP_DEBUG              141
> +#define GROP_I2C_DEBUG                  142
> +#define GROP_I2C_SLAVE                  143
> +#define GROP_WAKEUP                     144
> +#define GROP_UART2AXI                   145
> +#define GROP_USB0_I2C                   146
> +#define GROP_USB1_I2C                   147
> +#define GROP_USB0_OTG                   148
> +#define GROP_USB1_OTG                   149
> +#define GROP_UPHY0_DEBUG                150
> +#define GROP_UPHY1_DEBUG                151
> +#define GROP_UPHY0_EXT                  152
> +#define GROP_PROBE_PORT                 153
> +#define GROP_ANA_I2C_IF                 154
> +#define GROP_ANA_TEST_IF                155
> +
> +#endif
> diff --git a/include/dt-bindings/pinctrl/sppctl.h b/include/dt-bindings/pinctrl/sppctl.h
> new file mode 100644
> index 0000000..3e82989
> --- /dev/null
> +++ b/include/dt-bindings/pinctrl/sppctl.h
> @@ -0,0 +1,40 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * SP7021 pinmux pinctrl bindings.
> + * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
> + * Author: Dvorkin Dmitry <dvorkin@tibbo.com>
> + */
> +
> +#ifndef _DT_BINDINGS_PINCTRL_SPPCTL_H
> +#define _DT_BINDINGS_PINCTRL_SPPCTL_H
> +
> +#define IOP_G_MASTE             (0x01<<0)
> +#define IOP_G_FIRST             (0x01<<1)
> +
> +#define SPPCTL_PCTL_G_PMUX      (0x00|IOP_G_MASTE)
> +#define SPPCTL_PCTL_G_GPIO      (IOP_G_FIRST|IOP_G_MASTE)
> +#define SPPCTL_PCTL_G_IOPP      (IOP_G_FIRST|0x00)
> +
> +#define SPPCTL_PCTL_L_OUT       (0x01<<0)
> +#define SPPCTL_PCTL_L_OU1       (0x01<<1)
> +#define SPPCTL_PCTL_L_INV       (0x01<<2)
> +#define SPPCTL_PCTL_L_ONV       (0x01<<3)
> +#define SPPCTL_PCTL_L_ODR       (0x01<<4)
> +
> +#define SPPCTL_PCTLE_P(v)       ((v)<<24)
> +#define SPPCTL_PCTLE_G(v)       ((v)<<16)
> +#define SPPCTL_PCTLE_F(v)       ((v)<<8)
> +#define SPPCTL_PCTLE_L(v)       ((v)<<0)
> +
> +#define SPPCTL_PCTLD_P(v)       (((v)>>24) & 0xFF)
> +#define SPPCTL_PCTLD_G(v)       (((v)>>16) & 0xFF)
> +#define SPPCTL_PCTLD_F(v)       (((v) >> 8) & 0xFF)
> +#define SPPCTL_PCTLD_L(v)       (((v) >> 0) & 0xFF)
> +
> +/*
> + * pack into 32-bit value:
> + * pin#{8bit}, typ{8bit}, function{8bit}, flags{8bit}
> + */
> +#define SPPCTL_IOPAD(pin, typ, fun, fls) (((pin)<<24)|((typ)<<16)|((fun)<<8)|(fls))
> +
> +#endif
> -- 
> 2.7.4
> 
> 

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2 3/3] devicetree: bindings: pinctrl: Add bindings doc for Sunplus SP7021.
  2021-11-01  8:11   ` [PATCH v2 3/3] devicetree: bindings: pinctrl: Add bindings doc " Wells Lu
@ 2021-11-12 15:40     ` Rob Herring
  2021-11-18  9:15       ` Wells Lu 呂芳騰
  0 siblings, 1 reply; 19+ messages in thread
From: Rob Herring @ 2021-11-12 15:40 UTC (permalink / raw)
  To: Wells Lu
  Cc: linus.walleij, linux-gpio, linux-kernel, devicetree, qinjian,
	dvorkin, Wells Lu

On Mon, Nov 01, 2021 at 04:11:17PM +0800, Wells Lu wrote:
> Add bindings documentation for Sunplus SP7021.

Patch 2 and 3 can be combined. Use consistent subjects. Patch 2 is good. 
This one is not.

> 
> Signed-off-by: Wells Lu <wells.lu@sunplus.com>
> ---
> Changes in v2:
>  - None
> 
>  .../bindings/pinctrl/sunplus,sp7021-pinctrl.yaml   | 277 +++++++++++++++++++++
>  MAINTAINERS                                        |   1 +
>  2 files changed, 278 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
> 
> diff --git a/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
> new file mode 100644
> index 0000000..7cfa0ce
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
> @@ -0,0 +1,277 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +# Copyright (C) Sunplus Co., Ltd. 2021
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pinctrl/sunplus,sp7021-pinctrl.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Sunplus SP7021 Pin Controller Device Tree Bindings
> +
> +maintainers:
> +  - Dvorkin Dmitry <dvorkin@tibbo.com>
> +  - Wells Lu <wells.lu@sunplus.com>
> +
> +description: |
> +  The Sunplus SP7021 pin controller is used to control SoC pins. Please
> +  refer to pinctrl-bindings.txt in this directory for details of the common
> +  pinctrl bindings used by client devices.
> +
> +  Refer to https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/pages/
> +  1443495991/How+to+setup+pins+of+SP7021+in+device-tree+source
> +
> +  The device node of pin controller of Sunplus SP7021 has following
> +  properties.
> +
> +properties:
> +  compatible:
> +    const: sunplus,sp7021-pctl
> +
> +  gpio-controller: true
> +
> +  '#gpio-cells':
> +    const: 2
> +
> +  reg:
> +    items:
> +      - description: Base address and length of the MOON2 registers.
> +      - description: Base address and length of the GPIOXT registers.
> +      - description: Base address and length of the GPIOXT2 registers.
> +      - description: Base address and length of the FIRST registers.
> +      - description: Base address and length of the MOON1 registers.
> +
> +  clocks:
> +    maxItems: 1
> +
> +  resets:
> +    maxItems: 1
> +
> +patternProperties:
> +  '^.*$':
> +    if:
> +      type: object
> +    then:

For new bindings, don't use this hack. Make the node name something you 
can match on (e.g. '-pins$').

> +      description: |
> +        A pinctrl node should contain at least one subnodes representing the
> +        pins or function-pins group available on the machine. Each subnode
> +        will list the pins it needs, and how they should be configured.
> +
> +        Pinctrl node's client devices use subnodes for desired pin
> +        configuration. Client device subnodes use below standard properties.
> +
> +      properties:
> +        pins:
> +          description: |
> +            Define pins which are used by pinctrl node's client device.
> +
> +            It consists of one or more integers which represents the config
> +            setting for corresponding pin. Please use macro SPPCTL_IOPAD to
> +            define the integers for pins.
> +
> +            The first argument of the macro is pin number, the second is pin
> +            type, the third is type of GPIO, the last is default output state
> +            of GPIO.
> +          $ref: /schemas/types.yaml#/definitions/uint32-array
> +
> +        function:
> +          description: |
> +            Define pin-function which is used by pinctrl node's client device.
> +            The name should be one of string in the following enumeration.
> +          $ref: "/schemas/types.yaml#/definitions/string"
> +          enum: [ SPI_FLASH, SPI_FLASH_4BIT, SPI_NAND, CARD0_EMMC, SD_CARD,
> +                  UA0, FPGA_IFX, HDMI_TX, LCDIF, USB0_OTG, USB1_OTG ]
> +
> +        groups:
> +          description: |
> +            Define pin-group in a specified pin-function.
> +            The name should be one of string in the following enumeration.
> +          $ref: "/schemas/types.yaml#/definitions/string"
> +          enum: [ SPI_FLASH1, SPI_FLASH2, SPI_FLASH_4BIT1, SPI_FLASH_4BIT2,
> +                  SPI_NAND, CARD0_EMMC, SD_CARD, UA0, FPGA_IFX, HDMI_TX1,
> +                  HDMI_TX2, HDMI_TX3, LCDIF, USB0_OTG, USB1_OTG ]
> +
> +        zero_func:
> +          description: |
> +            Disabled pins which are not used by pinctrl node's client device.
> +          $ref: /schemas/types.yaml#/definitions/uint32-array
> +
> +      additionalProperties: false
> +
> +      allOf:
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - SPI_FLASH
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - SPI_FLASH1
> +                  - SPI_FLASH2
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - SPI_FLASH_4BIT
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - SPI_FLASH_4BIT1
> +                  - SPI_FLASH_4BIT2
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - SPI_NAND
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - SPI_NAND
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - CARD0_EMMC
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - CARD0_EMMC
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - SD_CARD
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - SD_CARD
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - UA0
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - UA0
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - FPGA_IFX
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - FPGA_IFX
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - HDMI_TX
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - HDMI_TX1
> +                  - HDMI_TX2
> +                  - HDMI_TX3
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - LCDIF
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - LCDIF
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - USB0_OTG
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - USB0_OTG
> +        - if:
> +            properties:
> +              function:
> +                enum:
> +                  - USB1_OTG
> +          then:
> +            properties:
> +              groups:
> +                enum:
> +                  - USB1_OTG
> +
> +required:
> +  - compatible
> +  - reg
> +  - "#gpio-cells"
> +  - gpio-controller
> +  - clocks
> +  - resets
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/sp-sp7021.h>
> +    #include <dt-bindings/reset/sp-sp7021.h>
> +    #include <dt-bindings/pinctrl/sppctl-sp7021.h>
> +
> +    pctl: pctl@9C000100 {

pinctl@9c000100

> +        compatible = "sunplus,sp7021-pctl";
> +        reg = <0x9C000100 0x100>, <0x9C000300 0x80>, <0x9C000380 0x80>,
> +              <0x9C0032e4 0x1C>, <0x9C000080 0x20>;
> +        gpio-controller;
> +        #gpio-cells = <2>;
> +        clocks = <&clkc GPIO>;
> +        resets = <&rstc RST_GPIO>;
> +
> +        pins_uart0: pins_uart0 {
> +            function = "UA0";
> +            groups = "UA0";
> +        };
> +
> +        pins_uart1: pins_uart1 {
> +            pins = <
> +                SPPCTL_IOPAD(11,SPPCTL_PCTL_G_PMUX,MUXF_UA1_TX,0)
> +                SPPCTL_IOPAD(10,SPPCTL_PCTL_G_PMUX,MUXF_UA1_RX,0)
> +                SPPCTL_IOPAD(7,SPPCTL_PCTL_G_GPIO,0,SPPCTL_PCTL_L_OUT)
> +            >;
> +        };
> +
> +        emmc_mux: emmc_mux {
> +            function = "CARD0_EMMC";
> +            groups = "CARD0_EMMC";
> +        };
> +
> +        mmc1_mux: mmc1_mux {
> +            function = "SD_CARD";
> +            groups = "SD_CARD";
> +            pins = < SPPCTL_IOPAD(91,SPPCTL_PCTL_G_GPIO,0,0) >;
> +        };
> +
> +        hdmi_A_tx1: hdmi_A_tx1_pins {
> +            function = "HDMI_TX";
> +            groups = "HDMI_TX1";
> +        };
> +        hdmi_A_tx2: hdmi_A_tx2_pins {
> +            function = "HDMI_TX";
> +            groups = "HDMI_TX2";
> +        };
> +        hdmi_A_tx3: hdmi_A_tx3_pins {
> +            function = "HDMI_TX";
> +            groups = "HDMI_TX3";
> +        };
> +    };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index da6378f..11835e7 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14872,6 +14872,7 @@ M:	Wells Lu <wells.lu@sunplus.com>
>  L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
>  S:	Maintained
>  W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
> +F:	Documentation/devicetree/bindings/pinctrl/sunplus,*
>  F:	drivers/pinctrl/sunplus/
>  F:	include/dt-bindings/pinctrl/sppctl*
>  
> -- 
> 2.7.4
> 
> 

^ permalink raw reply	[flat|nested] 19+ messages in thread

* RE: [PATCH v2 1/3] pinctrl: Add driver for Sunplus SP7021
  2021-11-09  4:30     ` Linus Walleij
@ 2021-11-17  8:35       ` Wells Lu 呂芳騰
  0 siblings, 0 replies; 19+ messages in thread
From: Wells Lu 呂芳騰 @ 2021-11-17  8:35 UTC (permalink / raw)
  To: Linus Walleij, Wells Lu
  Cc: linux-gpio, linux-kernel, robh+dt, devicetree, qinjian, dvorkin

Hi Linus,

Thanks for review and sorry for late reply.


> Hi Wells Lu,
> 
> thanks for your patch!
> 
> This driver needs a bit of work, I will point out some things and I think it will be quite
> different if we also change the bindings.
> 
> On Mon, Nov 1, 2021 at 9:11 AM Wells Lu <wellslutw@gmail.com> wrote:
> 
> > +config PINCTRL_SPPCTL
> > +       bool "Sunplus SP7021 pinmux and GPIO driver"
> > +       depends on SOC_SP7021
> > +       depends on OF && HAS_IOMEM
> > +       select PINMUX
> > +       select GENERIC_PINCTRL_GROUPS
> > +       select GENERIC_PINMUX_FUNCTIONS
> > +       select PINCONF
> > +       select GENERIC_PINCONF
> > +       select OF_GPIO
> > +       select GPIOLIB
> > +       select GPIO_SYSFS
> 
> Don't do this, sysfs is deprecated.

Yes, I'll remove 'select GPIO_SYSFS' in next patch.


> > +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl.o
> > +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_pinctrl.o
> > +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_sysfs.o
> > +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_gpio_ops.o
> > +obj-$(CONFIG_PINCTRL_SPPCTL) += sppctl_gpio.o
> > +obj-$(CONFIG_PINCTRL_SPPCTL) += pinctrl_inf_sp7021.o
> > +obj-$(CONFIG_PINCTRL_SPPCTL) += gpio_inf_sp7021.o
> 
> This multitide of files makes this a bit hard to read and review, usually pin controllers
> are in one-two files for a single SoC.

Yes, I'll re-arrange files into two files in next patch.

sppctl.c sppctl.h	-- for SoC-irrelevant code.
sppctl_sp7021.c		-- for SoC-relevant code.

Is this arrangement ok?


> > + * 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; either version 2 of the License, or
> > + * (at your option) any later version.
> > + *
> > + * 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.
> 
> Drop this boilerplate on all files and just use the SPDX tag.

Yes, I'll remove them in next patch.


> > +const size_t GPIS_listSZ =
> > +sizeof(sppctlgpio_list_s)/sizeof(*(sppctlgpio_list_s));
> 
> Use only lowercase in variable names.

Yes, I'll check and use lowercase for all variable names in next patch.


> This looks like a reimplementation of ARRAY_SIZE(), replace with that if this is the case.

Yes, I'll replace the statement with using ARRAY_SIZE() in next patch.

 
> > +const size_t sppctlpins_allSZ = ARRAY_SIZE(sppctlpins_all);
> 
> Instead of defining consts for random sizes like this, just inline ARRAY_SIZE() where you
> use it.

The array 'sppctlpins_all' is defined without explicit size.
ARRAY_SIZE won't work if it is not placed with define (within the same file).


> > +// gpio: is defined in gpio_inf_sp7021.c const size_t PMUX_listSZ =
> > +sizeof(sppctlpmux_list_s)/sizeof(*(sppctlpmux_list_s));
> 
> Same comment as above. Etc.

Yes, I'll replace the statement with using ARRAY_SIZE() in next patch.


> > +/* CEC pin is not used. Release it for others. */ //static const
> > +unsigned int pins_hdmi1[] = { D(10, 6), D(10, 7), D(12, 2), D(12, 1)
> > +}; //static const unsigned int pins_hdmi2[] = { D(8, 3), D(8, 4),
> > +D(8, 5), D(8, 6) }; //static const unsigned int pins_hdmi3[] = { D(7,
> > +4), D(7, 5), D(7, 6), D(7, 7) };
> 
> Don't leave commented-out code in the driver. Delete all this stuff.

Yes, I'll remove them in next patch.


> > +void print_device_tree_node(struct device_node *node, int depth) {
> > +       int i = 0;
> > +       struct device_node *child;
> > +       struct property    *properties;
> > +       char                indent[255] = "";
> > +
> > +       for (i = 0; i < depth * 3; i++)
> > +               indent[i] = ' ';
> > +       indent[i] = '\0';
> > +
> > +       ++depth;
> > +       if (depth == 1) {
> > +               pr_info("%s{ name = %s\n", indent, node->name);
> > +               for (properties = node->properties; properties != NULL;
> > +                       properties = properties->next)
> > +                       pr_info("%s  %s (%d)\n", indent, properties->name,
> properties->length);
> > +               pr_info("%s}\n", indent);
> > +       }
> > +
> > +       for_each_child_of_node(node, child) {
> > +               pr_info("%s{ name = %s\n", indent, child->name);
> > +               for (properties = child->properties; properties != NULL;
> > +                       properties = properties->next)
> > +                       pr_info("%s  %s (%d)\n", indent, properties->name,
> properties->length);
> > +               print_device_tree_node(child, depth);
> > +               pr_info("%s}\n", indent);
> > +       }
> > +}
> 
> This kind of debugging code should be deleted or use what is in the device tree core.

Yes, I'll remove them in next patch.
Could you please teach me which function I can use to print dt-node in core?


> > +void sppctl_gmx_set(struct sppctl_pdata_t *_p, uint8_t _roff, uint8_t _boff, uint8_t
> _bsiz,
> > +                   uint8_t _rval)
> > +{
> > +       uint32_t *r;
> 
> Don't use uint8_t or uint16_t or uint32_t, use the kernel short forms u8, u16 or u32, simply.

Yes, I'll replace them with kernel short forms u8, u16, u32 in next patch.


> Don't start any variable names with _underscore, it i a big confusion for the head because
> it has ambigous semantics.
> 
> Try to find concise descriptive variable names.

Yes, I'll review variables name with underscore prefix and give proper name in next patch


> > +       struct sppctl_reg_t x = { .m = (~(~0 << _bsiz)) << _boff,
> > +                                 .v = ((uint16_t)_rval) << _boff };
> > +
> > +       if (_p->debug > 1)
> > +               KDBG(_p->pcdp->dev, "%s(x%X,x%X,x%X,x%X) m:x%X v:x%X\n",
> > +                    __func__, _roff, _boff, _bsiz, _rval, x.m, x.v);
> 
> Do not reinvent kernel debugging use the dev_dbg() macro.

Yes, I'll replace KDBG with  dev_dbg() in next patch.


> > +       r = (uint32_t *)&x;
> 
> Try to avoid casting like this. It is usually a sign that something is wrong.

Yes, I'll review and remove the casting in next patch.


> 
> > +       if (_fun % 2 == 0)
> > +               ;
> > +       else {
> > +               x.v <<= 8;
> > +               x.m <<= 8;
> > +       }
> 
> This is code that is incredibly terse and deviant from the kernels general style. Please
> read a few other pin control drivers and familiarize with how these drivers usually look.

Yes, I'll study other drivers and modify them.


> > +uint8_t sppctl_fun_get(struct sppctl_pdata_t *_p,  uint8_t _fun) {
> > +       uint8_t pin = 0x00;
> > +       uint8_t func = (_fun >> 1) << 2;
> 
> This looks like shifting to get rid of bit 0.
> Just use bitwise logic instead.

Yes, I'll replace 'shift' with bitwise logic operation in next patch.


> > +       ret = request_firmware_nowait(THIS_MODULE, true, _fwname, _dev, GFP_KERNEL, p,
> > +                                     sppctl_fwload_cb);
> 
> So this pin controller needs a firmware? That is the first time I have ever seen that.
> Please add comments describing what this firmware is and what it does, also explain it
> in the commit message.

No, it restores pins settings from a file.
The driver supports saving pin-settings to a file and restoring pin-settings from a file.


> > +int sppctl_pctl_resmap(struct platform_device *_pd, struct
> > +sppctl_pdata_t *_pc) {
> > +       struct resource *rp;
> > +
> > +       // resF
> > +       rp = platform_get_resource(_pd, IORESOURCE_MEM, 0);
> > +       if (IS_ERR(rp)) {
> > +               KERR(&(_pd->dev), "%s get res#F ERR\n", __func__);
> > +               return PTR_ERR(rp);
> > +       }
> > +       KDBG(&(_pd->dev), "mres #F:%p\n", rp);
> 
> Thes resF etc are very terse and hard to understand. It seems written by someone who knows
> everything of what they are doing but with very little interest to explain it to others.
> Code readability is important.

Yes, I'll rename the variable name to improve code readability in next patch.


> > +static struct platform_driver sppctl_driver = {
> > +       .driver = {
> > +               .name           = MNAME,
> 
> Don't abbreviate so compulsively.
> SP7021_MODULE_NAME is fine.

Yes, I'll rename the defines to SPPCTL_MODULE_NAME in next patch.


> > +static int __init sppctl_drv_reg(void) {
> > +       return platform_driver_register(&sppctl_driver);
> > +}
> > +postcore_initcall(sppctl_drv_reg);
> 
> Why do you need a postcore_initcall()?
> 

postcore_initcall() is added here to make sure pinctrl driver 
will be loaded before other drivers which need pin settings.
With moving pinctrl driver earlier, HDMI-TX driver will be
loaded earlier.


> > +MODULE_AUTHOR(M_AUT1);
> > +MODULE_AUTHOR(M_AUT2);
> > +MODULE_DESCRIPTION(M_NAM);
> > +MODULE_LICENSE(M_LIC);
> 
> Just inline the strings, all other drivers do.

Yes, I'll use fixed string directly here in next patch.


> > +#define MNAME "sppctl"
> > +#define M_LIC "GPL v2"
> > +#define M_AUT1 "Dvorkin Dmitry <dvorkin@tibbo.com>"
> > +#define M_AUT2 "Wells Lu <wells.lu@sunplus.com>"
> > +#define M_NAM "SP7021 PinCtl"
> > +#define M_ORG "Sunplus/Tibbo Tech."
> > +#define M_CPR "(C) 2020"
> 
> This is too much and too abbreviated names, just use the strings directly in the macros.

Yes, I'll do it in next patch.


> > +#include <linux/version.h>
> 
> Why?

I'll remove the inclusion in next patch.
It is indeed not necessary.


> > +#include <linux/of_gpio.h>
> 
> Never use this include in new code. It is legacy.

I'll remove the inclusion in next patch.


> > +#define SPPCTL_MAX_NAM 64
> > +#define SPPCTL_MAX_BUF PAGE_SIZE
> > +
> > +#define KINF(pd, fmt, args...) \
> > +       do { \
> > +               if ((pd) != NULL) \
> > +                       dev_info((pd), fmt, ##args); \
> > +               else \
> > +                       pr_info(MNAME ": " fmt, ##args); \
> > +       } while (0)
> > +#define KERR(pd, fmt, args...) \
> > +       do { \
> > +               if ((pd) != NULL) \
> > +                       dev_info((pd), fmt, ##args); \
> > +               else \
> > +                       pr_err(MNAME ": " fmt, ##args); \
> > +       } while (0)
> > +#ifdef CONFIG_PINCTRL_SPPCTL_DEBUG
> > +#define KDBG(pd, fmt, args...) \
> > +       do { \
> > +               if ((pd) != NULL) \
> > +                       dev_info((pd), fmt, ##args); \
> > +               else \
> > +                       pr_debug(MNAME ": " fmt, ##args); \
> > +       } while (0)
> > +#else
> > +#define KDBG(pd, fmt, args...)
> > +#endif
> 
> Don't reimplement kernel debugging use dev_dbg(), dev_info()
> dev_err() etc directly. I don't see why you need CONFIG_PINCTRL_SPPCTL_DEBUG at all, if
> you absolutely want to control debugging for these files only just use this in your Makefile
> 
> subdir-ccflags-$(CONFIG_PINCTRL_SPPCTL_DEBUG)  := -DDEBUG
> 
> This will turn on/off the output from dev_dbg().

Yes, I'll remove KINF, KERR, KDB macros and use kernel debugging 
functions dev_info(), dev_err(), dev_info() in next patch.


> > +struct sppctl_pdata_t {
> > +       char name[SPPCTL_MAX_NAM];
> > +       uint8_t debug;
> 
> Don't use u8 for things like this use bool.

Yes, I'll remove 'debug' member and customized debug function 
will be removed.


> > +       char fwname[SPPCTL_MAX_NAM];
> > +       void *sysfs_sdp;
> > +       void __iomem *baseF;    // functions
> > +       void __iomem *base0;    // MASTER , OE , OUT , IN
> > +       void __iomem *base1;    // I_INV , O_INV , OD
> > +       void __iomem *base2;    // GPIO_FIRST
> > +       void __iomem *baseI;    // IOP
> > +       // pinctrl-related
> > +       struct pinctrl_desc pdesc;
> > +       struct pinctrl_dev *pcdp;
> > +       struct pinctrl_gpio_range gpio_range;
> > +       struct sppctlgpio_chip_t *gpiod;
> 
> *gpiod is a bad name because we use it quite a lot in the kernel for GPIO descriptors.

Yes, I'll rename 'gpiod' with '' in next patch.


> > +struct sppctl_reg_t {
> > +       uint16_t v;     // value part
> > +       uint16_t m;     // mask part
> > +};
> 
> These are not types (no typedef) so don't add *_t suffixes, just drop those everywhere.

Yes, I'll remove all _t suffixes in next patch.


> > +       const char * const name;
> > +       const uint8_t gval;             // value for register
> > +       const unsigned * const pins;    // list of pins
> > +       const unsigned int pnum;        // number of pins
> 
> Use kerneldoc to document struct members.
> 
> There will be many more comments but work on these things to begin with!

I never use kerneldoc. Where should I put my vendor-specified kernel doc?


> 
> Yours,
> Linus Walleij

Thank you very much for your review.

Best regards,
Wells

^ permalink raw reply	[flat|nested] 19+ messages in thread

* RE: [PATCH v2 2/3] dt-bindings: pinctrl: Add dt-bindings for Sunplus SP7021
  2021-11-12 15:37     ` Rob Herring
@ 2021-11-18  9:03       ` Wells Lu 呂芳騰
  0 siblings, 0 replies; 19+ messages in thread
From: Wells Lu 呂芳騰 @ 2021-11-18  9:03 UTC (permalink / raw)
  To: Rob Herring, Wells Lu
  Cc: linus.walleij, linux-gpio, linux-kernel, devicetree, qinjian, dvorkin

Hi,

Thank you for your review.


> On Mon, Nov 01, 2021 at 04:11:16PM +0800, Wells Lu wrote:
> > Add dt-bindings header files for Sunplus SP7021 SoC.
> >
> > Signed-off-by: Wells Lu <wells.lu@sunplus.com>
> > ---
> > Changes in v2:
> >  - Added more 'defines' in dt-bindings header files (forgot to add in v1).
> >
> >  MAINTAINERS                                 |   1 +
> >  include/dt-bindings/pinctrl/sppctl-sp7021.h | 171 ++++++++++++++++++++++++++++
> >  include/dt-bindings/pinctrl/sppctl.h        |  40 +++++++
> >  3 files changed, 212 insertions(+)
> >  create mode 100644 include/dt-bindings/pinctrl/sppctl-sp7021.h
> >  create mode 100644 include/dt-bindings/pinctrl/sppctl.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS index fd82c77..da6378f 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -14873,6 +14873,7 @@ L:	linux-arm-kernel@lists.infradead.org (moderated for
> non-subscribers)
> >  S:	Maintained
> >  W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
> >  F:	drivers/pinctrl/sunplus/
> > +F:	include/dt-bindings/pinctrl/sppctl*
> >
> >  PKTCDVD DRIVER
> >  M:	linux-block@vger.kernel.org
> > diff --git a/include/dt-bindings/pinctrl/sppctl-sp7021.h
> > b/include/dt-bindings/pinctrl/sppctl-sp7021.h
> > new file mode 100644
> > index 0000000..4e07d03
> > --- /dev/null
> > +++ b/include/dt-bindings/pinctrl/sppctl-sp7021.h
> > @@ -0,0 +1,171 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> 
> Care about OS other than Linux? Should be dual licensed.

Yes, I'll add dual license for dt-binding header file.


> > +/*
> > + * SP7021 pinmux pinctrl bindings.
> > + * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
> > + * Author: Dvorkin Dmitry <dvorkin@tibbo.com>  */
> > +
> > +#ifndef _DT_BINDINGS_PINCTRL_SPPCTL_SP7021_H
> > +#define _DT_BINDINGS_PINCTRL_SPPCTL_SP7021_H
> > +
> > +#include <dt-bindings/pinctrl/sppctl.h>
> > +
> > +#define MUXF_GPIO                       0
> 
> Where do these numbers come from? Hopefully they correspond to register offsets and aren't
> made up. A comment either way here would help.

The numbers are based on define of hardware.
SP7021 supports so-called 'fully pin-mux' function.
GPIO8 ~ GPIO71 are 'fully pin-mux' pins

For example,
If I set pin-mux register of GPIO8 to 1,
GPIO8 will output L2SW_CLK_OUT signal.

If I set pin-mux register of GPIO8 to 2,
GPIO8 will output L2SW_MAC_SMI_MDC signal.

If I set pin-mux register of GPIO8 to 7,
GPIO8 will output L2SW_MAC_SMI_DMIO signal.

In fact, all signals from number 2 to 121, 
can be selected to output to any fully 
pin-mux pins (GPIO8 ~ GPIO71).

Numbers 121 to 155 are also based on define 
of hardware. It is used to turn off specific
pin-function. With the number, drivers can
calculate the control-bit easily.


> > +#define MUXF_IOP                        1
> > +#define MUXF_L2SW_CLK_OUT               2
> > +#define MUXF_L2SW_MAC_SMI_MDC           3
> > +#define MUXF_L2SW_LED_FLASH0            4
> > +#define MUXF_L2SW_LED_FLASH1            5
> > +#define MUXF_L2SW_LED_ON0               6
> > +#define MUXF_L2SW_LED_ON1               7
> > +#define MUXF_L2SW_MAC_SMI_MDIO          8
> > +#define MUXF_L2SW_P0_MAC_RMII_TXEN      9
> > +#define MUXF_L2SW_P0_MAC_RMII_TXD0      10
> > +#define MUXF_L2SW_P0_MAC_RMII_TXD1      11
> > +#define MUXF_L2SW_P0_MAC_RMII_CRSDV     12
> > +#define MUXF_L2SW_P0_MAC_RMII_RXD0      13
> > +#define MUXF_L2SW_P0_MAC_RMII_RXD1      14
> > +#define MUXF_L2SW_P0_MAC_RMII_RXER      15
> > +#define MUXF_L2SW_P1_MAC_RMII_TXEN      16
> > +#define MUXF_L2SW_P1_MAC_RMII_TXD0      17
> > +#define MUXF_L2SW_P1_MAC_RMII_TXD1      18
> > +#define MUXF_L2SW_P1_MAC_RMII_CRSDV     19
> > +#define MUXF_L2SW_P1_MAC_RMII_RXD0      20
> > +#define MUXF_L2SW_P1_MAC_RMII_RXD1      21
> > +#define MUXF_L2SW_P1_MAC_RMII_RXER      22
> > +#define MUXF_DAISY_MODE                 23
> > +#define MUXF_SDIO_CLK                   24
> > +#define MUXF_SDIO_CMD                   25
> > +#define MUXF_SDIO_D0                    26
> > +#define MUXF_SDIO_D1                    27
> > +#define MUXF_SDIO_D2                    28
> > +#define MUXF_SDIO_D3                    29
> > +#define MUXF_PWM0                       30
> > +#define MUXF_PWM1                       31
> > +#define MUXF_PWM2                       32
> > +#define MUXF_PWM3                       33
> > +#define MUXF_PWM4                       34
> > +#define MUXF_PWM5                       35
> > +#define MUXF_PWM6                       36
> > +#define MUXF_PWM7                       37
> > +#define MUXF_ICM0_D                     38
> > +#define MUXF_ICM1_D                     39
> > +#define MUXF_ICM2_D                     40
> > +#define MUXF_ICM3_D                     41
> > +#define MUXF_ICM0_CLK                   42
> > +#define MUXF_ICM1_CLK                   43
> > +#define MUXF_ICM2_CLK                   44
> > +#define MUXF_ICM3_CLK                   45
> > +#define MUXF_SPIM0_INT                  46
> > +#define MUXF_SPIM0_CLK                  47
> > +#define MUXF_SPIM0_EN                   48
> > +#define MUXF_SPIM0_DO                   49
> > +#define MUXF_SPIM0_DI                   50
> > +#define MUXF_SPIM1_INT                  51
> > +#define MUXF_SPIM1_CLK                  52
> > +#define MUXF_SPIM1_EN                   53
> > +#define MUXF_SPIM1_DO                   54
> > +#define MUXF_SPIM1_DI                   55
> > +#define MUXF_SPIM2_INT                  56
> > +#define MUXF_SPIM2_CLK                  57
> > +#define MUXF_SPIM2_EN                   58
> > +#define MUXF_SPIM2_DO                   59
> > +#define MUXF_SPIM2_DI                   60
> > +#define MUXF_SPIM3_INT                  61
> > +#define MUXF_SPIM3_CLK                  62
> > +#define MUXF_SPIM3_EN                   63
> > +#define MUXF_SPIM3_DO                   64
> > +#define MUXF_SPIM3_DI                   65
> > +#define MUXF_SPI0S_INT                  66
> > +#define MUXF_SPI0S_CLK                  67
> > +#define MUXF_SPI0S_EN                   68
> > +#define MUXF_SPI0S_DO                   69
> > +#define MUXF_SPI0S_DI                   70
> > +#define MUXF_SPI1S_INT                  71
> > +#define MUXF_SPI1S_CLK                  72
> > +#define MUXF_SPI1S_EN                   73
> > +#define MUXF_SPI1S_DO                   74
> > +#define MUXF_SPI1S_DI                   75
> > +#define MUXF_SPI2S_INT                  76
> > +#define MUXF_SPI2S_CLK                  77
> > +#define MUXF_SPI2S_EN                   78
> > +#define MUXF_SPI2S_DO                   79
> > +#define MUXF_SPI2S_DI                   80
> > +#define MUXF_SPI3S_INT                  81
> > +#define MUXF_SPI3S_CLK                  82
> > +#define MUXF_SPI3S_EN                   83
> > +#define MUXF_SPI3S_DO                   84
> > +#define MUXF_SPI3S_DI                   85
> > +#define MUXF_I2CM0_CLK                  86
> > +#define MUXF_I2CM0_DAT                  87
> > +#define MUXF_I2CM1_CLK                  88
> > +#define MUXF_I2CM1_DAT                  89
> > +#define MUXF_I2CM2_CLK                  90
> > +#define MUXF_I2CM2_DAT                  91
> > +#define MUXF_I2CM3_CLK                  92
> > +#define MUXF_I2CM3_DAT                  93
> > +#define MUXF_UA1_TX                     94
> > +#define MUXF_UA1_RX                     95
> > +#define MUXF_UA1_CTS                    96
> > +#define MUXF_UA1_RTS                    97
> > +#define MUXF_UA2_TX                     98
> > +#define MUXF_UA2_RX                     99
> > +#define MUXF_UA2_CTS                    100
> > +#define MUXF_UA2_RTS                    101
> > +#define MUXF_UA3_TX                     102
> > +#define MUXF_UA3_RX                     103
> > +#define MUXF_UA3_CTS                    104
> > +#define MUXF_UA3_RTS                    105
> > +#define MUXF_UA4_TX                     106
> > +#define MUXF_UA4_RX                     107
> > +#define MUXF_UA4_CTS                    108
> > +#define MUXF_UA4_RTS                    109
> > +#define MUXF_TIMER0_INT                 110
> > +#define MUXF_TIMER1_INT                 111
> > +#define MUXF_TIMER2_INT                 112
> > +#define MUXF_TIMER3_INT                 113
> > +#define MUXF_GPIO_INT0                  114
> > +#define MUXF_GPIO_INT1                  115
> > +#define MUXF_GPIO_INT2                  116
> > +#define MUXF_GPIO_INT3                  117
> > +#define MUXF_GPIO_INT4                  118
> > +#define MUXF_GPIO_INT5                  119
> > +#define MUXF_GPIO_INT6                  120
> > +#define MUXF_GPIO_INT7                  121
> > +
> > +#define GROP_SPI_FLASH                  122
> > +#define GROP_SPI_FLASH_4BIT             123
> > +#define GROP_SPI_NAND                   124
> > +#define GROP_CARD0_EMMC                 125
> > +#define GROP_SD_CARD                    126
> > +#define GROP_UA0                        127
> > +#define GROP_ACHIP_DEBUG                128
> > +#define GROP_ACHIP_UA2AXI               129
> > +#define GROP_FPGA_IFX                   130
> > +#define GROP_HDMI_TX                    131
> > +#define GROP_AUD_EXT_ADC_IFX0           132
> > +#define GROP_AUD_EXT_DAC_IFX0           133
> > +#define GROP_SPDIF_RX                   134
> > +#define GROP_SPDIF_TX                   135
> > +#define GROP_TDMTX_IFX0                 136
> > +#define GROP_TDMRX_IFX0                 137
> > +#define GROP_PDMRX_IFX0                 138
> > +#define GROP_PCM_IEC_TX                 139
> > +#define GROP_LCDIF                      140
> > +#define GROP_DVD_DSP_DEBUG              141
> > +#define GROP_I2C_DEBUG                  142
> > +#define GROP_I2C_SLAVE                  143
> > +#define GROP_WAKEUP                     144
> > +#define GROP_UART2AXI                   145
> > +#define GROP_USB0_I2C                   146
> > +#define GROP_USB1_I2C                   147
> > +#define GROP_USB0_OTG                   148
> > +#define GROP_USB1_OTG                   149
> > +#define GROP_UPHY0_DEBUG                150
> > +#define GROP_UPHY1_DEBUG                151
> > +#define GROP_UPHY0_EXT                  152
> > +#define GROP_PROBE_PORT                 153
> > +#define GROP_ANA_I2C_IF                 154
> > +#define GROP_ANA_TEST_IF                155
> > +
> > +#endif
> > diff --git a/include/dt-bindings/pinctrl/sppctl.h
> > b/include/dt-bindings/pinctrl/sppctl.h
> > new file mode 100644
> > index 0000000..3e82989
> > --- /dev/null
> > +++ b/include/dt-bindings/pinctrl/sppctl.h
> > @@ -0,0 +1,40 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * SP7021 pinmux pinctrl bindings.
> > + * Copyright (C) Sunplus Tech/Tibbo Tech. 2020
> > + * Author: Dvorkin Dmitry <dvorkin@tibbo.com>  */
> > +
> > +#ifndef _DT_BINDINGS_PINCTRL_SPPCTL_H #define
> > +_DT_BINDINGS_PINCTRL_SPPCTL_H
> > +
> > +#define IOP_G_MASTE             (0x01<<0)
> > +#define IOP_G_FIRST             (0x01<<1)
> > +
> > +#define SPPCTL_PCTL_G_PMUX      (0x00|IOP_G_MASTE)
> > +#define SPPCTL_PCTL_G_GPIO      (IOP_G_FIRST|IOP_G_MASTE)
> > +#define SPPCTL_PCTL_G_IOPP      (IOP_G_FIRST|0x00)
> > +
> > +#define SPPCTL_PCTL_L_OUT       (0x01<<0)
> > +#define SPPCTL_PCTL_L_OU1       (0x01<<1)
> > +#define SPPCTL_PCTL_L_INV       (0x01<<2)
> > +#define SPPCTL_PCTL_L_ONV       (0x01<<3)
> > +#define SPPCTL_PCTL_L_ODR       (0x01<<4)
> > +
> > +#define SPPCTL_PCTLE_P(v)       ((v)<<24)
> > +#define SPPCTL_PCTLE_G(v)       ((v)<<16)
> > +#define SPPCTL_PCTLE_F(v)       ((v)<<8)
> > +#define SPPCTL_PCTLE_L(v)       ((v)<<0)
> > +
> > +#define SPPCTL_PCTLD_P(v)       (((v)>>24) & 0xFF)
> > +#define SPPCTL_PCTLD_G(v)       (((v)>>16) & 0xFF)
> > +#define SPPCTL_PCTLD_F(v)       (((v) >> 8) & 0xFF)
> > +#define SPPCTL_PCTLD_L(v)       (((v) >> 0) & 0xFF)
> > +
> > +/*
> > + * pack into 32-bit value:
> > + * pin#{8bit}, typ{8bit}, function{8bit}, flags{8bit}  */ #define
> > +SPPCTL_IOPAD(pin, typ, fun, fls)
> > +(((pin)<<24)|((typ)<<16)|((fun)<<8)|(fls))
> > +
> > +#endif
> > --
> > 2.7.4
> >
> >

^ permalink raw reply	[flat|nested] 19+ messages in thread

* RE: [PATCH v2 3/3] devicetree: bindings: pinctrl: Add bindings doc for Sunplus SP7021.
  2021-11-12 15:40     ` Rob Herring
@ 2021-11-18  9:15       ` Wells Lu 呂芳騰
  0 siblings, 0 replies; 19+ messages in thread
From: Wells Lu 呂芳騰 @ 2021-11-18  9:15 UTC (permalink / raw)
  To: Rob Herring, Wells Lu
  Cc: linus.walleij, linux-gpio, linux-kernel, devicetree, qinjian, dvorkin

Hi,

Thanks for your review.


> On Mon, Nov 01, 2021 at 04:11:17PM +0800, Wells Lu wrote:
> > Add bindings documentation for Sunplus SP7021.
> 
> Patch 2 and 3 can be combined. Use consistent subjects. Patch 2 is good.
> This one is not.

Yes, I'll combine patch 2 and 3 into a single patch in next patch series.


> >
> > Signed-off-by: Wells Lu <wells.lu@sunplus.com>
> > ---
> > Changes in v2:
> >  - None
> >
> >  .../bindings/pinctrl/sunplus,sp7021-pinctrl.yaml   | 277 +++++++++++++++++++++
> >  MAINTAINERS                                        |   1 +
> >  2 files changed, 278 insertions(+)
> >  create mode 100644
> > Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yaml
> >
> > diff --git
> > a/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yam
> > l
> > b/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl.yam
> > l
> > new file mode 100644
> > index 0000000..7cfa0ce
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/pinctrl/sunplus,sp7021-pinctrl
> > +++ .yaml
> > @@ -0,0 +1,277 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) # Copyright
> > +(C) Sunplus Co., Ltd. 2021 %YAML 1.2
> > +---
> > +$id:
> > +http://devicetree.org/schemas/pinctrl/sunplus,sp7021-pinctrl.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Sunplus SP7021 Pin Controller Device Tree Bindings
> > +
> > +maintainers:
> > +  - Dvorkin Dmitry <dvorkin@tibbo.com>
> > +  - Wells Lu <wells.lu@sunplus.com>
> > +
> > +description: |
> > +  The Sunplus SP7021 pin controller is used to control SoC pins.
> > +Please
> > +  refer to pinctrl-bindings.txt in this directory for details of the
> > +common
> > +  pinctrl bindings used by client devices.
> > +
> > +  Refer to https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/pages/
> > +  1443495991/How+to+setup+pins+of+SP7021+in+device-tree+source
> > +
> > +  The device node of pin controller of Sunplus SP7021 has following
> > + properties.
> > +
> > +properties:
> > +  compatible:
> > +    const: sunplus,sp7021-pctl
> > +
> > +  gpio-controller: true
> > +
> > +  '#gpio-cells':
> > +    const: 2
> > +
> > +  reg:
> > +    items:
> > +      - description: Base address and length of the MOON2 registers.
> > +      - description: Base address and length of the GPIOXT registers.
> > +      - description: Base address and length of the GPIOXT2 registers.
> > +      - description: Base address and length of the FIRST registers.
> > +      - description: Base address and length of the MOON1 registers.
> > +
> > +  clocks:
> > +    maxItems: 1
> > +
> > +  resets:
> > +    maxItems: 1
> > +
> > +patternProperties:
> > +  '^.*$':
> > +    if:
> > +      type: object
> > +    then:
> 
> For new bindings, don't use this hack. Make the node name something you can match on (e.g.
> '-pins$').

Yes, I'll modify the regular expression to '-pins$' in next patch.
Sub-nodes of pinctrl node will look like:

	uart0-pins {
		...
	};

	emmc-pins {
		...
	};

> > +      description: |
> > +        A pinctrl node should contain at least one subnodes representing the
> > +        pins or function-pins group available on the machine. Each subnode
> > +        will list the pins it needs, and how they should be configured.
> > +
> > +        Pinctrl node's client devices use subnodes for desired pin
> > +        configuration. Client device subnodes use below standard properties.
> > +
> > +      properties:
> > +        pins:
> > +          description: |
> > +            Define pins which are used by pinctrl node's client device.
> > +
> > +            It consists of one or more integers which represents the config
> > +            setting for corresponding pin. Please use macro SPPCTL_IOPAD to
> > +            define the integers for pins.
> > +
> > +            The first argument of the macro is pin number, the second is pin
> > +            type, the third is type of GPIO, the last is default output state
> > +            of GPIO.
> > +          $ref: /schemas/types.yaml#/definitions/uint32-array
> > +
> > +        function:
> > +          description: |
> > +            Define pin-function which is used by pinctrl node's client device.
> > +            The name should be one of string in the following enumeration.
> > +          $ref: "/schemas/types.yaml#/definitions/string"
> > +          enum: [ SPI_FLASH, SPI_FLASH_4BIT, SPI_NAND, CARD0_EMMC, SD_CARD,
> > +                  UA0, FPGA_IFX, HDMI_TX, LCDIF, USB0_OTG, USB1_OTG ]
> > +
> > +        groups:
> > +          description: |
> > +            Define pin-group in a specified pin-function.
> > +            The name should be one of string in the following enumeration.
> > +          $ref: "/schemas/types.yaml#/definitions/string"
> > +          enum: [ SPI_FLASH1, SPI_FLASH2, SPI_FLASH_4BIT1, SPI_FLASH_4BIT2,
> > +                  SPI_NAND, CARD0_EMMC, SD_CARD, UA0, FPGA_IFX, HDMI_TX1,
> > +                  HDMI_TX2, HDMI_TX3, LCDIF, USB0_OTG, USB1_OTG ]
> > +
> > +        zero_func:
> > +          description: |
> > +            Disabled pins which are not used by pinctrl node's client device.
> > +          $ref: /schemas/types.yaml#/definitions/uint32-array
> > +
> > +      additionalProperties: false
> > +
> > +      allOf:
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - SPI_FLASH
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - SPI_FLASH1
> > +                  - SPI_FLASH2
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - SPI_FLASH_4BIT
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - SPI_FLASH_4BIT1
> > +                  - SPI_FLASH_4BIT2
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - SPI_NAND
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - SPI_NAND
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - CARD0_EMMC
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - CARD0_EMMC
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - SD_CARD
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - SD_CARD
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - UA0
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - UA0
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - FPGA_IFX
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - FPGA_IFX
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - HDMI_TX
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - HDMI_TX1
> > +                  - HDMI_TX2
> > +                  - HDMI_TX3
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - LCDIF
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - LCDIF
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - USB0_OTG
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - USB0_OTG
> > +        - if:
> > +            properties:
> > +              function:
> > +                enum:
> > +                  - USB1_OTG
> > +          then:
> > +            properties:
> > +              groups:
> > +                enum:
> > +                  - USB1_OTG
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - "#gpio-cells"
> > +  - gpio-controller
> > +  - clocks
> > +  - resets
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > +  - |
> > +    #include <dt-bindings/clock/sp-sp7021.h>
> > +    #include <dt-bindings/reset/sp-sp7021.h>
> > +    #include <dt-bindings/pinctrl/sppctl-sp7021.h>
> > +
> > +    pctl: pctl@9C000100 {
> 
> pinctl@9c000100

Yes, I'll modify node name of pin-ctrl to 'pinctl@9c000100' in next patch.


> > +        compatible = "sunplus,sp7021-pctl";
> > +        reg = <0x9C000100 0x100>, <0x9C000300 0x80>, <0x9C000380 0x80>,
> > +              <0x9C0032e4 0x1C>, <0x9C000080 0x20>;
> > +        gpio-controller;
> > +        #gpio-cells = <2>;
> > +        clocks = <&clkc GPIO>;
> > +        resets = <&rstc RST_GPIO>;
> > +
> > +        pins_uart0: pins_uart0 {
> > +            function = "UA0";
> > +            groups = "UA0";
> > +        };
> > +
> > +        pins_uart1: pins_uart1 {
> > +            pins = <
> > +                SPPCTL_IOPAD(11,SPPCTL_PCTL_G_PMUX,MUXF_UA1_TX,0)
> > +                SPPCTL_IOPAD(10,SPPCTL_PCTL_G_PMUX,MUXF_UA1_RX,0)
> > +                SPPCTL_IOPAD(7,SPPCTL_PCTL_G_GPIO,0,SPPCTL_PCTL_L_OUT)
> > +            >;
> > +        };
> > +
> > +        emmc_mux: emmc_mux {
> > +            function = "CARD0_EMMC";
> > +            groups = "CARD0_EMMC";
> > +        };
> > +
> > +        mmc1_mux: mmc1_mux {
> > +            function = "SD_CARD";
> > +            groups = "SD_CARD";
> > +            pins = < SPPCTL_IOPAD(91,SPPCTL_PCTL_G_GPIO,0,0) >;
> > +        };
> > +
> > +        hdmi_A_tx1: hdmi_A_tx1_pins {
> > +            function = "HDMI_TX";
> > +            groups = "HDMI_TX1";
> > +        };
> > +        hdmi_A_tx2: hdmi_A_tx2_pins {
> > +            function = "HDMI_TX";
> > +            groups = "HDMI_TX2";
> > +        };
> > +        hdmi_A_tx3: hdmi_A_tx3_pins {
> > +            function = "HDMI_TX";
> > +            groups = "HDMI_TX3";
> > +        };
> > +    };
> > +...
> > diff --git a/MAINTAINERS b/MAINTAINERS index da6378f..11835e7 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -14872,6 +14872,7 @@ M:	Wells Lu <wells.lu@sunplus.com>
> >  L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> >  S:	Maintained
> >  W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
> > +F:	Documentation/devicetree/bindings/pinctrl/sunplus,*
> >  F:	drivers/pinctrl/sunplus/
> >  F:	include/dt-bindings/pinctrl/sppctl*
> >
> > --
> > 2.7.4
> >
> >

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH 3/3] devicetree: bindings: pinctrl: Add bindings doc for Sunplus SP7021.
       [not found]     ` <f315d79da3e742b4a4ec0131d6035046@sphcmbx02.sunplus.com.tw>
@ 2021-11-18 12:20       ` Dvorkin Dmitry
  0 siblings, 0 replies; 19+ messages in thread
From: Dvorkin Dmitry @ 2021-11-18 12:20 UTC (permalink / raw)
  To: Linus Walleij, Wells Lu
  Cc: linux-gpio, linux-kernel, robh+dt, devicetree, qinjian

[-- Attachment #1: Type: text/plain, Size: 5501 bytes --]

Dear Linus!

I am the person who wrote this driver. Let me answer to your questions...

-----Original Message-----
>> From: Linus Walleij <linus.walleij@linaro.org>
>> Sent: Tuesday, November 9, 2021 12:00 PM
>> To: Wells Lu <wellslutw@gmail.com>
>> Cc: linux-gpio@vger.kernel.org; linux-kernel@vger.kernel.org;
>> robh+dt@kernel.org; devicetree@vger.kernel.org; qinjian@cqplus1.com;
>> dvorkin@tibbo.com; Wells Lu 呂芳騰 <wells.lu@sunplus.com>
>> Subject: Re: [PATCH 3/3] devicetree: bindings: pinctrl: Add bindings doc for
>> Sunplus SP7021.
>>
>>
>>> +        zero_func:
>>> +          description: |
>>> +            Disabled pins which are not used by pinctrl node's client
>> device.
>>> +          $ref: /schemas/types.yaml#/definitions/uint32-array
>> I have never seen this before. Can't you just use pin control hogs for this so the
>> pin controller just take care of these pins?

zero_func is required.

The bootloader may have different device tree (I am using general sp7021 
DTS in my u-boot setup, for example), while the kernel DTS may be 
changed between boots and specifies it more precisely - it is configured 
by user. So u-boot DTB and kernel DTB may be different -> result is that 
some pins may be muxed wrongly after u-boot starts kernel. Or even in 
pre-u-boot stage (we have the bootloader that starts u-boot, called 
XBoot). This XBoot also do some muxing. So we need this feature to get 
rid of possible unneded muxes done before kernel has been started.

There is the "group of pins" functions and individual pins that may 
intersect.

You may have "group of pins", say, emmc preconfigured before kernel 
started (in general DTS for u-boot) and you may want to have the pin 
from emmc group to be muxed as, say, SD card detect. You mux it in 
kernel DTS as GPIO, it will be in correct GPIO state, configured 
correctly, but while emmc group is enabled (nobody disabled it in kernel 
DTS!) the pin will belong to emmc function (preset group) and will not 
be functional.

I invented zero_func while has been debugging the problem like "why my 
Eth is not working when all pins are configured correctly and muxed to 
Eth". I spend some time to find that the pin I muxed to Eth has been 
muxed to SPI_FLASH GROUP in very early stage (in ROM boot). And I have 
no way to cleanup this mux group easily.

zero_func is the way to easily guarantee that you will successfully and 
correctly mux some pins / functions on kernel load even if somebody 
muxed other pins to this functions before kernel.

If I'd implement "automatic" mux cleanup before muxing some pin, the 
code would be more complex. I would like to keep code as simple as I can 
and give better control to user.


>>
>>> +      allOf:
>>> +        - if:
>>> +            properties:
>>> +              function:
>>> +                enum:
>>> +                  - SPI_FLASH
>>> +          then:
>>> +            properties:
>>> +              groups:
>>> +                enum:
>>> +                  - SPI_FLASH1
>>> +                  - SPI_FLASH2
>>> +        - if:
>>> +            properties:
>>> +              function:
>>> +                enum:
>>> +                  - SPI_FLASH_4BIT
>>> +          then:
>>> +            properties:
>>> +              groups:
>>> +                enum:
>>> +                  - SPI_FLASH_4BIT1
>>> +                  - SPI_FLASH_4BIT2
>>> +        - if:
>>> +            properties:
>>> +              function:
>>> +                enum:
>>> +                  - HDMI_TX
>>> +          then:
>>> +            properties:
>>> +              groups:
>>> +                enum:
>>> +                  - HDMI_TX1
>>> +                  - HDMI_TX2
>>> +                  - HDMI_TX3
>>> +        - if:
>>> +            properties:
>>> +              function:
>>> +                enum:
>>> +                  - LCDIF
>>> +          then:
>>> +            properties:
>>> +              groups:
>>> +                enum:
>>> +                  - LCDIF
>>>
>>> This looks complex to me, I need feedback from bindings people on this.

sp7021 supports two types of muxes:

1) group muxing (1-N sets of predefined pins for some function)

2) individual pin muxing

Some functions may be muxed only in group, like SPI_FLASH or HDMI.

That's why we have

pins = <...>;

and

function = <funcname>;

group = <funcsubname-group>;

second case could be cuted to

function = <funcsubname-group> only;

But I think, the syntax of a pair {function,group} fits SoC logic 
better. Especially if customer is reading possible muxes table for the chip.


>>>
>>> +        pins_uart0: pins_uart0 {
>>> +            function = "UA0";
>>> +            groups = "UA0";
>>> +        };
>>> +
>>> +        pins_uart1: pins_uart1 {
>>> +            pins = <
>>> +
>> SPPCTL_IOPAD(11,SPPCTL_PCTL_G_PMUX,MUXF_UA1_TX,0)
>>> +
>> SPPCTL_IOPAD(10,SPPCTL_PCTL_G_PMUX,MUXF_UA1_RX,0)
>>> +
>> SPPCTL_IOPAD(7,SPPCTL_PCTL_G_GPIO,0,SPPCTL_PCTL_L_OUT)
>>> +            >;
>>> +        };
>> This first looks like two ways to do the same thing?
>> UART0 uses strings for group + function and uart1 control individual pins.
>>
>> Is it possible to just do it one way?
>>
>> I think the pins = <...> scheme includes also multiplexing settings and then it
>> should be named pinmux = <...>:
No. Sorry. It is two different way of supported two different types of 
muxing, described above.
>>
>> Please read
>> Documentation/devicetree/bindings/pinctrl/pinmux-node.yaml
>> closely.
>>
>> Yours,
>> Linus Walleij

[-- Attachment #2: dvorkin.vcf --]
[-- Type: text/x-vcard, Size: 391 bytes --]

BEGIN:VCARD
VERSION:4.0
EMAIL;PREF=1:dvorkin@tibbo.com
EMAIL:dvorkindmitry@gmail.com
FN:Dmitry Dvorkin
NICKNAME:dv
ORG:Tibbo Technology Inc.;
TITLE:Embedded Linux Architect
N:Dvorkin;Dmitry;;;
ADR:;;9F-3\, No.31, Lane 169, Kang-Ning St., Hsi-Chih;New Taipei City;;2218
 0;Taiwan
TEL;VALUE=TEXT:+79190546388
URL;VALUE=URL:https://tibbo.com/
UID:1c58210f-ac8c-4337-b391-0bde146d2d83
END:VCARD

^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2021-11-18 12:21 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-27  8:55 [PATCH 0/3] Add pin control driver for Sunplus SP7021 SoC Wells Lu
2021-10-27  8:55 ` [PATCH 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
2021-10-27 22:12   ` Randy Dunlap
2021-10-29  3:40     ` Wells Lu 呂芳騰
2021-10-29  4:38       ` Randy Dunlap
2021-10-27  8:55 ` [PATCH 2/3] dt-bindings: pinctrl: Add dt-bindings " Wells Lu
2021-10-27  8:55 ` [PATCH 3/3] devicetree: bindings: pinctrl: Add bindings doc " Wells Lu
2021-11-09  3:59   ` Linus Walleij
     [not found]     ` <f315d79da3e742b4a4ec0131d6035046@sphcmbx02.sunplus.com.tw>
2021-11-18 12:20       ` Dvorkin Dmitry
2021-11-01  8:11 ` [PATCH v2 0/3] This is a patch series for pinctrl driver for Sunplus SP7021 SoC Wells Lu
2021-11-01  8:11   ` [PATCH v2 1/3] pinctrl: Add driver for Sunplus SP7021 Wells Lu
2021-11-09  4:30     ` Linus Walleij
2021-11-17  8:35       ` Wells Lu 呂芳騰
2021-11-01  8:11   ` [PATCH v2 2/3] dt-bindings: pinctrl: Add dt-bindings " Wells Lu
2021-11-12 15:37     ` Rob Herring
2021-11-18  9:03       ` Wells Lu 呂芳騰
2021-11-01  8:11   ` [PATCH v2 3/3] devicetree: bindings: pinctrl: Add bindings doc " Wells Lu
2021-11-12 15:40     ` Rob Herring
2021-11-18  9:15       ` Wells Lu 呂芳騰

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).