diff mbox

[U-Boot,v3,17/25] rockchip: rk3288: Add pinctrl driver

Message ID 1435102150-29438-18-git-send-email-sjg@chromium.org
State Deferred
Delegated to: Tom Rini
Headers show

Commit Message

Simon Glass June 23, 2015, 11:29 p.m. UTC
Add a driver which supports pin multiplexing setup for the most commonly
used peripherals.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

Changes in v3: None
Changes in v2: None

 drivers/pinctrl/Kconfig                   |   9 +
 drivers/pinctrl/Makefile                  |   1 +
 drivers/pinctrl/rockchip/Makefile         |   8 +
 drivers/pinctrl/rockchip/pinctrl_rk3288.c | 429 ++++++++++++++++++++++++++++++
 4 files changed, 447 insertions(+)
 create mode 100644 drivers/pinctrl/rockchip/Makefile
 create mode 100644 drivers/pinctrl/rockchip/pinctrl_rk3288.c

Comments

Masahiro Yamada July 6, 2015, 5:24 p.m. UTC | #1
Hi Simon,


2015-06-24 8:29 GMT+09:00 Simon Glass <sjg@chromium.org>:

> +
> +static int rk3288_pinctrl_get_periph_id(struct udevice *dev,
> +                                       struct udevice *periph)
> +{
> +       u32 cell[3];
> +       int ret;
> +
> +       ret = fdtdec_get_int_array(gd->fdt_blob, periph->of_offset,
> +                                  "interrupts", cell, ARRAY_SIZE(cell));
> +       if (ret < 0)
> +               return -EINVAL;
> +
> +       switch (cell[1]) {
> +       case 44:
> +               return PERIPH_ID_SPI0;
> +       case 45:
> +               return PERIPH_ID_SPI1;
> +       case 46:
> +               return PERIPH_ID_SPI2;
> +       case 60:
> +               return PERIPH_ID_I2C0;
> +       case 62: /* Note strange order */
> +               return PERIPH_ID_I2C1;
> +       case 61:
> +               return PERIPH_ID_I2C2;
> +       case 63:
> +               return PERIPH_ID_I2C3;
> +       case 64:
> +               return PERIPH_ID_I2C4;
> +       case 65:
> +               return PERIPH_ID_I2C5;
> +       }
> +
> +       return -ENOENT;
> +}

Weird.

Do you parse the "interrupts" property
to identify the peripheral?

This is what I really do not like to do.
Simon Glass July 6, 2015, 5:32 p.m. UTC | #2
Hi Masahiro,

On 6 July 2015 at 11:24, Masahiro Yamada <yamada.masahiro@socionext.com> wrote:
>
> Hi Simon,
>
>
> 2015-06-24 8:29 GMT+09:00 Simon Glass <sjg@chromium.org>:
>
> > +
> > +static int rk3288_pinctrl_get_periph_id(struct udevice *dev,
> > +                                       struct udevice *periph)
> > +{
> > +       u32 cell[3];
> > +       int ret;
> > +
> > +       ret = fdtdec_get_int_array(gd->fdt_blob, periph->of_offset,
> > +                                  "interrupts", cell, ARRAY_SIZE(cell));
> > +       if (ret < 0)
> > +               return -EINVAL;
> > +
> > +       switch (cell[1]) {
> > +       case 44:
> > +               return PERIPH_ID_SPI0;
> > +       case 45:
> > +               return PERIPH_ID_SPI1;
> > +       case 46:
> > +               return PERIPH_ID_SPI2;
> > +       case 60:
> > +               return PERIPH_ID_I2C0;
> > +       case 62: /* Note strange order */
> > +               return PERIPH_ID_I2C1;
> > +       case 61:
> > +               return PERIPH_ID_I2C2;
> > +       case 63:
> > +               return PERIPH_ID_I2C3;
> > +       case 64:
> > +               return PERIPH_ID_I2C4;
> > +       case 65:
> > +               return PERIPH_ID_I2C5;
> > +       }
> > +
> > +       return -ENOENT;
> > +}
>
> Weird.
>
> Do you parse the "interrupts" property
> to identify the peripheral?
>
> This is what I really do not like to do.

Yes. It's similar to a clock number. It doesn't really matter what we
use. The interrupt number is a fixed part of the binding and will
never change.

Regards,
Simon
Masahiro Yamada July 8, 2015, 9:35 a.m. UTC | #3
Hi Simon,


2015-07-07 2:32 GMT+09:00 Simon Glass <sjg@chromium.org>:
> Hi Masahiro,
>
> On 6 July 2015 at 11:24, Masahiro Yamada <yamada.masahiro@socionext.com> wrote:
>>
>> Hi Simon,
>>
>>
>> 2015-06-24 8:29 GMT+09:00 Simon Glass <sjg@chromium.org>:
>>
>> > +
>> > +static int rk3288_pinctrl_get_periph_id(struct udevice *dev,
>> > +                                       struct udevice *periph)
>> > +{
>> > +       u32 cell[3];
>> > +       int ret;
>> > +
>> > +       ret = fdtdec_get_int_array(gd->fdt_blob, periph->of_offset,
>> > +                                  "interrupts", cell, ARRAY_SIZE(cell));
>> > +       if (ret < 0)
>> > +               return -EINVAL;
>> > +
>> > +       switch (cell[1]) {
>> > +       case 44:
>> > +               return PERIPH_ID_SPI0;
>> > +       case 45:
>> > +               return PERIPH_ID_SPI1;
>> > +       case 46:
>> > +               return PERIPH_ID_SPI2;
>> > +       case 60:
>> > +               return PERIPH_ID_I2C0;
>> > +       case 62: /* Note strange order */
>> > +               return PERIPH_ID_I2C1;
>> > +       case 61:
>> > +               return PERIPH_ID_I2C2;
>> > +       case 63:
>> > +               return PERIPH_ID_I2C3;
>> > +       case 64:
>> > +               return PERIPH_ID_I2C4;
>> > +       case 65:
>> > +               return PERIPH_ID_I2C5;
>> > +       }
>> > +
>> > +       return -ENOENT;
>> > +}
>>
>> Weird.
>>
>> Do you parse the "interrupts" property
>> to identify the peripheral?
>>
>> This is what I really do not like to do.
>
> Yes. It's similar to a clock number. It doesn't really matter what we
> use. The interrupt number is a fixed part of the binding and will
> never change.


I guess it is abuse of "interrupts" property.

Moreover, it can not cover cases where a single signal can be routed to
several pins.


For example, say we want to set pinmux for the I2C channel 0 (SDA0 and SCL0).

On some vendors' SoCs, it is possible to connect SDA0 to either pin10
or pin20 for example.
Likewise, SCL0 can be connected to pin11 or pin21.
(Yes, it is possible on my SoCs)


The interrupt ID can identify it is I2C0, and that's it.

Some boards expect the signals come out of pin10, 11,
and other boards pin20, 21.

The interrupt ID cannot distinguish those cases; the "pinctrl-*"
property can do it.

This is mentioned in Documentation/pinctrl.txt and
that is why a single function can have multiple pin-groups in Linux's
pinctrl drivers.

I think this design cannot meet some vendors' demand.
At least, I am unhappy with it.

Could you hold your pinctrl patches and give me time to discuss a bit
more, please?

I understand how pinctrl drivers work in Linux, so please give me some time
to consider if better design might be possible.
Simon Glass July 8, 2015, 8:27 p.m. UTC | #4
Hi Masahiro,

On 8 July 2015 at 03:35, Masahiro Yamada <yamada.masahiro@socionext.com> wrote:
> Hi Simon,
>
>
> 2015-07-07 2:32 GMT+09:00 Simon Glass <sjg@chromium.org>:
>> Hi Masahiro,
>>
>> On 6 July 2015 at 11:24, Masahiro Yamada <yamada.masahiro@socionext.com> wrote:
>>>
>>> Hi Simon,
>>>
>>>
>>> 2015-06-24 8:29 GMT+09:00 Simon Glass <sjg@chromium.org>:
>>>
>>> > +
>>> > +static int rk3288_pinctrl_get_periph_id(struct udevice *dev,
>>> > +                                       struct udevice *periph)
>>> > +{
>>> > +       u32 cell[3];
>>> > +       int ret;
>>> > +
>>> > +       ret = fdtdec_get_int_array(gd->fdt_blob, periph->of_offset,
>>> > +                                  "interrupts", cell, ARRAY_SIZE(cell));
>>> > +       if (ret < 0)
>>> > +               return -EINVAL;
>>> > +
>>> > +       switch (cell[1]) {
>>> > +       case 44:
>>> > +               return PERIPH_ID_SPI0;
>>> > +       case 45:
>>> > +               return PERIPH_ID_SPI1;
>>> > +       case 46:
>>> > +               return PERIPH_ID_SPI2;
>>> > +       case 60:
>>> > +               return PERIPH_ID_I2C0;
>>> > +       case 62: /* Note strange order */
>>> > +               return PERIPH_ID_I2C1;
>>> > +       case 61:
>>> > +               return PERIPH_ID_I2C2;
>>> > +       case 63:
>>> > +               return PERIPH_ID_I2C3;
>>> > +       case 64:
>>> > +               return PERIPH_ID_I2C4;
>>> > +       case 65:
>>> > +               return PERIPH_ID_I2C5;
>>> > +       }
>>> > +
>>> > +       return -ENOENT;
>>> > +}
>>>
>>> Weird.
>>>
>>> Do you parse the "interrupts" property
>>> to identify the peripheral?
>>>
>>> This is what I really do not like to do.
>>
>> Yes. It's similar to a clock number. It doesn't really matter what we
>> use. The interrupt number is a fixed part of the binding and will
>> never change.
>
>
> I guess it is abuse of "interrupts" property.
>
> Moreover, it can not cover cases where a single signal can be routed to
> several pins.
>

This is code that is specific to RK3288. The numbering scheme is
completely arbitrary and is local to that one function. If we wanted
to use a different scheme (now or later) it would just involve
changing that one function.

Other SoCs may have other rules.

>
> For example, say we want to set pinmux for the I2C channel 0 (SDA0 and SCL0).
>
> On some vendors' SoCs, it is possible to connect SDA0 to either pin10
> or pin20 for example.
> Likewise, SCL0 can be connected to pin11 or pin21.
> (Yes, it is possible on my SoCs)
>
>
> The interrupt ID can identify it is I2C0, and that's it.
>
> Some boards expect the signals come out of pin10, 11,
> and other boards pin20, 21.
>
> The interrupt ID cannot distinguish those cases; the "pinctrl-*"
> property can do it.

The interrupt ID is just a way of determining the peripheral. The
actual pinmux setting for that peripheral is handled by the flags
parameter.

>
> This is mentioned in Documentation/pinctrl.txt and
> that is why a single function can have multiple pin-groups in Linux's
> pinctrl drivers.
>
> I think this design cannot meet some vendors' demand.
> At least, I am unhappy with it.
>
> Could you hold your pinctrl patches and give me time to discuss a bit
> more, please?
>
> I understand how pinctrl drivers work in Linux, so please give me some time
> to consider if better design might be possible.

It is clear that we are heading towards a more general pinmux scheme,
and I can imagine us implementing the pinctrl for some SoCs.

The current proposal is a step towards that but is considerably
simpler. I am familiar with pinctrl but decided against a full
implementation for U-Boot as I feel it is overkill and would not
attract substantial SoC support.

There is no hurry with applying these patches. Let me know what you figure out.

Regards,
Simon

>
>
>
> --
> Best Regards
> Masahiro Yamada
diff mbox

Patch

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index e667361..9e45acd 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -17,3 +17,12 @@  config SPL_PINCTRL_SUPPORT
 	  to adjust pin multiplexing in SPL in order to boot into U-Boot,
 	  enable this option. You will need to enable device tree in SPL
 	  for this to work.
+
+config ROCKCHIP_PINCTRL
+	bool "Rockchip pin control driver"
+	depends on DM
+	help
+	  Support pin multiplexing control on Rockchip SoCs. The driver is
+	  controlled by a device tree node which contains both the GPIO
+	  definitions and pin control functions for each available multiplex
+	  function.
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 6e63ee7..764b1bf 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -6,3 +6,4 @@ 
 #
 
 obj-$(CONFIG_PINCTRL) += pinctrl-uclass.o
+obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
diff --git a/drivers/pinctrl/rockchip/Makefile b/drivers/pinctrl/rockchip/Makefile
new file mode 100644
index 0000000..251bace
--- /dev/null
+++ b/drivers/pinctrl/rockchip/Makefile
@@ -0,0 +1,8 @@ 
+#
+# Copyright (c) 2015 Google, Inc
+# Written by Simon Glass <sjg@chromium.org>
+#
+# SPDX-License-Identifier:	GPL-2.0+
+#
+
+obj-$(CONFIG_ROCKCHIP_PINCTRL) += pinctrl_rk3288.o
diff --git a/drivers/pinctrl/rockchip/pinctrl_rk3288.c b/drivers/pinctrl/rockchip/pinctrl_rk3288.c
new file mode 100644
index 0000000..9a01929
--- /dev/null
+++ b/drivers/pinctrl/rockchip/pinctrl_rk3288.c
@@ -0,0 +1,429 @@ 
+/*
+ * Pinctrl driver for Rockchip SoCs
+ * Copyright (c) 2015 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <pinctrl.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/grf_rk3288.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/periph.h>
+#include <asm/arch/pmu_rk3288.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct rk3288_pinctrl_priv {
+	struct rk3288_grf *grf;
+	struct rk3288_pmu *pmu;
+};
+
+static void pinctrl_rk3288_pwm_config(struct rk3288_grf *grf, int pwm_id)
+{
+	switch (pwm_id) {
+	case PERIPH_ID_PWM0:
+		rk_clrsetreg(&grf->gpio7a_iomux, GPIO7A0_MASK << GPIO7A0_SHIFT,
+			     GPIO7A0_PWM_0 << GPIO7A0_SHIFT);
+		break;
+	case PERIPH_ID_PWM1:
+		rk_clrsetreg(&grf->gpio7a_iomux, GPIO7A1_MASK << GPIO7A1_SHIFT,
+			     GPIO7A1_PWM_1 << GPIO7A1_SHIFT);
+		break;
+	case PERIPH_ID_PWM2:
+		rk_clrsetreg(&grf->gpio7a_iomux, GPIO7C6_MASK << GPIO7C6_SHIFT,
+			     GPIO7C6_PWM_2 << GPIO7C6_SHIFT);
+		break;
+	case PERIPH_ID_PWM3:
+		rk_clrsetreg(&grf->gpio7a_iomux, GPIO7C7_MASK << GPIO7C6_SHIFT,
+			     GPIO7C7_PWM_3 << GPIO7C7_SHIFT);
+		break;
+	default:
+		debug("pwm id = %d iomux error!\n", pwm_id);
+		break;
+	}
+}
+
+static void pinctrl_rk3288_i2c_config(struct rk3288_grf *grf,
+				      struct rk3288_pmu *pmu, int i2c_id)
+{
+	switch (i2c_id) {
+	case PERIPH_ID_I2C0:
+		clrsetbits_le32(&pmu->gpio0b_iomux,
+				GPIO0_B7_MASK << GPIO0_B7_SHIFT,
+				GPIO0_B7_I2C0PMU_SDA << GPIO0_B7_SHIFT);
+		clrsetbits_le32(&pmu->gpio0b_iomux,
+				GPIO0_C0_MASK << GPIO0_C0_SHIFT,
+				GPIO0_C0_I2C0PMU_SCL << GPIO0_C0_SHIFT);
+		break;
+	case PERIPH_ID_I2C1:
+		rk_clrsetreg(&grf->gpio8a_iomux,
+			     GPIO8A4_MASK << GPIO8A4_SHIFT |
+			     GPIO8A5_MASK << GPIO8A5_SHIFT,
+			     GPIO8A4_I2C2SENSOR_SDA << GPIO8A4_SHIFT |
+			     GPIO8A5_I2C2SENSOR_SCL << GPIO8A5_SHIFT);
+		break;
+	case PERIPH_ID_I2C2:
+		rk_clrsetreg(&grf->gpio6b_iomux,
+			     GPIO6B1_MASK << GPIO6B1_SHIFT |
+			     GPIO6B2_MASK << GPIO6B2_SHIFT,
+			     GPIO6B1_I2C1AUDIO_SDA << GPIO6B1_SHIFT |
+			     GPIO6B2_I2C1AUDIO_SCL << GPIO6B2_SHIFT);
+		break;
+	case PERIPH_ID_I2C3:
+		rk_clrsetreg(&grf->gpio2c_iomux,
+			     GPIO2C1_MASK << GPIO2C1_SHIFT |
+			     GPIO2C0_MASK << GPIO2C0_SHIFT,
+			     GPIO2C1_I2C3CAM_SDA << GPIO2C1_SHIFT |
+			     GPIO2C0_I2C3CAM_SCL << GPIO2C0_SHIFT);
+		break;
+	case PERIPH_ID_I2C4:
+		rk_clrsetreg(&grf->gpio7cl_iomux,
+			     GPIO7C1_MASK << GPIO7C1_SHIFT |
+			     GPIO7C2_MASK << GPIO7C2_SHIFT,
+			     GPIO7C1_I2C4TP_SDA << GPIO7C1_SHIFT |
+			     GPIO7C2_I2C4TP_SCL << GPIO7C2_SHIFT);
+		break;
+	case PERIPH_ID_I2C5:
+		rk_clrsetreg(&grf->gpio7cl_iomux,
+			     GPIO7C3_MASK << GPIO7C3_SHIFT,
+			     GPIO7C3_I2C5HDMI_SDA << GPIO7C3_SHIFT);
+		rk_clrsetreg(&grf->gpio7ch_iomux,
+			     GPIO7C4_MASK << GPIO7C4_SHIFT,
+			     GPIO7C4_I2C5HDMI_SCL << GPIO7C4_SHIFT);
+		break;
+	default:
+		debug("i2c id = %d iomux error!\n", i2c_id);
+		break;
+	}
+}
+
+static void pinctrl_rk3288_lcdc_config(struct rk3288_grf *grf, int lcd_id)
+{
+	switch (lcd_id) {
+	case PERIPH_ID_LCDC0:
+		rk_clrsetreg(&grf->gpio1d_iomux,
+			     GPIO1D3_MASK << GPIO1D0_SHIFT |
+			     GPIO1D2_MASK << GPIO1D2_SHIFT |
+			     GPIO1D1_MASK << GPIO1D1_SHIFT |
+			     GPIO1D0_MASK << GPIO1D0_SHIFT,
+			     GPIO1D3_LCDC0_DCLK << GPIO1D3_SHIFT |
+			     GPIO1D2_LCDC0_DEN << GPIO1D2_SHIFT |
+			     GPIO1D1_LCDC0_VSYNC << GPIO1D1_SHIFT |
+			     GPIO1D0_LCDC0_HSYNC << GPIO1D0_SHIFT);
+		break;
+	default:
+		debug("lcdc id = %d iomux error!\n", lcd_id);
+		break;
+	}
+}
+
+static int pinctrl_rk3288_spi_config(struct rk3288_grf *grf,
+				     enum periph_id spi_id, int cs)
+{
+	switch (spi_id) {
+	case PERIPH_ID_SPI0:
+		switch (cs) {
+		case 0:
+			rk_clrsetreg(&grf->gpio5b_iomux,
+				     GPIO5B5_MASK << GPIO5B5_SHIFT,
+				     GPIO5B5_SPI0_CSN0 << GPIO5B5_SHIFT);
+			break;
+		case 1:
+			rk_clrsetreg(&grf->gpio5c_iomux,
+				     GPIO5C0_MASK << GPIO5C0_SHIFT,
+				     GPIO5C0_SPI0_CSN1 << GPIO5C0_SHIFT);
+			break;
+		default:
+			goto err;
+		}
+		rk_clrsetreg(&grf->gpio5b_iomux,
+			     GPIO5B7_MASK << GPIO5B7_SHIFT |
+			     GPIO5B6_MASK << GPIO5B6_SHIFT |
+			     GPIO5B4_MASK << GPIO5B4_SHIFT,
+			     GPIO5B7_SPI0_RXD << GPIO5B7_SHIFT |
+			     GPIO5B6_SPI0_TXD << GPIO5B6_SHIFT |
+			     GPIO5B4_SPI0_CLK << GPIO5B4_SHIFT);
+		break;
+	case PERIPH_ID_SPI1:
+		if (cs != 0)
+			goto err;
+		rk_clrsetreg(&grf->gpio7b_iomux,
+			     GPIO7B6_MASK << GPIO7B6_SHIFT |
+			     GPIO7B7_MASK << GPIO7B7_SHIFT |
+			     GPIO7B5_MASK << GPIO7B5_SHIFT |
+			     GPIO7B4_MASK << GPIO7B4_SHIFT,
+			     GPIO7B6_SPI1_RXD << GPIO7B6_SHIFT |
+			     GPIO7B7_SPI1_TXD << GPIO7B7_SHIFT |
+			     GPIO7B5_SPI1_CSN0 << GPIO7B5_SHIFT |
+			     GPIO7B4_SPI1_CLK << GPIO7B4_SHIFT);
+		break;
+	case PERIPH_ID_SPI2:
+		switch (cs) {
+		case 0:
+			rk_clrsetreg(&grf->gpio8a_iomux,
+				     GPIO8A7_MASK << GPIO8A7_SHIFT,
+				     GPIO8A7_SPI2_CSN0 << GPIO8A7_SHIFT);
+			break;
+		case 1:
+			rk_clrsetreg(&grf->gpio8a_iomux,
+				     GPIO8A3_MASK << GPIO8A3_SHIFT,
+				     GPIO8A3_SPI2_CSN1 << GPIO8A3_SHIFT);
+			break;
+		default:
+			goto err;
+		}
+		rk_clrsetreg(&grf->gpio8b_iomux,
+			     GPIO8B1_MASK << GPIO8B1_SHIFT |
+			     GPIO8B0_MASK << GPIO8B0_SHIFT,
+			     GPIO8B1_SPI2_TXD << GPIO8B1_SHIFT |
+			     GPIO8B0_SPI2_RXD << GPIO8B0_SHIFT);
+		rk_clrsetreg(&grf->gpio8a_iomux,
+			     GPIO8A6_MASK << GPIO8A6_SHIFT,
+			     GPIO8A6_SPI2_CLK << GPIO8A6_SHIFT);
+		break;
+	default:
+		goto err;
+	}
+
+	return 0;
+err:
+	debug("rkspi: periph%d cs=%d not supported", spi_id, cs);
+	return -ENOENT;
+}
+
+static void pinctrl_rk3288_uart_config(struct rk3288_grf *grf, int uart_id)
+{
+	switch (uart_id) {
+	case PERIPH_ID_UART_BT:
+		rk_clrsetreg(&grf->gpio4c_iomux,
+			     GPIO4C3_MASK << GPIO4C3_SHIFT |
+			     GPIO4C2_MASK << GPIO4C2_SHIFT |
+			     GPIO4C1_MASK << GPIO4C1_SHIFT |
+			     GPIO4C0_MASK << GPIO4C0_SHIFT,
+			     GPIO4C3_UART0BT_RTSN << GPIO4C3_SHIFT |
+			     GPIO4C2_UART0BT_CTSN << GPIO4C2_SHIFT |
+			     GPIO4C1_UART0BT_SOUT << GPIO4C1_SHIFT |
+			     GPIO4C0_UART0BT_SIN << GPIO4C0_SHIFT);
+		break;
+	case PERIPH_ID_UART_BB:
+		rk_clrsetreg(&grf->gpio5b_iomux,
+			     GPIO5B3_MASK << GPIO5B3_SHIFT |
+			     GPIO5B2_MASK << GPIO5B2_SHIFT |
+			     GPIO5B1_MASK << GPIO5B1_SHIFT |
+			     GPIO5B0_MASK << GPIO5B0_SHIFT,
+			     GPIO5B3_UART1BB_RTSN << GPIO5B3_SHIFT |
+			     GPIO5B2_UART1BB_CTSN << GPIO5B2_SHIFT |
+			     GPIO5B1_UART1BB_SOUT << GPIO5B1_SHIFT |
+			     GPIO5B0_UART1BB_SIN << GPIO5B0_SHIFT);
+		break;
+	case PERIPH_ID_UART_DBG:
+		rk_clrsetreg(&grf->gpio7ch_iomux,
+			     GPIO7C7_MASK << GPIO7C7_SHIFT |
+			     GPIO7C6_MASK << GPIO7C6_SHIFT,
+			     GPIO7C7_UART2DBG_SOUT << GPIO7C7_SHIFT |
+			     GPIO7C6_UART2DBG_SIN << GPIO7C6_SHIFT);
+		break;
+	case PERIPH_ID_UART_GPS:
+		rk_clrsetreg(&grf->gpio7b_iomux,
+			     GPIO7B2_MASK << GPIO7B2_SHIFT |
+			     GPIO7B1_MASK << GPIO7B1_SHIFT |
+			     GPIO7B0_MASK << GPIO7B0_SHIFT,
+			     GPIO7B2_UART3GPS_RTSN << GPIO7B2_SHIFT |
+			     GPIO7B1_UART3GPS_CTSN << GPIO7B1_SHIFT |
+			     GPIO7B0_UART3GPS_SOUT << GPIO7B0_SHIFT);
+		rk_clrsetreg(&grf->gpio7a_iomux,
+			     GPIO7A7_MASK << GPIO7A7_SHIFT,
+			     GPIO7A7_UART3GPS_SIN << GPIO7A7_SHIFT);
+		break;
+	case PERIPH_ID_UART_EXP:
+		rk_clrsetreg(&grf->gpio5b_iomux,
+			     GPIO5B5_MASK << GPIO5B5_SHIFT |
+			     GPIO5B4_MASK << GPIO5B4_SHIFT |
+			     GPIO5B6_MASK << GPIO5B6_SHIFT |
+			     GPIO5B7_MASK << GPIO5B7_SHIFT,
+			     GPIO5B5_UART4EXP_RTSN << GPIO5B5_SHIFT |
+			     GPIO5B4_UART4EXP_CTSN << GPIO5B4_SHIFT |
+			     GPIO5B6_UART4EXP_SOUT << GPIO5B6_SHIFT |
+			     GPIO5B7_UART4EXP_SIN << GPIO5B7_SHIFT);
+		break;
+	default:
+		debug("uart id = %d iomux error!\n", uart_id);
+		break;
+	}
+}
+
+static void pinctrl_rk3288_sdmmc_config(struct rk3288_grf *grf, int mmc_id)
+{
+	switch (mmc_id) {
+	case PERIPH_ID_EMMC:
+		rk_clrsetreg(&grf->gpio3a_iomux, 0xffff,
+			     GPIO3A7_EMMC_DATA7 << GPIO3A7_SHIFT |
+			     GPIO3A6_EMMC_DATA6 << GPIO3A6_SHIFT |
+			     GPIO3A5_EMMC_DATA5 << GPIO3A5_SHIFT |
+			     GPIO3A4_EMMC_DATA4 << GPIO3A4_SHIFT |
+			     GPIO3A3_EMMC_DATA3 << GPIO3A3_SHIFT |
+			     GPIO3A2_EMMC_DATA2 << GPIO3A2_SHIFT |
+			     GPIO3A1_EMMC_DATA1 << GPIO3A1_SHIFT |
+			     GPIO3A0_EMMC_DATA0 << GPIO3A0_SHIFT);
+		rk_clrsetreg(&grf->gpio3b_iomux, GPIO3B1_MASK << GPIO3B1_SHIFT,
+			     GPIO3B1_EMMC_PWREN << GPIO3B1_SHIFT);
+		rk_clrsetreg(&grf->gpio3c_iomux,
+			     GPIO3C0_MASK << GPIO3C0_SHIFT,
+			     GPIO3C0_EMMC_CMD << GPIO3C0_SHIFT);
+		break;
+	case PERIPH_ID_SDCARD:
+		rk_clrsetreg(&grf->gpio6c_iomux, 0xffff,
+			     GPIO6C6_SDMMC0_DECTN << GPIO6C6_SHIFT |
+			     GPIO6C5_SDMMC0_CMD << GPIO6C5_SHIFT |
+			     GPIO6C4_SDMMC0_CLKOUT << GPIO6C4_SHIFT |
+			     GPIO6C3_SDMMC0_DATA3 << GPIO6C3_SHIFT |
+			     GPIO6C2_SDMMC0_DATA2 << GPIO6C2_SHIFT |
+			     GPIO6C1_SDMMC0_DATA1 << GPIO6C1_SHIFT |
+			     GPIO6C0_SDMMC0_DATA0 << GPIO6C0_SHIFT);
+
+		/* use sdmmc0 io, disable JTAG function */
+		rk_clrsetreg(&grf->soc_con0, 1 << GRF_FORCE_JTAG_SHIFT, 0);
+		break;
+	default:
+		debug("mmc id = %d iomux error!\n", mmc_id);
+		break;
+	}
+}
+
+static void pinctrl_rk3288_hdmi_config(struct rk3288_grf *grf, int hdmi_id)
+{
+	switch (hdmi_id) {
+	case PERIPH_ID_HDMI:
+		rk_clrsetreg(&grf->gpio7cl_iomux, GPIO7C3_MASK << GPIO7C3_SHIFT,
+			     GPIO7C3_EDPHDMII2C_SDA << GPIO7C3_SHIFT);
+		rk_clrsetreg(&grf->gpio7ch_iomux, GPIO7C4_MASK << GPIO7C4_SHIFT,
+			     GPIO7C4_EDPHDMII2C_SCL << GPIO7C4_SHIFT);
+		break;
+	default:
+		debug("hdmi id = %d iomux error!\n", hdmi_id);
+		break;
+	}
+}
+
+static int rk3288_pinctrl_request(struct udevice *dev, int func, int flags)
+{
+	struct rk3288_pinctrl_priv *priv = dev_get_priv(dev);
+
+	debug("%s: func=%x, flags=%x\n", __func__, func, flags);
+	switch (func) {
+	case PERIPH_ID_PWM0:
+	case PERIPH_ID_PWM1:
+	case PERIPH_ID_PWM2:
+	case PERIPH_ID_PWM3:
+	case PERIPH_ID_PWM4:
+		pinctrl_rk3288_pwm_config(priv->grf, func);
+		break;
+	case PERIPH_ID_I2C0:
+	case PERIPH_ID_I2C1:
+	case PERIPH_ID_I2C2:
+	case PERIPH_ID_I2C3:
+	case PERIPH_ID_I2C4:
+	case PERIPH_ID_I2C5:
+		pinctrl_rk3288_i2c_config(priv->grf, priv->pmu, func);
+		break;
+	case PERIPH_ID_SPI0:
+	case PERIPH_ID_SPI1:
+	case PERIPH_ID_SPI2:
+		pinctrl_rk3288_spi_config(priv->grf, func, flags);
+		break;
+	case PERIPH_ID_UART0:
+	case PERIPH_ID_UART1:
+	case PERIPH_ID_UART2:
+	case PERIPH_ID_UART3:
+	case PERIPH_ID_UART4:
+		pinctrl_rk3288_uart_config(priv->grf, func);
+		break;
+	case PERIPH_ID_LCDC0:
+	case PERIPH_ID_LCDC1:
+		pinctrl_rk3288_lcdc_config(priv->grf, func);
+		break;
+	case PERIPH_ID_SDMMC0:
+	case PERIPH_ID_SDMMC1:
+		pinctrl_rk3288_sdmmc_config(priv->grf, func);
+		break;
+	case PERIPH_ID_HDMI:
+		pinctrl_rk3288_hdmi_config(priv->grf, func);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rk3288_pinctrl_get_periph_id(struct udevice *dev,
+					struct udevice *periph)
+{
+	u32 cell[3];
+	int ret;
+
+	ret = fdtdec_get_int_array(gd->fdt_blob, periph->of_offset,
+				   "interrupts", cell, ARRAY_SIZE(cell));
+	if (ret < 0)
+		return -EINVAL;
+
+	switch (cell[1]) {
+	case 44:
+		return PERIPH_ID_SPI0;
+	case 45:
+		return PERIPH_ID_SPI1;
+	case 46:
+		return PERIPH_ID_SPI2;
+	case 60:
+		return PERIPH_ID_I2C0;
+	case 62: /* Note strange order */
+		return PERIPH_ID_I2C1;
+	case 61:
+		return PERIPH_ID_I2C2;
+	case 63:
+		return PERIPH_ID_I2C3;
+	case 64:
+		return PERIPH_ID_I2C4;
+	case 65:
+		return PERIPH_ID_I2C5;
+	}
+
+	return -ENOENT;
+}
+
+static struct pinctrl_ops rk3288_pinctrl_ops = {
+	.request	= rk3288_pinctrl_request,
+	.get_periph_id	= rk3288_pinctrl_get_periph_id,
+};
+
+static int rk3288_pinctrl_probe(struct udevice *dev)
+{
+	struct rk3288_pinctrl_priv *priv = dev_get_priv(dev);
+
+	priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
+	priv->pmu = syscon_get_first_range(ROCKCHIP_SYSCON_PMU);
+	debug("%s: grf=%p, pmu=%p\n", __func__, priv->grf, priv->pmu);
+
+	return 0;
+}
+
+static const struct udevice_id rk3288_pinctrl_ids[] = {
+	{ .compatible = "rockchip,rk3288-pinctrl" },
+	{ }
+};
+
+U_BOOT_DRIVER(pinctrl_rk3288) = {
+	.name		= "pinctrl_rk3288",
+	.id		= UCLASS_PINCTRL,
+	.of_match	= rk3288_pinctrl_ids,
+	.priv_auto_alloc_size = sizeof(struct rk3288_pinctrl_priv),
+	.ops		= &rk3288_pinctrl_ops,
+	.probe		= rk3288_pinctrl_probe,
+};