diff mbox series

[3/3] rockchip: Port IO-domain driver for RK3568 from linux

Message ID 20230806120420.1275052-4-jonas@kwiboo.se
State Superseded
Delegated to: Kever Yang
Headers show
Series rockchip: Port IO-domain driver for RK3568 from linux | expand

Commit Message

Jonas Karlman Aug. 6, 2023, 12:04 p.m. UTC
Port the Rockchip IO-domain driver for RK3568 from linux.

The driver auto probe after bind to configure IO-domain based on the
regulator voltage. Compared to the linux driver this driver is not
notified about regulator voltage changes and only configure IO-domain
based on the initial voltage autoset by the regulator.

It is not recommended to enable MMC_IO_VOLTAGE or the mmc signal voltage
and IO-domain may end up out of sync.

Based on the linux commit 28b05a64e47c ("soc: rockchip: io-domain: add
rk3568 support").

Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
Cc: Jianqun Xu <jay.xu@rock-chips.com>
Cc: Heiko Stuebner <heiko@sntech.de>
Cc: Doug Anderson <dianders@chromium.org>
---
 drivers/misc/Kconfig              |   9 ++
 drivers/misc/Makefile             |   1 +
 drivers/misc/rockchip-io-domain.c | 157 ++++++++++++++++++++++++++++++
 3 files changed, 167 insertions(+)
 create mode 100644 drivers/misc/rockchip-io-domain.c

Comments

Simon Glass Aug. 7, 2023, 1:33 a.m. UTC | #1
Hi Jonas,

On Sun, 6 Aug 2023 at 06:04, Jonas Karlman <jonas@kwiboo.se> wrote:
>
> Port the Rockchip IO-domain driver for RK3568 from linux.
>
> The driver auto probe after bind to configure IO-domain based on the
> regulator voltage. Compared to the linux driver this driver is not
> notified about regulator voltage changes and only configure IO-domain
> based on the initial voltage autoset by the regulator.
>
> It is not recommended to enable MMC_IO_VOLTAGE or the mmc signal voltage
> and IO-domain may end up out of sync.
>
> Based on the linux commit 28b05a64e47c ("soc: rockchip: io-domain: add
> rk3568 support").
>
> Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
> ---
> Cc: Jianqun Xu <jay.xu@rock-chips.com>
> Cc: Heiko Stuebner <heiko@sntech.de>
> Cc: Doug Anderson <dianders@chromium.org>
> ---
>  drivers/misc/Kconfig              |   9 ++
>  drivers/misc/Makefile             |   1 +
>  drivers/misc/rockchip-io-domain.c | 157 ++++++++++++++++++++++++++++++
>  3 files changed, 167 insertions(+)
>  create mode 100644 drivers/misc/rockchip-io-domain.c

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

nits below

>
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index b9f5c7a37aed..d160ce693939 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -101,6 +101,15 @@ config ROCKCHIP_OTP
>           addressing and a length or through child-nodes that are generated
>           based on the e-fuse map retrieved from the DTS.
>
> +config ROCKCHIP_IODOMAIN
> +       bool "Rockchip IO-domain driver support"
> +       depends on DM_REGULATOR && ARCH_ROCKCHIP
> +       default y if ROCKCHIP_RK3568
> +       help
> +         Enable support for IO-domains in Rockchip SoCs. It is necessary
> +         for the IO-domain setting of the SoC to match the voltage supplied
> +         by the regulators.
> +
>  config SIFIVE_OTP
>         bool "SiFive eMemory OTP driver"
>         depends on MISC
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index fd8805f34bd9..b67b82358a6c 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -69,6 +69,7 @@ obj-$(CONFIG_SANDBOX) += qfw_sandbox.o
>  endif
>  obj-$(CONFIG_ROCKCHIP_EFUSE) += rockchip-efuse.o
>  obj-$(CONFIG_ROCKCHIP_OTP) += rockchip-otp.o
> +obj-$(CONFIG_$(SPL_TPL_)ROCKCHIP_IODOMAIN) += rockchip-io-domain.o
>  obj-$(CONFIG_SANDBOX) += syscon_sandbox.o misc_sandbox.o
>  obj-$(CONFIG_SIFIVE_OTP) += sifive-otp.o
>  obj-$(CONFIG_SMSC_LPC47M) += smsc_lpc47m.o
> diff --git a/drivers/misc/rockchip-io-domain.c b/drivers/misc/rockchip-io-domain.c
> new file mode 100644
> index 000000000000..e458e3a17e2d
> --- /dev/null
> +++ b/drivers/misc/rockchip-io-domain.c
> @@ -0,0 +1,157 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <common.h>
> +#include <dm.h>
> +#include <dm/device_compat.h>
> +#include <regmap.h>
> +#include <syscon.h>
> +#include <power/regulator.h>
> +
> +#define MAX_SUPPLIES           16
> +
> +/*
> + * The max voltage for 1.8V and 3.3V come from the Rockchip datasheet under
> + * "Recommended Operating Conditions" for "Digital GPIO".   When the typical
> + * is 3.3V the max is 3.6V.  When the typical is 1.8V the max is 1.98V.
> + *
> + * They are used like this:
> + * - If the voltage on a rail is above the "1.8" voltage (1.98V) we'll tell the
> + *   SoC we're at 3.3.
> + * - If the voltage on a rail is above the "3.3" voltage (3.6V) we'll consider
> + *   that to be an error.
> + */
> +#define MAX_VOLTAGE_1_8                1980000
> +#define MAX_VOLTAGE_3_3                3600000
> +
> +#define RK3568_PMU_GRF_IO_VSEL0                (0x0140)
> +#define RK3568_PMU_GRF_IO_VSEL1                (0x0144)
> +#define RK3568_PMU_GRF_IO_VSEL2                (0x0148)

Drop brackets

Do we need the RK3568_ prefix?

> +
> +struct rockchip_iodomain_soc_data {
> +       int grf_offset;
> +       const char *supply_names[MAX_SUPPLIES];
> +       int (*write)(struct regmap *grf, int idx, int uV);
> +};
> +
> +static int rk3568_iodomain_write(struct regmap *grf, int idx, int uV)
> +{
> +       u32 is_3v3 = uV > MAX_VOLTAGE_1_8;
> +       u32 val0, val1;
> +       int b;
> +
> +       switch (idx) {
> +       case 0: /* pmuio1 */
> +               break;
> +       case 1: /* pmuio2 */
> +               b = idx;
> +               val0 = BIT(16 + b) | (is_3v3 ? 0 : BIT(b));
> +               b = idx + 4;
> +               val1 = BIT(16 + b) | (is_3v3 ? BIT(b) : 0);
> +
> +               regmap_write(grf, RK3568_PMU_GRF_IO_VSEL2, val0);
> +               regmap_write(grf, RK3568_PMU_GRF_IO_VSEL2, val1);
> +               break;
> +       case 3: /* vccio2 */
> +               break;
> +       case 2: /* vccio1 */
> +       case 4: /* vccio3 */
> +       case 5: /* vccio4 */
> +       case 6: /* vccio5 */
> +       case 7: /* vccio6 */
> +       case 8: /* vccio7 */
> +               b = idx - 1;
> +               val0 = BIT(16 + b) | (is_3v3 ? 0 : BIT(b));
> +               val1 = BIT(16 + b) | (is_3v3 ? BIT(b) : 0);
> +
> +               regmap_write(grf, RK3568_PMU_GRF_IO_VSEL0, val0);
> +               regmap_write(grf, RK3568_PMU_GRF_IO_VSEL1, val1);
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct rockchip_iodomain_soc_data soc_data_rk3568_pmu = {
> +       .grf_offset = 0x140,
> +       .supply_names = {
> +               NULL,
> +               "pmuio2-supply",
> +               "vccio1-supply",
> +               NULL,
> +               "vccio3-supply",
> +               "vccio4-supply",
> +               "vccio5-supply",
> +               "vccio6-supply",
> +               "vccio7-supply",
> +       },
> +       .write = rk3568_iodomain_write,
> +};
> +
> +static const struct udevice_id rockchip_iodomain_ids[] = {
> +       {
> +               .compatible = "rockchip,rk3568-pmu-io-voltage-domain",
> +               .data = (ulong)&soc_data_rk3568_pmu,
> +       },
> +       { }
> +};
> +
> +static int rockchip_iodomain_bind(struct udevice *dev)
> +{
> +       dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);


Why is this needed?

> +
> +       return 0;
> +}
> +
> +static int rockchip_iodomain_probe(struct udevice *dev)
> +{
> +       struct rockchip_iodomain_soc_data *soc_data =
> +               (struct rockchip_iodomain_soc_data *)dev_get_driver_data(dev);
> +       struct regmap *grf;
> +       int ret;
> +
> +       grf = syscon_get_regmap(dev_get_parent(dev));
> +       if (IS_ERR(grf))
> +               return PTR_ERR(grf);
> +
> +       for (int i = 0; i < MAX_SUPPLIES; i++) {
> +               const char *supply_name = soc_data->supply_names[i];
> +               struct udevice *reg;
> +               int uV;
> +
> +               if (!supply_name)
> +                       continue;
> +
> +               ret = device_get_supply_regulator(dev, supply_name, &reg);
> +               if (ret)
> +                       continue;
> +
> +               ret = regulator_autoset(reg);
> +               if (ret && ret != -EALREADY && ret != -EMEDIUMTYPE &&
> +                   ret != -ENOSYS)
> +                       continue;
> +
> +               uV = regulator_get_value(reg);
> +               if (uV <= 0)
> +                       continue;
> +
> +               if (uV > MAX_VOLTAGE_3_3) {
> +                       dev_crit(dev, "%s: %d uV is too high. May damage SoC!\n",
> +                                supply_name, uV);
> +                       continue;
> +               }
> +
> +               soc_data->write(grf, i, uV);
> +       }
> +
> +       return 0;
> +}
> +
> +U_BOOT_DRIVER(rockchip_iodomain) = {
> +       .name = "rockchip_iodomain",
> +       .id = UCLASS_NOP,

So this just exists to probe some power supplies at the start?

> +       .of_match = rockchip_iodomain_ids,
> +       .bind = rockchip_iodomain_bind,
> +       .probe = rockchip_iodomain_probe,
> +};
> --
> 2.41.0
>

Regards,
Simon
Jonas Karlman Aug. 7, 2023, 2:17 a.m. UTC | #2
Hi Simon,
On 2023-08-07 03:33, Simon Glass wrote:
> Hi Jonas,
> 
> On Sun, 6 Aug 2023 at 06:04, Jonas Karlman <jonas@kwiboo.se> wrote:
>>
>> Port the Rockchip IO-domain driver for RK3568 from linux.
>>
>> The driver auto probe after bind to configure IO-domain based on the
>> regulator voltage. Compared to the linux driver this driver is not
>> notified about regulator voltage changes and only configure IO-domain
>> based on the initial voltage autoset by the regulator.
>>
>> It is not recommended to enable MMC_IO_VOLTAGE or the mmc signal voltage
>> and IO-domain may end up out of sync.
>>
>> Based on the linux commit 28b05a64e47c ("soc: rockchip: io-domain: add
>> rk3568 support").
>>
>> Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
>> ---
>> Cc: Jianqun Xu <jay.xu@rock-chips.com>
>> Cc: Heiko Stuebner <heiko@sntech.de>
>> Cc: Doug Anderson <dianders@chromium.org>
>> ---
>>  drivers/misc/Kconfig              |   9 ++
>>  drivers/misc/Makefile             |   1 +
>>  drivers/misc/rockchip-io-domain.c | 157 ++++++++++++++++++++++++++++++
>>  3 files changed, 167 insertions(+)
>>  create mode 100644 drivers/misc/rockchip-io-domain.c
> 
> Reviewed-by: Simon Glass <sjg@chromium.org>
> 
> nits below
> 
>>
>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>> index b9f5c7a37aed..d160ce693939 100644
>> --- a/drivers/misc/Kconfig
>> +++ b/drivers/misc/Kconfig
>> @@ -101,6 +101,15 @@ config ROCKCHIP_OTP
>>           addressing and a length or through child-nodes that are generated
>>           based on the e-fuse map retrieved from the DTS.
>>
>> +config ROCKCHIP_IODOMAIN
>> +       bool "Rockchip IO-domain driver support"
>> +       depends on DM_REGULATOR && ARCH_ROCKCHIP
>> +       default y if ROCKCHIP_RK3568
>> +       help
>> +         Enable support for IO-domains in Rockchip SoCs. It is necessary
>> +         for the IO-domain setting of the SoC to match the voltage supplied
>> +         by the regulators.
>> +
>>  config SIFIVE_OTP
>>         bool "SiFive eMemory OTP driver"
>>         depends on MISC
>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>> index fd8805f34bd9..b67b82358a6c 100644
>> --- a/drivers/misc/Makefile
>> +++ b/drivers/misc/Makefile
>> @@ -69,6 +69,7 @@ obj-$(CONFIG_SANDBOX) += qfw_sandbox.o
>>  endif
>>  obj-$(CONFIG_ROCKCHIP_EFUSE) += rockchip-efuse.o
>>  obj-$(CONFIG_ROCKCHIP_OTP) += rockchip-otp.o
>> +obj-$(CONFIG_$(SPL_TPL_)ROCKCHIP_IODOMAIN) += rockchip-io-domain.o
>>  obj-$(CONFIG_SANDBOX) += syscon_sandbox.o misc_sandbox.o
>>  obj-$(CONFIG_SIFIVE_OTP) += sifive-otp.o
>>  obj-$(CONFIG_SMSC_LPC47M) += smsc_lpc47m.o
>> diff --git a/drivers/misc/rockchip-io-domain.c b/drivers/misc/rockchip-io-domain.c
>> new file mode 100644
>> index 000000000000..e458e3a17e2d
>> --- /dev/null
>> +++ b/drivers/misc/rockchip-io-domain.c
>> @@ -0,0 +1,157 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +#include <common.h>
>> +#include <dm.h>
>> +#include <dm/device_compat.h>
>> +#include <regmap.h>
>> +#include <syscon.h>
>> +#include <power/regulator.h>
>> +
>> +#define MAX_SUPPLIES           16
>> +
>> +/*
>> + * The max voltage for 1.8V and 3.3V come from the Rockchip datasheet under
>> + * "Recommended Operating Conditions" for "Digital GPIO".   When the typical
>> + * is 3.3V the max is 3.6V.  When the typical is 1.8V the max is 1.98V.
>> + *
>> + * They are used like this:
>> + * - If the voltage on a rail is above the "1.8" voltage (1.98V) we'll tell the
>> + *   SoC we're at 3.3.
>> + * - If the voltage on a rail is above the "3.3" voltage (3.6V) we'll consider
>> + *   that to be an error.
>> + */
>> +#define MAX_VOLTAGE_1_8                1980000
>> +#define MAX_VOLTAGE_3_3                3600000
>> +
>> +#define RK3568_PMU_GRF_IO_VSEL0                (0x0140)
>> +#define RK3568_PMU_GRF_IO_VSEL1                (0x0144)
>> +#define RK3568_PMU_GRF_IO_VSEL2                (0x0148)
> 
> Drop brackets

Will do in a v2.

> 
> Do we need the RK3568_ prefix?

Yes, this only imports the RK3568 parts almost 1:1 from linux at this
stage. Support for other SoCs should be added in the future so that we
can move away from having to hard-code IO-domain settings in board init.

RockPro64 configures two IO-domains in misc_init_r, that can be dropped
and would be handled by this driver once RK3399 support has been ported.
https://source.denx.de/u-boot/u-boot/-/blob/master/board/pine64/rockpro64_rk3399/rockpro64-rk3399.c#L20-32

Linux driver has support for 10 different SoCs:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/soc/rockchip/io-domain.c#n510

> 
>> +
>> +struct rockchip_iodomain_soc_data {
>> +       int grf_offset;
>> +       const char *supply_names[MAX_SUPPLIES];
>> +       int (*write)(struct regmap *grf, int idx, int uV);
>> +};
>> +
>> +static int rk3568_iodomain_write(struct regmap *grf, int idx, int uV)
>> +{
>> +       u32 is_3v3 = uV > MAX_VOLTAGE_1_8;
>> +       u32 val0, val1;
>> +       int b;
>> +
>> +       switch (idx) {
>> +       case 0: /* pmuio1 */
>> +               break;
>> +       case 1: /* pmuio2 */
>> +               b = idx;
>> +               val0 = BIT(16 + b) | (is_3v3 ? 0 : BIT(b));
>> +               b = idx + 4;
>> +               val1 = BIT(16 + b) | (is_3v3 ? BIT(b) : 0);
>> +
>> +               regmap_write(grf, RK3568_PMU_GRF_IO_VSEL2, val0);
>> +               regmap_write(grf, RK3568_PMU_GRF_IO_VSEL2, val1);
>> +               break;
>> +       case 3: /* vccio2 */
>> +               break;
>> +       case 2: /* vccio1 */
>> +       case 4: /* vccio3 */
>> +       case 5: /* vccio4 */
>> +       case 6: /* vccio5 */
>> +       case 7: /* vccio6 */
>> +       case 8: /* vccio7 */
>> +               b = idx - 1;
>> +               val0 = BIT(16 + b) | (is_3v3 ? 0 : BIT(b));
>> +               val1 = BIT(16 + b) | (is_3v3 ? BIT(b) : 0);
>> +
>> +               regmap_write(grf, RK3568_PMU_GRF_IO_VSEL0, val0);
>> +               regmap_write(grf, RK3568_PMU_GRF_IO_VSEL1, val1);
>> +               break;
>> +       default:
>> +               return -EINVAL;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct rockchip_iodomain_soc_data soc_data_rk3568_pmu = {
>> +       .grf_offset = 0x140,
>> +       .supply_names = {
>> +               NULL,
>> +               "pmuio2-supply",
>> +               "vccio1-supply",
>> +               NULL,
>> +               "vccio3-supply",
>> +               "vccio4-supply",
>> +               "vccio5-supply",
>> +               "vccio6-supply",
>> +               "vccio7-supply",
>> +       },
>> +       .write = rk3568_iodomain_write,
>> +};
>> +
>> +static const struct udevice_id rockchip_iodomain_ids[] = {
>> +       {
>> +               .compatible = "rockchip,rk3568-pmu-io-voltage-domain",
>> +               .data = (ulong)&soc_data_rk3568_pmu,
>> +       },
>> +       { }
>> +};
>> +
>> +static int rockchip_iodomain_bind(struct udevice *dev)
>> +{
>> +       dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
> 
> 
> Why is this needed?

So that the driver auto probe as early as possible. Is there any other
way of telling that this device must be probed? There is no real
consumer of this device so lazy loading it does not work.

> 
>> +
>> +       return 0;
>> +}
>> +
>> +static int rockchip_iodomain_probe(struct udevice *dev)
>> +{
>> +       struct rockchip_iodomain_soc_data *soc_data =
>> +               (struct rockchip_iodomain_soc_data *)dev_get_driver_data(dev);
>> +       struct regmap *grf;
>> +       int ret;
>> +
>> +       grf = syscon_get_regmap(dev_get_parent(dev));
>> +       if (IS_ERR(grf))
>> +               return PTR_ERR(grf);
>> +
>> +       for (int i = 0; i < MAX_SUPPLIES; i++) {
>> +               const char *supply_name = soc_data->supply_names[i];
>> +               struct udevice *reg;
>> +               int uV;
>> +
>> +               if (!supply_name)
>> +                       continue;
>> +
>> +               ret = device_get_supply_regulator(dev, supply_name, &reg);
>> +               if (ret)
>> +                       continue;
>> +
>> +               ret = regulator_autoset(reg);
>> +               if (ret && ret != -EALREADY && ret != -EMEDIUMTYPE &&
>> +                   ret != -ENOSYS)
>> +                       continue;
>> +
>> +               uV = regulator_get_value(reg);
>> +               if (uV <= 0)
>> +                       continue;
>> +
>> +               if (uV > MAX_VOLTAGE_3_3) {
>> +                       dev_crit(dev, "%s: %d uV is too high. May damage SoC!\n",
>> +                                supply_name, uV);
>> +                       continue;
>> +               }
>> +
>> +               soc_data->write(grf, i, uV);
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +U_BOOT_DRIVER(rockchip_iodomain) = {
>> +       .name = "rockchip_iodomain",
>> +       .id = UCLASS_NOP,
> 
> So this just exists to probe some power supplies at the start?

No, it exists to configure the different VCCIO-domains in 1.8V or 3.3V
mode to match the voltage of the input supply of a IO-domain. In order
to do that it needs to probe each IO-domains input supply regulator.

To quote from a reference schematics [1] for RK3568:

When the IO domain power supply voltage is 1.8V, the IO domain voltage
configuration in DTS must be set to 1.8V mode. If it is misconfigured to
3.3V mode, the IO function of this power domain will be abnormally;

When the IO domain power supply voltage is 3.3V, the IO domain voltage
configuration in DTS must be set to 3.3V mode. If it is misconfigured to
1.8V mode, the IO in this power domain will be in overvoltage state, and
the IO will be damaged after long-term operation.

On RK3568 an issue with ethernet was observed, TX packages would not
reach the network, yet RX packages was received. Turned out that the
VCCIO-domain was default configured in 3.3V, and input supply provided
and PHY expected 1.8V.

[1] http://opensource.rock-chips.com/images/5/56/RK3568_hardware_reference_20220806.zip

Regards,
Jonas

> 
>> +       .of_match = rockchip_iodomain_ids,
>> +       .bind = rockchip_iodomain_bind,
>> +       .probe = rockchip_iodomain_probe,
>> +};
>> --
>> 2.41.0
>>
> 
> Regards,
> Simon
diff mbox series

Patch

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b9f5c7a37aed..d160ce693939 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -101,6 +101,15 @@  config ROCKCHIP_OTP
 	  addressing and a length or through child-nodes that are generated
 	  based on the e-fuse map retrieved from the DTS.
 
+config ROCKCHIP_IODOMAIN
+	bool "Rockchip IO-domain driver support"
+	depends on DM_REGULATOR && ARCH_ROCKCHIP
+	default y if ROCKCHIP_RK3568
+	help
+	  Enable support for IO-domains in Rockchip SoCs. It is necessary
+	  for the IO-domain setting of the SoC to match the voltage supplied
+	  by the regulators.
+
 config SIFIVE_OTP
 	bool "SiFive eMemory OTP driver"
 	depends on MISC
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index fd8805f34bd9..b67b82358a6c 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -69,6 +69,7 @@  obj-$(CONFIG_SANDBOX) += qfw_sandbox.o
 endif
 obj-$(CONFIG_ROCKCHIP_EFUSE) += rockchip-efuse.o
 obj-$(CONFIG_ROCKCHIP_OTP) += rockchip-otp.o
+obj-$(CONFIG_$(SPL_TPL_)ROCKCHIP_IODOMAIN) += rockchip-io-domain.o
 obj-$(CONFIG_SANDBOX) += syscon_sandbox.o misc_sandbox.o
 obj-$(CONFIG_SIFIVE_OTP) += sifive-otp.o
 obj-$(CONFIG_SMSC_LPC47M) += smsc_lpc47m.o
diff --git a/drivers/misc/rockchip-io-domain.c b/drivers/misc/rockchip-io-domain.c
new file mode 100644
index 000000000000..e458e3a17e2d
--- /dev/null
+++ b/drivers/misc/rockchip-io-domain.c
@@ -0,0 +1,157 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <common.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <power/regulator.h>
+
+#define MAX_SUPPLIES		16
+
+/*
+ * The max voltage for 1.8V and 3.3V come from the Rockchip datasheet under
+ * "Recommended Operating Conditions" for "Digital GPIO".   When the typical
+ * is 3.3V the max is 3.6V.  When the typical is 1.8V the max is 1.98V.
+ *
+ * They are used like this:
+ * - If the voltage on a rail is above the "1.8" voltage (1.98V) we'll tell the
+ *   SoC we're at 3.3.
+ * - If the voltage on a rail is above the "3.3" voltage (3.6V) we'll consider
+ *   that to be an error.
+ */
+#define MAX_VOLTAGE_1_8		1980000
+#define MAX_VOLTAGE_3_3		3600000
+
+#define RK3568_PMU_GRF_IO_VSEL0		(0x0140)
+#define RK3568_PMU_GRF_IO_VSEL1		(0x0144)
+#define RK3568_PMU_GRF_IO_VSEL2		(0x0148)
+
+struct rockchip_iodomain_soc_data {
+	int grf_offset;
+	const char *supply_names[MAX_SUPPLIES];
+	int (*write)(struct regmap *grf, int idx, int uV);
+};
+
+static int rk3568_iodomain_write(struct regmap *grf, int idx, int uV)
+{
+	u32 is_3v3 = uV > MAX_VOLTAGE_1_8;
+	u32 val0, val1;
+	int b;
+
+	switch (idx) {
+	case 0: /* pmuio1 */
+		break;
+	case 1: /* pmuio2 */
+		b = idx;
+		val0 = BIT(16 + b) | (is_3v3 ? 0 : BIT(b));
+		b = idx + 4;
+		val1 = BIT(16 + b) | (is_3v3 ? BIT(b) : 0);
+
+		regmap_write(grf, RK3568_PMU_GRF_IO_VSEL2, val0);
+		regmap_write(grf, RK3568_PMU_GRF_IO_VSEL2, val1);
+		break;
+	case 3: /* vccio2 */
+		break;
+	case 2: /* vccio1 */
+	case 4: /* vccio3 */
+	case 5: /* vccio4 */
+	case 6: /* vccio5 */
+	case 7: /* vccio6 */
+	case 8: /* vccio7 */
+		b = idx - 1;
+		val0 = BIT(16 + b) | (is_3v3 ? 0 : BIT(b));
+		val1 = BIT(16 + b) | (is_3v3 ? BIT(b) : 0);
+
+		regmap_write(grf, RK3568_PMU_GRF_IO_VSEL0, val0);
+		regmap_write(grf, RK3568_PMU_GRF_IO_VSEL1, val1);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct rockchip_iodomain_soc_data soc_data_rk3568_pmu = {
+	.grf_offset = 0x140,
+	.supply_names = {
+		NULL,
+		"pmuio2-supply",
+		"vccio1-supply",
+		NULL,
+		"vccio3-supply",
+		"vccio4-supply",
+		"vccio5-supply",
+		"vccio6-supply",
+		"vccio7-supply",
+	},
+	.write = rk3568_iodomain_write,
+};
+
+static const struct udevice_id rockchip_iodomain_ids[] = {
+	{
+		.compatible = "rockchip,rk3568-pmu-io-voltage-domain",
+		.data = (ulong)&soc_data_rk3568_pmu,
+	},
+	{ }
+};
+
+static int rockchip_iodomain_bind(struct udevice *dev)
+{
+	dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
+
+	return 0;
+}
+
+static int rockchip_iodomain_probe(struct udevice *dev)
+{
+	struct rockchip_iodomain_soc_data *soc_data =
+		(struct rockchip_iodomain_soc_data *)dev_get_driver_data(dev);
+	struct regmap *grf;
+	int ret;
+
+	grf = syscon_get_regmap(dev_get_parent(dev));
+	if (IS_ERR(grf))
+		return PTR_ERR(grf);
+
+	for (int i = 0; i < MAX_SUPPLIES; i++) {
+		const char *supply_name = soc_data->supply_names[i];
+		struct udevice *reg;
+		int uV;
+
+		if (!supply_name)
+			continue;
+
+		ret = device_get_supply_regulator(dev, supply_name, &reg);
+		if (ret)
+			continue;
+
+		ret = regulator_autoset(reg);
+		if (ret && ret != -EALREADY && ret != -EMEDIUMTYPE &&
+		    ret != -ENOSYS)
+			continue;
+
+		uV = regulator_get_value(reg);
+		if (uV <= 0)
+			continue;
+
+		if (uV > MAX_VOLTAGE_3_3) {
+			dev_crit(dev, "%s: %d uV is too high. May damage SoC!\n",
+				 supply_name, uV);
+			continue;
+		}
+
+		soc_data->write(grf, i, uV);
+	}
+
+	return 0;
+}
+
+U_BOOT_DRIVER(rockchip_iodomain) = {
+	.name = "rockchip_iodomain",
+	.id = UCLASS_NOP,
+	.of_match = rockchip_iodomain_ids,
+	.bind = rockchip_iodomain_bind,
+	.probe = rockchip_iodomain_probe,
+};