diff mbox series

[v2,05/16] power: rk8xx: add support for RK806

Message ID 20240223-rk3588-saradc-v2-5-11c046b7e94a@theobroma-systems.com
State Superseded
Delegated to: Kever Yang
Headers show
Series rockchip: add support for SARADCv2 and RK806 PMIC and regulators | expand

Commit Message

Quentin Schulz Feb. 23, 2024, 9:05 a.m. UTC
From: Quentin Schulz <quentin.schulz@theobroma-systems.com>

This adds support for RK806, only the SPI variant has been tested.

The communication "protocol" over SPI is the following:
 - write three bytes:
   - 1 byte: [0:3] length of the payload, [6] Enable CRC, [7] Write
   - 1 byte: LSB register address
   - 1 byte: MSB register address
 - write/read length of payload

The CRC is always disabled for now.

The RK806 technically supports I2C as well, and this should be able to
support it without any change, but it wasn't tested.

The DT node name prefix for the buck converters has changed in the
Device Tree and is now dcdc-reg. The logic for buck converters is
however manageable within the current logic inside the rk8xx regulator
driver. The same cannot be said for the NLDO and PLDO.

Because pmic_bind_children() parses the DT nodes and extracts the LDO
index from the DT node name, NLDO and PLDO will have overlapping
indices. Therefore, we need a separate logic from the already-existing
ldo callbacks. Let's reuse as much as possible though.

Cc: Quentin Schulz <foss+uboot@0leil.net>
Signed-off-by: Quentin Schulz <quentin.schulz@theobroma-systems.com>
---
 drivers/power/pmic/rk8xx.c      |  85 +++++++
 drivers/power/regulator/rk8xx.c | 514 +++++++++++++++++++++++++++++++++++++++-
 include/power/rk8xx_pmic.h      |  19 ++
 3 files changed, 616 insertions(+), 2 deletions(-)

Comments

Jonas Karlman Feb. 29, 2024, 11:47 a.m. UTC | #1
Hi Quentin,

On 2024-02-23 10:05, Quentin Schulz wrote:
> From: Quentin Schulz <quentin.schulz@theobroma-systems.com>
> 
> This adds support for RK806, only the SPI variant has been tested.
> 
> The communication "protocol" over SPI is the following:
>  - write three bytes:
>    - 1 byte: [0:3] length of the payload, [6] Enable CRC, [7] Write
>    - 1 byte: LSB register address
>    - 1 byte: MSB register address
>  - write/read length of payload
> 
> The CRC is always disabled for now.
> 
> The RK806 technically supports I2C as well, and this should be able to
> support it without any change, but it wasn't tested.
> 
> The DT node name prefix for the buck converters has changed in the
> Device Tree and is now dcdc-reg. The logic for buck converters is
> however manageable within the current logic inside the rk8xx regulator
> driver. The same cannot be said for the NLDO and PLDO.
> 
> Because pmic_bind_children() parses the DT nodes and extracts the LDO
> index from the DT node name, NLDO and PLDO will have overlapping
> indices. Therefore, we need a separate logic from the already-existing
> ldo callbacks. Let's reuse as much as possible though.
> 
> Cc: Quentin Schulz <foss+uboot@0leil.net>
> Signed-off-by: Quentin Schulz <quentin.schulz@theobroma-systems.com>
> ---
>  drivers/power/pmic/rk8xx.c      |  85 +++++++
>  drivers/power/regulator/rk8xx.c | 514 +++++++++++++++++++++++++++++++++++++++-
>  include/power/rk8xx_pmic.h      |  19 ++
>  3 files changed, 616 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/power/pmic/rk8xx.c b/drivers/power/pmic/rk8xx.c
> index 4e3a17337ee..15cb82029cc 100644
> --- a/drivers/power/pmic/rk8xx.c
> +++ b/drivers/power/pmic/rk8xx.c
> @@ -9,8 +9,10 @@
>  #include <dm/lists.h>
>  #include <errno.h>
>  #include <log.h>
> +#include <linux/bitfield.h>
>  #include <power/rk8xx_pmic.h>
>  #include <power/pmic.h>
> +#include <spi.h>
>  #include <sysreset.h>
>  
>  static int rk8xx_sysreset_request(struct udevice *dev, enum sysreset_t type)
> @@ -32,6 +34,10 @@ static int rk8xx_sysreset_request(struct udevice *dev, enum sysreset_t type)
>  		pmic_clrsetbits(dev->parent, RK817_REG_SYS_CFG3, 0,
>  				BIT(0));
>  		break;
> +	case RK806_ID:
> +		pmic_clrsetbits(dev->parent, RK806_REG_SYS_CFG3, 0,
> +				BIT(0));
> +		break;
>  	default:
>  		printf("Unknown PMIC RK%x: Cannot shutdown\n",
>  		       priv->variant);
> @@ -83,6 +89,11 @@ void rk8xx_off_for_plugin(struct udevice *dev)
>  	}
>  }
>  
> +static struct reg_data rk806_init_reg[] = {
> +	/* RST_FUN */
> +	{ RK806_REG_SYS_CFG3, GENMASK(7, 6), BIT(7)},
> +};
> +
>  static struct reg_data rk817_init_reg[] = {
>  /* enable the under-voltage protection,
>   * the under-voltage protection will shutdown the LDO3 and reset the PMIC
> @@ -92,7 +103,10 @@ static struct reg_data rk817_init_reg[] = {
>  
>  static const struct pmic_child_info pmic_children_info[] = {
>  	{ .prefix = "DCDC_REG", .driver = "rk8xx_buck"},
> +	{ .prefix = "dcdc-reg", .driver = "rk8xx_buck"},
>  	{ .prefix = "LDO_REG", .driver = "rk8xx_ldo"},
> +	{ .prefix = "nldo-reg", .driver = "rk8xx_nldo"},
> +	{ .prefix = "pldo-reg", .driver = "rk8xx_pldo"},
>  	{ .prefix = "SWITCH_REG", .driver = "rk8xx_switch"},
>  	{ },
>  };
> @@ -102,11 +116,47 @@ static int rk8xx_reg_count(struct udevice *dev)
>  	return RK808_NUM_OF_REGS;
>  }
>  
> +struct rk806_cmd {
> +	char	len: 4; /* Payload size in bytes - 1 */
> +	char	reserved: 2;
> +	char	crc_en: 1;
> +	char	op: 1; /* READ=0; WRITE=1; */
> +	char	reg_l;
> +#define REG_L_MASK	GENMASK(7, 0)
> +	char	reg_h;
> +#define REG_H_MASK	GENMASK(15, 8)
> +};
> +
>  static int rk8xx_write(struct udevice *dev, uint reg, const uint8_t *buff,
>  			  int len)
>  {
>  	int ret;
>  
> +	if (device_get_uclass_id(dev->parent) == UCLASS_SPI) {
> +		struct spi_slave *spi = dev_get_parent_priv(dev);
> +		struct rk806_cmd cmd = {
> +			.op = 1,
> +			.len = len - 1,
> +			.reg_l = FIELD_GET(REG_L_MASK, reg),
> +			.reg_h = FIELD_GET(REG_H_MASK, reg),
> +		};
> +
> +		ret = dm_spi_claim_bus(dev);
> +		if (ret) {
> +			debug("Couldn't claim bus for device: %p!\n", dev);
> +			return ret;
> +		}
> +
> +		ret = spi_write_then_read(spi, (u8 *)&cmd, sizeof(cmd), buff, NULL, len);
> +		if (ret)
> +			debug("write error to device: %p register: %#x!\n",
> +			      dev, reg);
> +
> +		dm_spi_release_bus(dev);

I am getting following build error on PineTab2 [1] with this:

aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.o: in function `rk8xx_write':
drivers/power/pmic/rk8xx.c:144:(.text.rk8xx_write+0x60): undefined reference to `dm_spi_claim_bus'
aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.c:150:(.text.rk8xx_write+0x84): undefined reference to `spi_write_then_read'
aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.c:155:(.text.rk8xx_write+0x90): undefined reference to `dm_spi_release_bus'
aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.o: in function `rk8xx_read':
drivers/power/pmic/rk8xx.c:182:(.text.rk8xx_read+0x60): undefined reference to `dm_spi_claim_bus'
aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.c:188:(.text.rk8xx_read+0x84): undefined reference to `spi_write_then_read'
aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.c:193:(.text.rk8xx_read+0x90): undefined reference to `dm_spi_release_bus'
make[2]: *** [scripts/Makefile.spl:527: spl/u-boot-spl] Error 1
make[1]: *** [Makefile:2055: spl/u-boot-spl] Error 2

On PineTab2 this driver is enabled in SPL and this build error happens
because SPL_SPI and/or SPL_DM_SPI is not enabled.

The RK8XX driver is enabled in SPL to be able to quickly power down the
tablet if it was powered on because a power cable was connected.

CONFIG_ROCKCHIP_RK8XX_DISABLE_BOOT_ON_POWERON=y
CONFIG_SPL_I2C=y
CONFIG_SPL_PMIC_RK8XX=y

[1] https://patchwork.ozlabs.org/patch/1895031/

> +
> +		return ret;
> +	}
> +
>  	ret = dm_i2c_write(dev, reg, buff, len);
>  	if (ret) {
>  		debug("write error to device: %p register: %#x!\n", dev, reg);
> @@ -120,6 +170,31 @@ static int rk8xx_read(struct udevice *dev, uint reg, uint8_t *buff, int len)
>  {
>  	int ret;
>  
> +	if (device_get_uclass_id(dev->parent) == UCLASS_SPI) {
> +		struct spi_slave *spi = dev_get_parent_priv(dev);
> +		struct rk806_cmd cmd = {
> +			.op = 0,
> +			.len = len - 1,
> +			.reg_l = FIELD_GET(REG_L_MASK, reg),
> +			.reg_h = FIELD_GET(REG_H_MASK, reg),
> +		};
> +
> +		ret = dm_spi_claim_bus(dev);
> +		if (ret) {
> +			debug("Couldn't claim bus for device: %p!\n", dev);
> +			return ret;
> +		}
> +
> +		ret = spi_write_then_read(spi, (u8 *)&cmd, sizeof(cmd), NULL, buff, len);
> +		if (ret)
> +			debug("read error to device: %p register: %#x!\n",
> +			      dev, reg);
> +
> +		dm_spi_release_bus(dev);

Same build error here.

Regards,
Jonas

> +
> +		return ret;
> +	}
> +
>  	ret = dm_i2c_read(dev, reg, buff, len);
>  	if (ret) {
>  		debug("read error from device: %p register: %#x!\n", dev, reg);
> @@ -181,6 +256,9 @@ static int rk8xx_probe(struct udevice *dev)
>  	    device_is_compatible(dev, "rockchip,rk809")) {
>  		id_msb = RK817_ID_MSB;
>  		id_lsb = RK817_ID_LSB;
> +	} else if (device_is_compatible(dev, "rockchip,rk806")) {
> +		id_msb = RK806_ID_MSB;
> +		id_lsb = RK806_ID_LSB;
>  	} else {
>  		id_msb = ID_MSB;
>  		id_lsb = ID_LSB;
> @@ -221,6 +299,12 @@ static int rk8xx_probe(struct udevice *dev)
>  		value = (power_en2 & 0x0f) | ((power_en3 & 0x0f) << 4);
>  		pmic_reg_write(dev, RK817_POWER_EN_SAVE1, value);
>  		break;
> +	case RK806_ID:
> +		on_source = RK806_ON_SOURCE;
> +		off_source = RK806_OFF_SOURCE;
> +		init_data = rk806_init_reg;
> +		init_data_num = ARRAY_SIZE(rk806_init_reg);
> +		break;
>  	default:
>  		printf("Unknown PMIC: RK%x!!\n", priv->variant);
>  		return -EINVAL;
> @@ -263,6 +347,7 @@ static struct dm_pmic_ops rk8xx_ops = {
>  
>  static const struct udevice_id rk8xx_ids[] = {
>  	{ .compatible = "rockchip,rk805" },
> +	{ .compatible = "rockchip,rk806" },
>  	{ .compatible = "rockchip,rk808" },
>  	{ .compatible = "rockchip,rk809" },
>  	{ .compatible = "rockchip,rk816" },

[snip]
Quentin Schulz Feb. 29, 2024, 11:58 a.m. UTC | #2
Hi Jonas,

On 2/29/24 12:47, Jonas Karlman wrote:
> Hi Quentin,
> 
> On 2024-02-23 10:05, Quentin Schulz wrote:
>> From: Quentin Schulz <quentin.schulz@theobroma-systems.com>
>>
>> This adds support for RK806, only the SPI variant has been tested.
>>
>> The communication "protocol" over SPI is the following:
>>   - write three bytes:
>>     - 1 byte: [0:3] length of the payload, [6] Enable CRC, [7] Write
>>     - 1 byte: LSB register address
>>     - 1 byte: MSB register address
>>   - write/read length of payload
>>
>> The CRC is always disabled for now.
>>
>> The RK806 technically supports I2C as well, and this should be able to
>> support it without any change, but it wasn't tested.
>>
>> The DT node name prefix for the buck converters has changed in the
>> Device Tree and is now dcdc-reg. The logic for buck converters is
>> however manageable within the current logic inside the rk8xx regulator
>> driver. The same cannot be said for the NLDO and PLDO.
>>
>> Because pmic_bind_children() parses the DT nodes and extracts the LDO
>> index from the DT node name, NLDO and PLDO will have overlapping
>> indices. Therefore, we need a separate logic from the already-existing
>> ldo callbacks. Let's reuse as much as possible though.
>>
>> Cc: Quentin Schulz <foss+uboot@0leil.net>
>> Signed-off-by: Quentin Schulz <quentin.schulz@theobroma-systems.com>
>> ---
>>   drivers/power/pmic/rk8xx.c      |  85 +++++++
>>   drivers/power/regulator/rk8xx.c | 514 +++++++++++++++++++++++++++++++++++++++-
>>   include/power/rk8xx_pmic.h      |  19 ++
>>   3 files changed, 616 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/power/pmic/rk8xx.c b/drivers/power/pmic/rk8xx.c
>> index 4e3a17337ee..15cb82029cc 100644
>> --- a/drivers/power/pmic/rk8xx.c
>> +++ b/drivers/power/pmic/rk8xx.c
>> @@ -9,8 +9,10 @@
>>   #include <dm/lists.h>
>>   #include <errno.h>
>>   #include <log.h>
>> +#include <linux/bitfield.h>
>>   #include <power/rk8xx_pmic.h>
>>   #include <power/pmic.h>
>> +#include <spi.h>
>>   #include <sysreset.h>
>>   
>>   static int rk8xx_sysreset_request(struct udevice *dev, enum sysreset_t type)
>> @@ -32,6 +34,10 @@ static int rk8xx_sysreset_request(struct udevice *dev, enum sysreset_t type)
>>   		pmic_clrsetbits(dev->parent, RK817_REG_SYS_CFG3, 0,
>>   				BIT(0));
>>   		break;
>> +	case RK806_ID:
>> +		pmic_clrsetbits(dev->parent, RK806_REG_SYS_CFG3, 0,
>> +				BIT(0));
>> +		break;
>>   	default:
>>   		printf("Unknown PMIC RK%x: Cannot shutdown\n",
>>   		       priv->variant);
>> @@ -83,6 +89,11 @@ void rk8xx_off_for_plugin(struct udevice *dev)
>>   	}
>>   }
>>   
>> +static struct reg_data rk806_init_reg[] = {
>> +	/* RST_FUN */
>> +	{ RK806_REG_SYS_CFG3, GENMASK(7, 6), BIT(7)},
>> +};
>> +
>>   static struct reg_data rk817_init_reg[] = {
>>   /* enable the under-voltage protection,
>>    * the under-voltage protection will shutdown the LDO3 and reset the PMIC
>> @@ -92,7 +103,10 @@ static struct reg_data rk817_init_reg[] = {
>>   
>>   static const struct pmic_child_info pmic_children_info[] = {
>>   	{ .prefix = "DCDC_REG", .driver = "rk8xx_buck"},
>> +	{ .prefix = "dcdc-reg", .driver = "rk8xx_buck"},
>>   	{ .prefix = "LDO_REG", .driver = "rk8xx_ldo"},
>> +	{ .prefix = "nldo-reg", .driver = "rk8xx_nldo"},
>> +	{ .prefix = "pldo-reg", .driver = "rk8xx_pldo"},
>>   	{ .prefix = "SWITCH_REG", .driver = "rk8xx_switch"},
>>   	{ },
>>   };
>> @@ -102,11 +116,47 @@ static int rk8xx_reg_count(struct udevice *dev)
>>   	return RK808_NUM_OF_REGS;
>>   }
>>   
>> +struct rk806_cmd {
>> +	char	len: 4; /* Payload size in bytes - 1 */
>> +	char	reserved: 2;
>> +	char	crc_en: 1;
>> +	char	op: 1; /* READ=0; WRITE=1; */
>> +	char	reg_l;
>> +#define REG_L_MASK	GENMASK(7, 0)
>> +	char	reg_h;
>> +#define REG_H_MASK	GENMASK(15, 8)
>> +};
>> +
>>   static int rk8xx_write(struct udevice *dev, uint reg, const uint8_t *buff,
>>   			  int len)
>>   {
>>   	int ret;
>>   
>> +	if (device_get_uclass_id(dev->parent) == UCLASS_SPI) {
>> +		struct spi_slave *spi = dev_get_parent_priv(dev);
>> +		struct rk806_cmd cmd = {
>> +			.op = 1,
>> +			.len = len - 1,
>> +			.reg_l = FIELD_GET(REG_L_MASK, reg),
>> +			.reg_h = FIELD_GET(REG_H_MASK, reg),
>> +		};
>> +
>> +		ret = dm_spi_claim_bus(dev);
>> +		if (ret) {
>> +			debug("Couldn't claim bus for device: %p!\n", dev);
>> +			return ret;
>> +		}
>> +
>> +		ret = spi_write_then_read(spi, (u8 *)&cmd, sizeof(cmd), buff, NULL, len);
>> +		if (ret)
>> +			debug("write error to device: %p register: %#x!\n",
>> +			      dev, reg);
>> +
>> +		dm_spi_release_bus(dev);
> 
> I am getting following build error on PineTab2 [1] with this:
> 
> aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.o: in function `rk8xx_write':
> drivers/power/pmic/rk8xx.c:144:(.text.rk8xx_write+0x60): undefined reference to `dm_spi_claim_bus'
> aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.c:150:(.text.rk8xx_write+0x84): undefined reference to `spi_write_then_read'
> aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.c:155:(.text.rk8xx_write+0x90): undefined reference to `dm_spi_release_bus'
> aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.o: in function `rk8xx_read':
> drivers/power/pmic/rk8xx.c:182:(.text.rk8xx_read+0x60): undefined reference to `dm_spi_claim_bus'
> aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.c:188:(.text.rk8xx_read+0x84): undefined reference to `spi_write_then_read'
> aarch64-linux-gnu-ld.bfd: drivers/power/pmic/rk8xx.c:193:(.text.rk8xx_read+0x90): undefined reference to `dm_spi_release_bus'
> make[2]: *** [scripts/Makefile.spl:527: spl/u-boot-spl] Error 1
> make[1]: *** [Makefile:2055: spl/u-boot-spl] Error 2
> 
> On PineTab2 this driver is enabled in SPL and this build error happens
> because SPL_SPI and/or SPL_DM_SPI is not enabled.
> 
> The RK8XX driver is enabled in SPL to be able to quickly power down the
> tablet if it was powered on because a power cable was connected.
> 
> CONFIG_ROCKCHIP_RK8XX_DISABLE_BOOT_ON_POWERON=y
> CONFIG_SPL_I2C=y
> CONFIG_SPL_PMIC_RK8XX=y
> 
> [1] https://patchwork.ozlabs.org/patch/1895031/
> 

I was investigating this yesterday but this turned out to be a perfect 
match to Hal fixing a light bulb in Malcom in the Middle 
(https://www.youtube.com/watch?v=AbSehcT19u0).

I will settle on adding some ifdef that should have been unnecessary and 
fix the actual issue in a later patch series because it'll be involving 
likely a lot of people and I wouldn't really want to block this patch 
series for fixing missing dependencies in Kconfig symbols :)

In short, SPL_DM_SPI is not enough for the ifdef because while the 
symbol is enabled, nothing in SPI will be compiled for the SPL unless 
SPL_SPI is selected as well... And we're missing a bunch of those 
(SPL_DM_I2C as well, SPL_PMIC on SPL_POWER, etc...). The issue is the 
moment I add a hard dependency on SPL_SPI for SPL_DM_SPI a bunch of 
defconfigs/devices will have SPL_DM_SPI dropped because of an unmatched 
dependency. While this is nothing more than cleaning stuff up, I have a 
feeling people who enabled those symbols expected them to work. So we 
have two schools here: it's been broken for years (for SPL_I2C 
dependency missing, July 2021) so if nobody figured it was missing until 
then, it's probably not required for the current usecases where SPL_I2C 
is missing or, they wanted it enabled and somehow it went through 
maintainers' nets but we should fix it now to add it back for the 
devices that are requiring it.

Cheers,
Quentin
diff mbox series

Patch

diff --git a/drivers/power/pmic/rk8xx.c b/drivers/power/pmic/rk8xx.c
index 4e3a17337ee..15cb82029cc 100644
--- a/drivers/power/pmic/rk8xx.c
+++ b/drivers/power/pmic/rk8xx.c
@@ -9,8 +9,10 @@ 
 #include <dm/lists.h>
 #include <errno.h>
 #include <log.h>
+#include <linux/bitfield.h>
 #include <power/rk8xx_pmic.h>
 #include <power/pmic.h>
+#include <spi.h>
 #include <sysreset.h>
 
 static int rk8xx_sysreset_request(struct udevice *dev, enum sysreset_t type)
@@ -32,6 +34,10 @@  static int rk8xx_sysreset_request(struct udevice *dev, enum sysreset_t type)
 		pmic_clrsetbits(dev->parent, RK817_REG_SYS_CFG3, 0,
 				BIT(0));
 		break;
+	case RK806_ID:
+		pmic_clrsetbits(dev->parent, RK806_REG_SYS_CFG3, 0,
+				BIT(0));
+		break;
 	default:
 		printf("Unknown PMIC RK%x: Cannot shutdown\n",
 		       priv->variant);
@@ -83,6 +89,11 @@  void rk8xx_off_for_plugin(struct udevice *dev)
 	}
 }
 
+static struct reg_data rk806_init_reg[] = {
+	/* RST_FUN */
+	{ RK806_REG_SYS_CFG3, GENMASK(7, 6), BIT(7)},
+};
+
 static struct reg_data rk817_init_reg[] = {
 /* enable the under-voltage protection,
  * the under-voltage protection will shutdown the LDO3 and reset the PMIC
@@ -92,7 +103,10 @@  static struct reg_data rk817_init_reg[] = {
 
 static const struct pmic_child_info pmic_children_info[] = {
 	{ .prefix = "DCDC_REG", .driver = "rk8xx_buck"},
+	{ .prefix = "dcdc-reg", .driver = "rk8xx_buck"},
 	{ .prefix = "LDO_REG", .driver = "rk8xx_ldo"},
+	{ .prefix = "nldo-reg", .driver = "rk8xx_nldo"},
+	{ .prefix = "pldo-reg", .driver = "rk8xx_pldo"},
 	{ .prefix = "SWITCH_REG", .driver = "rk8xx_switch"},
 	{ },
 };
@@ -102,11 +116,47 @@  static int rk8xx_reg_count(struct udevice *dev)
 	return RK808_NUM_OF_REGS;
 }
 
+struct rk806_cmd {
+	char	len: 4; /* Payload size in bytes - 1 */
+	char	reserved: 2;
+	char	crc_en: 1;
+	char	op: 1; /* READ=0; WRITE=1; */
+	char	reg_l;
+#define REG_L_MASK	GENMASK(7, 0)
+	char	reg_h;
+#define REG_H_MASK	GENMASK(15, 8)
+};
+
 static int rk8xx_write(struct udevice *dev, uint reg, const uint8_t *buff,
 			  int len)
 {
 	int ret;
 
+	if (device_get_uclass_id(dev->parent) == UCLASS_SPI) {
+		struct spi_slave *spi = dev_get_parent_priv(dev);
+		struct rk806_cmd cmd = {
+			.op = 1,
+			.len = len - 1,
+			.reg_l = FIELD_GET(REG_L_MASK, reg),
+			.reg_h = FIELD_GET(REG_H_MASK, reg),
+		};
+
+		ret = dm_spi_claim_bus(dev);
+		if (ret) {
+			debug("Couldn't claim bus for device: %p!\n", dev);
+			return ret;
+		}
+
+		ret = spi_write_then_read(spi, (u8 *)&cmd, sizeof(cmd), buff, NULL, len);
+		if (ret)
+			debug("write error to device: %p register: %#x!\n",
+			      dev, reg);
+
+		dm_spi_release_bus(dev);
+
+		return ret;
+	}
+
 	ret = dm_i2c_write(dev, reg, buff, len);
 	if (ret) {
 		debug("write error to device: %p register: %#x!\n", dev, reg);
@@ -120,6 +170,31 @@  static int rk8xx_read(struct udevice *dev, uint reg, uint8_t *buff, int len)
 {
 	int ret;
 
+	if (device_get_uclass_id(dev->parent) == UCLASS_SPI) {
+		struct spi_slave *spi = dev_get_parent_priv(dev);
+		struct rk806_cmd cmd = {
+			.op = 0,
+			.len = len - 1,
+			.reg_l = FIELD_GET(REG_L_MASK, reg),
+			.reg_h = FIELD_GET(REG_H_MASK, reg),
+		};
+
+		ret = dm_spi_claim_bus(dev);
+		if (ret) {
+			debug("Couldn't claim bus for device: %p!\n", dev);
+			return ret;
+		}
+
+		ret = spi_write_then_read(spi, (u8 *)&cmd, sizeof(cmd), NULL, buff, len);
+		if (ret)
+			debug("read error to device: %p register: %#x!\n",
+			      dev, reg);
+
+		dm_spi_release_bus(dev);
+
+		return ret;
+	}
+
 	ret = dm_i2c_read(dev, reg, buff, len);
 	if (ret) {
 		debug("read error from device: %p register: %#x!\n", dev, reg);
@@ -181,6 +256,9 @@  static int rk8xx_probe(struct udevice *dev)
 	    device_is_compatible(dev, "rockchip,rk809")) {
 		id_msb = RK817_ID_MSB;
 		id_lsb = RK817_ID_LSB;
+	} else if (device_is_compatible(dev, "rockchip,rk806")) {
+		id_msb = RK806_ID_MSB;
+		id_lsb = RK806_ID_LSB;
 	} else {
 		id_msb = ID_MSB;
 		id_lsb = ID_LSB;
@@ -221,6 +299,12 @@  static int rk8xx_probe(struct udevice *dev)
 		value = (power_en2 & 0x0f) | ((power_en3 & 0x0f) << 4);
 		pmic_reg_write(dev, RK817_POWER_EN_SAVE1, value);
 		break;
+	case RK806_ID:
+		on_source = RK806_ON_SOURCE;
+		off_source = RK806_OFF_SOURCE;
+		init_data = rk806_init_reg;
+		init_data_num = ARRAY_SIZE(rk806_init_reg);
+		break;
 	default:
 		printf("Unknown PMIC: RK%x!!\n", priv->variant);
 		return -EINVAL;
@@ -263,6 +347,7 @@  static struct dm_pmic_ops rk8xx_ops = {
 
 static const struct udevice_id rk8xx_ids[] = {
 	{ .compatible = "rockchip,rk805" },
+	{ .compatible = "rockchip,rk806" },
 	{ .compatible = "rockchip,rk808" },
 	{ .compatible = "rockchip,rk809" },
 	{ .compatible = "rockchip,rk816" },
diff --git a/drivers/power/regulator/rk8xx.c b/drivers/power/regulator/rk8xx.c
index 212bb752a76..1bd4605d43a 100644
--- a/drivers/power/regulator/rk8xx.c
+++ b/drivers/power/regulator/rk8xx.c
@@ -25,6 +25,19 @@ 
 #define NA			0xff
 
 /* Field Definitions */
+#define RK806_BUCK_CONFIG(n)	(0x10 + (n) - 1)
+#define RK806_BUCK_ON_VSEL(n)	(0x1a + (n) - 1)
+#define RK806_BUCK_SLP_VSEL(n)	(0x24 + (n) - 1)
+#define RK806_BUCK_VSEL_MASK	0xff
+
+#define RK806_NLDO_ON_VSEL(n)	(0x43 + (n) - 1)
+#define RK806_NLDO_SLP_VSEL(n)	(0x48 + (n) - 1)
+#define RK806_NLDO_VSEL_MASK	0xff
+
+#define RK806_PLDO_ON_VSEL(n)	(0x4e + (n) - 1)
+#define RK806_PLDO_SLP_VSEL(n)	(0x54 + (n) - 1)
+#define RK806_PLDO_VSEL_MASK	0xff
+
 #define RK808_BUCK_VSEL_MASK	0x3f
 #define RK808_BUCK4_VSEL_MASK	0xf
 #define RK808_LDO_VSEL_MASK	0x1f
@@ -91,6 +104,49 @@  struct rk8xx_reg_info {
 	u8 max_sel;
 };
 
+static const struct rk8xx_reg_info rk806_buck[] = {
+	/* buck 1 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(1), RK806_BUCK_SLP_VSEL(1), RK806_BUCK_CONFIG(1), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(1), RK806_BUCK_SLP_VSEL(1), RK806_BUCK_CONFIG(1), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(1), RK806_BUCK_SLP_VSEL(1), RK806_BUCK_CONFIG(1), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+	/* buck 2 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(2), RK806_BUCK_SLP_VSEL(2), RK806_BUCK_CONFIG(2), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(2), RK806_BUCK_SLP_VSEL(2), RK806_BUCK_CONFIG(2), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(2), RK806_BUCK_SLP_VSEL(2), RK806_BUCK_CONFIG(2), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+	/* buck 3 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(3), RK806_BUCK_SLP_VSEL(3), RK806_BUCK_CONFIG(3), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(3), RK806_BUCK_SLP_VSEL(3), RK806_BUCK_CONFIG(3), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(3), RK806_BUCK_SLP_VSEL(3), RK806_BUCK_CONFIG(3), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+	/* buck 4 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(4), RK806_BUCK_SLP_VSEL(4), RK806_BUCK_CONFIG(4), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(4), RK806_BUCK_SLP_VSEL(4), RK806_BUCK_CONFIG(4), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(4), RK806_BUCK_SLP_VSEL(4), RK806_BUCK_CONFIG(4), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+	/* buck 5 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(5), RK806_BUCK_SLP_VSEL(5), RK806_BUCK_CONFIG(5), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(5), RK806_BUCK_SLP_VSEL(5), RK806_BUCK_CONFIG(5), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(5), RK806_BUCK_SLP_VSEL(5), RK806_BUCK_CONFIG(5), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+	/* buck 6 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(6), RK806_BUCK_SLP_VSEL(6), RK806_BUCK_CONFIG(6), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(6), RK806_BUCK_SLP_VSEL(6), RK806_BUCK_CONFIG(6), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(6), RK806_BUCK_SLP_VSEL(6), RK806_BUCK_CONFIG(6), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+	/* buck 7 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(7), RK806_BUCK_SLP_VSEL(7), RK806_BUCK_CONFIG(7), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(7), RK806_BUCK_SLP_VSEL(7), RK806_BUCK_CONFIG(7), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(7), RK806_BUCK_SLP_VSEL(7), RK806_BUCK_CONFIG(7), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+	/* buck 8 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(8), RK806_BUCK_SLP_VSEL(8), RK806_BUCK_CONFIG(8), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(8), RK806_BUCK_SLP_VSEL(8), RK806_BUCK_CONFIG(8), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(8), RK806_BUCK_SLP_VSEL(8), RK806_BUCK_CONFIG(8), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+	/* buck 9 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(9), RK806_BUCK_SLP_VSEL(9), RK806_BUCK_CONFIG(9), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(9), RK806_BUCK_SLP_VSEL(9), RK806_BUCK_CONFIG(9), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(9), RK806_BUCK_SLP_VSEL(9), RK806_BUCK_CONFIG(9), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+	/* buck 10 */
+	{  500000,   6250, RK806_BUCK_ON_VSEL(10), RK806_BUCK_SLP_VSEL(10), RK806_BUCK_CONFIG(10), RK806_BUCK_VSEL_MASK, 0x00, 0x9f },
+	{ 1500000,  25000, RK806_BUCK_ON_VSEL(10), RK806_BUCK_SLP_VSEL(10), RK806_BUCK_CONFIG(10), RK806_BUCK_VSEL_MASK, 0xa0, 0xeb },
+	{ 3400000,	0, RK806_BUCK_ON_VSEL(10), RK806_BUCK_SLP_VSEL(10), RK806_BUCK_CONFIG(10), RK806_BUCK_VSEL_MASK, 0xec, 0xff },
+};
+
 static const struct rk8xx_reg_info rk808_buck[] = {
 	{  712500,  12500, REG_BUCK1_ON_VSEL, REG_BUCK1_SLP_VSEL, REG_BUCK1_CONFIG,  RK808_BUCK_VSEL_MASK, 0x00, 0x3f },
 	{  712500,  12500, REG_BUCK2_ON_VSEL, REG_BUCK2_SLP_VSEL, REG_BUCK2_CONFIG,  RK808_BUCK_VSEL_MASK, 0x00, 0x3f },
@@ -148,6 +204,45 @@  static const struct rk8xx_reg_info rk818_buck[] = {
 };
 
 #ifdef ENABLE_DRIVER
+static const struct rk8xx_reg_info rk806_nldo[] = {
+	/* nldo 1 */
+	{  500000, 12500, RK806_NLDO_ON_VSEL(1), RK806_NLDO_SLP_VSEL(1), NA, RK806_NLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_NLDO_ON_VSEL(1), RK806_NLDO_SLP_VSEL(1), NA, RK806_NLDO_VSEL_MASK, 0xe8, 0xff},
+	/* nldo 2 */
+	{  500000, 12500, RK806_NLDO_ON_VSEL(2), RK806_NLDO_SLP_VSEL(2), NA, RK806_NLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_NLDO_ON_VSEL(2), RK806_NLDO_SLP_VSEL(2), NA, RK806_NLDO_VSEL_MASK, 0xe8, 0xff},
+	/* nldo 3 */
+	{  500000, 12500, RK806_NLDO_ON_VSEL(3), RK806_NLDO_SLP_VSEL(3), NA, RK806_NLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_NLDO_ON_VSEL(3), RK806_NLDO_SLP_VSEL(3), NA, RK806_NLDO_VSEL_MASK, 0xe8, 0xff},
+	/* nldo 4 */
+	{  500000, 12500, RK806_NLDO_ON_VSEL(4), RK806_NLDO_SLP_VSEL(4), NA, RK806_NLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_NLDO_ON_VSEL(4), RK806_NLDO_SLP_VSEL(4), NA, RK806_NLDO_VSEL_MASK, 0xe8, 0xff},
+	/* nldo 5 */
+	{  500000, 12500, RK806_NLDO_ON_VSEL(5), RK806_NLDO_SLP_VSEL(5), NA, RK806_NLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_NLDO_ON_VSEL(5), RK806_NLDO_SLP_VSEL(5), NA, RK806_NLDO_VSEL_MASK, 0xe8, 0xff},
+};
+
+static const struct rk8xx_reg_info rk806_pldo[] = {
+	/* pldo 1 */
+	{  500000, 12500, RK806_PLDO_ON_VSEL(1), RK806_PLDO_SLP_VSEL(1), NA, RK806_PLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_PLDO_ON_VSEL(1), RK806_PLDO_SLP_VSEL(1), NA, RK806_PLDO_VSEL_MASK, 0xe8, 0xff},
+	/* pldo 2 */
+	{  500000, 12500, RK806_PLDO_ON_VSEL(2), RK806_PLDO_SLP_VSEL(2), NA, RK806_PLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_PLDO_ON_VSEL(2), RK806_PLDO_SLP_VSEL(2), NA, RK806_PLDO_VSEL_MASK, 0xe8, 0xff},
+	/* pldo 3 */
+	{  500000, 12500, RK806_PLDO_ON_VSEL(3), RK806_PLDO_SLP_VSEL(3), NA, RK806_PLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_PLDO_ON_VSEL(3), RK806_PLDO_SLP_VSEL(3), NA, RK806_PLDO_VSEL_MASK, 0xe8, 0xff},
+	/* pldo 4 */
+	{  500000, 12500, RK806_PLDO_ON_VSEL(4), RK806_PLDO_SLP_VSEL(4), NA, RK806_PLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_PLDO_ON_VSEL(4), RK806_PLDO_SLP_VSEL(4), NA, RK806_PLDO_VSEL_MASK, 0xe8, 0xff},
+	/* pldo 5 */
+	{  500000, 12500, RK806_PLDO_ON_VSEL(5), RK806_PLDO_SLP_VSEL(5), NA, RK806_PLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_PLDO_ON_VSEL(5), RK806_PLDO_SLP_VSEL(5), NA, RK806_PLDO_VSEL_MASK, 0xe8, 0xff},
+	/* pldo 6 */
+	{  500000, 12500, RK806_PLDO_ON_VSEL(6), RK806_PLDO_SLP_VSEL(6), NA, RK806_PLDO_VSEL_MASK, 0x00, 0xe7},
+	{ 3400000,     0, RK806_PLDO_ON_VSEL(6), RK806_PLDO_SLP_VSEL(6), NA, RK806_PLDO_VSEL_MASK, 0xe8, 0xff},
+};
+
 static const struct rk8xx_reg_info rk808_ldo[] = {
 	{ 1800000, 100000, REG_LDO1_ON_VSEL, REG_LDO1_SLP_VSEL, NA, RK808_LDO_VSEL_MASK, },
 	{ 1800000, 100000, REG_LDO2_ON_VSEL, REG_LDO2_SLP_VSEL, NA, RK808_LDO_VSEL_MASK, },
@@ -230,7 +325,12 @@  static const struct rk8xx_reg_info *get_buck_reg(struct udevice *pmic,
 		default:
 			return &rk816_buck[num + 4];
 		}
-
+	case RK806_ID:
+		if (uvolt < 1500000)
+			return &rk806_buck[num * 3 + 0];
+		else if (uvolt < 3400000)
+			return &rk806_buck[num * 3 + 1];
+		return &rk806_buck[num * 3 + 2];
 	case RK809_ID:
 	case RK817_ID:
 		switch (num) {
@@ -314,7 +414,11 @@  static int _buck_set_enable(struct udevice *pmic, int buck, bool enable)
 			value = ((0 << buck) | (1 << (buck + 4)));
 		ret = pmic_reg_write(pmic, en_reg, value);
 		break;
-
+	case RK806_ID:
+		value = RK806_POWER_EN_CLRSETBITS(buck % 4, enable);
+		en_reg = RK806_POWER_EN((buck + 1) / 4);
+		ret = pmic_reg_write(pmic, en_reg, value);
+		break;
 	case RK808_ID:
 	case RK818_ID:
 		mask = 1 << buck;
@@ -389,6 +493,10 @@  static int _buck_get_enable(struct udevice *pmic, int buck)
 			ret = pmic_reg_read(pmic, RK816_REG_DCDC_EN1);
 		}
 		break;
+	case RK806_ID:
+		mask = BIT(buck % 4);
+		ret = pmic_reg_read(pmic, RK806_POWER_EN((buck + 1) / 4));
+		break;
 	case RK808_ID:
 	case RK818_ID:
 		mask = 1 << buck;
@@ -428,6 +536,20 @@  static int _buck_set_suspend_enable(struct udevice *pmic, int buck, bool enable)
 		ret = pmic_clrsetbits(pmic, RK816_REG_DCDC_SLP_EN, mask,
 				      enable ? mask : 0);
 		break;
+	case RK806_ID:
+		{
+			u8 reg;
+
+			if (buck + 1 >= 9) {
+				reg = RK806_POWER_SLP_EN1;
+				mask = BIT(buck + 1 - 3);
+			} else {
+				reg = RK806_POWER_SLP_EN0;
+				mask = BIT(buck + 1);
+			}
+			ret = pmic_clrsetbits(pmic, reg, mask, enable ? mask : 0);
+		}
+		break;
 	case RK808_ID:
 	case RK818_ID:
 		mask = 1 << buck;
@@ -465,6 +587,21 @@  static int _buck_get_suspend_enable(struct udevice *pmic, int buck)
 			return val;
 		ret = val & mask ? 1 : 0;
 		break;
+	case RK806_ID:
+		{
+			u8 reg;
+
+			if (buck + 1 >= 9) {
+				reg = RK806_POWER_SLP_EN1;
+				mask = BIT(buck + 1 - 3);
+			} else {
+				reg = RK806_POWER_SLP_EN0;
+				mask = BIT(buck + 1);
+			}
+			val = pmic_reg_read(pmic, reg);
+		}
+		ret = (val & mask) ? 1 : 0;
+		break;
 	case RK808_ID:
 	case RK818_ID:
 		mask = 1 << buck;
@@ -514,6 +651,34 @@  static const struct rk8xx_reg_info *get_ldo_reg(struct udevice *pmic,
 	}
 }
 
+static const struct rk8xx_reg_info *get_nldo_reg(struct udevice *pmic,
+						 int num, int uvolt)
+{
+	const struct rk8xx_priv *priv = dev_get_priv(pmic);
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		if (uvolt < 3400000)
+			return &rk806_nldo[num * 2 + 0];
+		return &rk806_nldo[num * 2 + 1];
+	}
+}
+
+static const struct rk8xx_reg_info *get_pldo_reg(struct udevice *pmic,
+						 int num, int uvolt)
+{
+	const struct rk8xx_priv *priv = dev_get_priv(pmic);
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		if (uvolt < 3400000)
+			return &rk806_pldo[num * 2 + 0];
+		return &rk806_pldo[num * 2 + 1];
+	}
+}
+
 static int _ldo_get_enable(struct udevice *pmic, int ldo)
 {
 	struct rk8xx_priv *priv = dev_get_priv(pmic);
@@ -561,6 +726,63 @@  static int _ldo_get_enable(struct udevice *pmic, int ldo)
 	return ret & mask ? true : false;
 }
 
+static int _nldo_get_enable(struct udevice *pmic, int nldo)
+{
+	struct rk8xx_priv *priv = dev_get_priv(pmic);
+	uint mask = 0;
+	int ret = 0;
+	u8 en_reg = 0;
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		if (nldo + 1 >= 5) {
+			mask = BIT(2);
+			en_reg = RK806_POWER_EN(5);
+		} else {
+			mask = BIT(nldo);
+			en_reg = RK806_POWER_EN(3);
+		}
+		ret = pmic_reg_read(pmic, en_reg);
+		break;
+	}
+
+	if (ret < 0)
+		return ret;
+
+	return (ret & mask) ? 1 : 0;
+}
+
+static int _pldo_get_enable(struct udevice *pmic, int pldo)
+{
+	struct rk8xx_priv *priv = dev_get_priv(pmic);
+	uint mask = 0;
+	int ret = 0;
+	u8 en_reg = 0;
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		if (pldo + 1 <= 3) {
+			mask = BIT(pldo + 1);
+			en_reg = RK806_POWER_EN(4);
+		} else if (pldo + 1 == 6) {
+			mask = BIT(0);
+			en_reg = RK806_POWER_EN(4);
+		} else {
+			mask = BIT((pldo + 1) % 4);
+			en_reg = RK806_POWER_EN(5);
+		}
+		ret = pmic_reg_read(pmic, en_reg);
+		break;
+	}
+
+	if (ret < 0)
+		return ret;
+
+	return (ret & mask) ? 1 : 0;
+}
+
 static int _ldo_set_enable(struct udevice *pmic, int ldo, bool enable)
 {
 	struct rk8xx_priv *priv = dev_get_priv(pmic);
@@ -616,6 +838,62 @@  static int _ldo_set_enable(struct udevice *pmic, int ldo, bool enable)
 	return ret;
 }
 
+static int _nldo_set_enable(struct udevice *pmic, int nldo, bool enable)
+{
+	struct rk8xx_priv *priv = dev_get_priv(pmic);
+	uint value, en_reg;
+	int ret = 0;
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		if (nldo + 1 >= 5) {
+			value = RK806_POWER_EN_CLRSETBITS(2, enable);
+			en_reg = RK806_POWER_EN(5);
+		} else {
+			value = RK806_POWER_EN_CLRSETBITS(nldo, enable);
+			en_reg = RK806_POWER_EN(3);
+		}
+		ret = pmic_reg_write(pmic, en_reg, value);
+		break;
+	}
+
+	if (enable)
+		udelay(500);
+
+	return ret;
+}
+
+static int _pldo_set_enable(struct udevice *pmic, int pldo, bool enable)
+{
+	struct rk8xx_priv *priv = dev_get_priv(pmic);
+	uint value, en_reg;
+	int ret = 0;
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		/* PLDO */
+		if (pldo + 1 <= 3) {
+			value = RK806_POWER_EN_CLRSETBITS(pldo + 1, enable);
+			en_reg = RK806_POWER_EN(4);
+		} else if (pldo + 1 == 6) {
+			value = RK806_POWER_EN_CLRSETBITS(0, enable);
+			en_reg = RK806_POWER_EN(4);
+		} else {
+			value = RK806_POWER_EN_CLRSETBITS((pldo + 1) % 4, enable);
+			en_reg = RK806_POWER_EN(5);
+		}
+		ret = pmic_reg_write(pmic, en_reg, value);
+		break;
+	}
+
+	if (enable)
+		udelay(500);
+
+	return ret;
+}
+
 static int _ldo_set_suspend_enable(struct udevice *pmic, int ldo, bool enable)
 {
 	struct rk8xx_priv *priv = dev_get_priv(pmic);
@@ -652,6 +930,43 @@  static int _ldo_set_suspend_enable(struct udevice *pmic, int ldo, bool enable)
 	return ret;
 }
 
+static int _nldo_set_suspend_enable(struct udevice *pmic, int nldo, bool enable)
+{
+	struct rk8xx_priv *priv = dev_get_priv(pmic);
+	uint mask;
+	int ret = 0;
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		mask = BIT(nldo);
+		ret = pmic_clrsetbits(pmic, RK806_POWER_SLP_EN1, mask, enable ? mask : 0);
+		break;
+	}
+
+	return ret;
+}
+
+static int _pldo_set_suspend_enable(struct udevice *pmic, int pldo, bool enable)
+{
+	struct rk8xx_priv *priv = dev_get_priv(pmic);
+	uint mask;
+	int ret = 0;
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		if (pldo + 1 >= 6)
+			mask = BIT(0);
+		else
+			mask = BIT(pldo + 1);
+		ret = pmic_clrsetbits(pmic, RK806_POWER_SLP_EN2, mask, enable ? mask : 0);
+		break;
+	}
+
+	return ret;
+}
+
 static int _ldo_get_suspend_enable(struct udevice *pmic, int ldo)
 {
 	struct rk8xx_priv *priv = dev_get_priv(pmic);
@@ -696,6 +1011,45 @@  static int _ldo_get_suspend_enable(struct udevice *pmic, int ldo)
 	return ret;
 }
 
+static int _nldo_get_suspend_enable(struct udevice *pmic, int nldo)
+{
+	struct rk8xx_priv *priv = dev_get_priv(pmic);
+	int val, ret = 0;
+	uint mask;
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		mask = BIT(nldo);
+		val = pmic_reg_read(pmic, RK806_POWER_SLP_EN1);
+		ret = (val & mask) ? 1 : 0;
+		break;
+	}
+
+	return ret;
+}
+
+static int _pldo_get_suspend_enable(struct udevice *pmic, int pldo)
+{
+	struct rk8xx_priv *priv = dev_get_priv(pmic);
+	int val, ret = 0;
+	uint mask;
+
+	switch (priv->variant) {
+	case RK806_ID:
+	default:
+		if (pldo + 1 >= 6)
+			mask = BIT(0);
+		else
+			mask = BIT(pldo + 1);
+		val = pmic_reg_read(pmic, RK806_POWER_SLP_EN2);
+		ret = (val & mask) ? 1 : 0;
+		break;
+	}
+
+	return ret;
+}
+
 static int buck_get_value(struct udevice *dev)
 {
 	int buck = dev->driver_data - 1;
@@ -803,6 +1157,22 @@  static int ldo_get_value(struct udevice *dev)
 	return _ldo_get_value(dev, info);
 }
 
+static int nldo_get_value(struct udevice *dev)
+{
+	int nldo = dev->driver_data - 1;
+	const struct rk8xx_reg_info *info = get_nldo_reg(dev->parent, nldo, 0);
+
+	return _ldo_get_value(dev, info);
+}
+
+static int pldo_get_value(struct udevice *dev)
+{
+	int pldo = dev->driver_data - 1;
+	const struct rk8xx_reg_info *info = get_pldo_reg(dev->parent, pldo, 0);
+
+	return _ldo_get_value(dev, info);
+}
+
 static int _ldo_set_value(struct udevice *dev, const struct rk8xx_reg_info *info, int uvolt)
 {
 	int mask = info->vsel_mask;
@@ -830,6 +1200,22 @@  static int ldo_set_value(struct udevice *dev, int uvolt)
 	return _ldo_set_value(dev, info, uvolt);
 }
 
+static int nldo_set_value(struct udevice *dev, int uvolt)
+{
+	int nldo = dev->driver_data - 1;
+	const struct rk8xx_reg_info *info = get_nldo_reg(dev->parent, nldo, uvolt);
+
+	return _ldo_set_value(dev, info, uvolt);
+}
+
+static int pldo_set_value(struct udevice *dev, int uvolt)
+{
+	int pldo = dev->driver_data - 1;
+	const struct rk8xx_reg_info *info = get_pldo_reg(dev->parent, pldo, uvolt);
+
+	return _ldo_set_value(dev, info, uvolt);
+}
+
 static int _ldo_set_suspend_value(struct udevice *dev, const struct rk8xx_reg_info *info, int uvolt)
 {
 	int mask = info->vsel_mask;
@@ -857,6 +1243,22 @@  static int ldo_set_suspend_value(struct udevice *dev, int uvolt)
 	return _ldo_set_suspend_value(dev->parent, info, uvolt);
 }
 
+static int nldo_set_suspend_value(struct udevice *dev, int uvolt)
+{
+	int nldo = dev->driver_data - 1;
+	const struct rk8xx_reg_info *info = get_nldo_reg(dev->parent, nldo, uvolt);
+
+	return _ldo_set_suspend_value(dev->parent, info, uvolt);
+}
+
+static int pldo_set_suspend_value(struct udevice *dev, int uvolt)
+{
+	int pldo = dev->driver_data - 1;
+	const struct rk8xx_reg_info *info = get_pldo_reg(dev->parent, pldo, uvolt);
+
+	return _ldo_set_suspend_value(dev->parent, info, uvolt);
+}
+
 static int _ldo_get_suspend_value(struct udevice *dev, const struct rk8xx_reg_info *info)
 {
 	int mask = info->vsel_mask;
@@ -882,6 +1284,22 @@  static int ldo_get_suspend_value(struct udevice *dev)
 	return _ldo_get_suspend_value(dev->parent, info);
 }
 
+static int nldo_get_suspend_value(struct udevice *dev)
+{
+	int nldo = dev->driver_data - 1;
+	const struct rk8xx_reg_info *info = get_nldo_reg(dev->parent, nldo, 0);
+
+	return _ldo_get_suspend_value(dev->parent, info);
+}
+
+static int pldo_get_suspend_value(struct udevice *dev)
+{
+	int pldo = dev->driver_data - 1;
+	const struct rk8xx_reg_info *info = get_pldo_reg(dev->parent, pldo, 0);
+
+	return _ldo_get_suspend_value(dev->parent, info);
+}
+
 static int ldo_set_enable(struct udevice *dev, bool enable)
 {
 	int ldo = dev->driver_data - 1;
@@ -889,6 +1307,20 @@  static int ldo_set_enable(struct udevice *dev, bool enable)
 	return _ldo_set_enable(dev->parent, ldo, enable);
 }
 
+static int nldo_set_enable(struct udevice *dev, bool enable)
+{
+	int nldo = dev->driver_data - 1;
+
+	return _nldo_set_enable(dev->parent, nldo, enable);
+}
+
+static int pldo_set_enable(struct udevice *dev, bool enable)
+{
+	int pldo = dev->driver_data - 1;
+
+	return _pldo_set_enable(dev->parent, pldo, enable);
+}
+
 static int ldo_set_suspend_enable(struct udevice *dev, bool enable)
 {
 	int ldo = dev->driver_data - 1;
@@ -896,6 +1328,20 @@  static int ldo_set_suspend_enable(struct udevice *dev, bool enable)
 	return _ldo_set_suspend_enable(dev->parent, ldo, enable);
 }
 
+static int nldo_set_suspend_enable(struct udevice *dev, bool enable)
+{
+	int nldo = dev->driver_data - 1;
+
+	return _nldo_set_suspend_enable(dev->parent, nldo, enable);
+}
+
+static int pldo_set_suspend_enable(struct udevice *dev, bool enable)
+{
+	int pldo = dev->driver_data - 1;
+
+	return _pldo_set_suspend_enable(dev->parent, pldo, enable);
+}
+
 static int ldo_get_suspend_enable(struct udevice *dev)
 {
 	int ldo = dev->driver_data - 1;
@@ -903,6 +1349,20 @@  static int ldo_get_suspend_enable(struct udevice *dev)
 	return _ldo_get_suspend_enable(dev->parent, ldo);
 }
 
+static int nldo_get_suspend_enable(struct udevice *dev)
+{
+	int nldo = dev->driver_data - 1;
+
+	return _nldo_get_suspend_enable(dev->parent, nldo);
+}
+
+static int pldo_get_suspend_enable(struct udevice *dev)
+{
+	int pldo = dev->driver_data - 1;
+
+	return _pldo_get_suspend_enable(dev->parent, pldo);
+}
+
 static int ldo_get_enable(struct udevice *dev)
 {
 	int ldo = dev->driver_data - 1;
@@ -910,6 +1370,20 @@  static int ldo_get_enable(struct udevice *dev)
 	return _ldo_get_enable(dev->parent, ldo);
 }
 
+static int nldo_get_enable(struct udevice *dev)
+{
+	int nldo = dev->driver_data - 1;
+
+	return _nldo_get_enable(dev->parent, nldo);
+}
+
+static int pldo_get_enable(struct udevice *dev)
+{
+	int pldo = dev->driver_data - 1;
+
+	return _pldo_get_enable(dev->parent, pldo);
+}
+
 static int switch_set_enable(struct udevice *dev, bool enable)
 {
 	struct rk8xx_priv *priv = dev_get_priv(dev->parent);
@@ -1133,6 +1607,28 @@  static const struct dm_regulator_ops rk8xx_ldo_ops = {
 	.get_suspend_enable = ldo_get_suspend_enable,
 };
 
+static const struct dm_regulator_ops rk8xx_nldo_ops = {
+	.get_value  = nldo_get_value,
+	.set_value  = nldo_set_value,
+	.set_suspend_value = nldo_set_suspend_value,
+	.get_suspend_value = nldo_get_suspend_value,
+	.get_enable = nldo_get_enable,
+	.set_enable = nldo_set_enable,
+	.set_suspend_enable = nldo_set_suspend_enable,
+	.get_suspend_enable = nldo_get_suspend_enable,
+};
+
+static const struct dm_regulator_ops rk8xx_pldo_ops = {
+	.get_value  = pldo_get_value,
+	.set_value  = pldo_set_value,
+	.set_suspend_value = pldo_set_suspend_value,
+	.get_suspend_value = pldo_get_suspend_value,
+	.get_enable = pldo_get_enable,
+	.set_enable = pldo_set_enable,
+	.set_suspend_enable = pldo_set_suspend_enable,
+	.get_suspend_enable = pldo_get_suspend_enable,
+};
+
 static const struct dm_regulator_ops rk8xx_switch_ops = {
 	.get_value  = switch_get_value,
 	.set_value  = switch_set_value,
@@ -1158,6 +1654,20 @@  U_BOOT_DRIVER(rk8xx_ldo) = {
 	.probe = rk8xx_ldo_probe,
 };
 
+U_BOOT_DRIVER(rk8xx_nldo) = {
+	.name = "rk8xx_nldo",
+	.id = UCLASS_REGULATOR,
+	.ops = &rk8xx_nldo_ops,
+	.probe = rk8xx_ldo_probe,
+};
+
+U_BOOT_DRIVER(rk8xx_pldo) = {
+	.name = "rk8xx_pldo",
+	.id = UCLASS_REGULATOR,
+	.ops = &rk8xx_pldo_ops,
+	.probe = rk8xx_ldo_probe,
+};
+
 U_BOOT_DRIVER(rk8xx_switch) = {
 	.name = "rk8xx_switch",
 	.id = UCLASS_REGULATOR,
diff --git a/include/power/rk8xx_pmic.h b/include/power/rk8xx_pmic.h
index 0db82419d4f..31221aa46b6 100644
--- a/include/power/rk8xx_pmic.h
+++ b/include/power/rk8xx_pmic.h
@@ -182,8 +182,19 @@  enum {
 	RK816_REG_LDO_EN2,
 };
 
+enum {
+	RK806_POWER_SLP_EN0 = 0x06,
+	RK806_POWER_SLP_EN1,
+	RK806_POWER_SLP_EN2,
+	RK806_REG_SYS_CFG3 = 0x72,
+	RK806_WDT_REG,
+	RK806_ON_SOURCE,
+	RK806_OFF_SOURCE
+};
+
 enum {
 	RK805_ID = 0x8050,
+	RK806_ID = 0x8060,
 	RK808_ID = 0x0000,
 	RK809_ID = 0x8090,
 	RK816_ID = 0x8160,
@@ -201,6 +212,14 @@  enum {
 #define RK817_POWER_EN_SAVE0	0x99
 #define RK817_POWER_EN_SAVE1	0xa4
 
+#define RK806_POWER_EN(x)			(0x00 + (x))
+/* POWER_ENx register lower 4 bits are write-protected unless the associated top bit is set */
+#define RK806_POWER_EN_CLRSETBITS(bit, val)	(((val) << (bit)) | (1 << ((bit) + 4)))
+
+#define RK806_POWER_SLP_EN(x)			(0x06 + (x))
+
+#define RK806_ID_MSB	0x5a
+#define RK806_ID_LSB	0x5b
 #define RK817_ID_MSB	0xed
 #define RK817_ID_LSB	0xee
 #define RK8XX_ID_MSK	0xfff0