Em Tue, 11 Aug 2020 17:41:26 +0200
Mauro Carvalho Chehab <mchehab+hua...@kernel.org> escreveu:

> The Hikey 970 board uses a different PMIC than the one found on Hikey 960.
> 
> This PMIC uses a SPMI board.
> 
> This patch series backport the OOT drivers from the Linaro's official
> tree for this board:
> 
>       https://github.com/96boards-hikey/linux/tree/hikey970-v4.9
>       
> Porting them to upstream, cleaning up coding style issues, solving
> driver probing order and adding DT documentation.
> 
> I opted to not fold all patches into a single one, in order to:
> 
> - Preserve the authorship of the original authors;
> - Keep a history of changes.
> 
> As this could be harder for people to review, I'll be replying to patch 00/32
> with all patches folded. This should help reviewers to see the current
> code after the entire series is applied.

As promised, it follows the diff from this entire patch series.

Feel free to review either on the top of this e-mail or on the
individual patches.

Thanks!
Mauro

 Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml      | 
175 ++++++++++++++++++++++++++++++++++++++
 Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml |  
54 ++++++++++++
 MAINTAINERS                                                                |   
8 ++
 arch/arm64/boot/dts/hisilicon/hi3670-hikey970.dts                          |  
16 +---
 arch/arm64/boot/dts/hisilicon/hikey970-pmic.dtsi                           | 
200 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/Kconfig                                                        |  
17 +++-
 drivers/mfd/Makefile                                                       |   
1 +
 drivers/mfd/hi6421-spmi-pmic.c                                             | 
399 
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/regulator/Kconfig                                                  |   
8 ++
 drivers/regulator/Makefile                                                 |   
1 +
 drivers/regulator/hi6421v600-regulator.c                                   | 
493 
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/spmi/Kconfig                                                       |   
9 ++
 drivers/spmi/Makefile                                                      |   
2 +
 drivers/spmi/hisi-spmi-controller.c                                        | 
384 
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/spmi/spmi.c                                                        |  
10 +--
 include/linux/mfd/hi6421-spmi-pmic.h                                       |  
67 +++++++++++++++
 16 files changed, 1826 insertions(+), 18 deletions(-)


diff --git 
a/Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml 
b/Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml
new file mode 100644
index 000000000000..33dcbaeb474e
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml
@@ -0,0 +1,175 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: 
http://devicetree.org/schemas/regulator/hisilicon,hi6421v600-regulator.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: HiSilicon 6421v600 SPMI PMIC
+
+maintainers:
+  - Mauro Carvalho Chehab <mchehab+hua...@kernel.org>
+
+description: |
+  HiSilicon 6421v600 uses a MIPI System Power Management (SPMI) bus in order
+  to provide interrupts and power supply.
+
+  The GPIO and interrupt settings are represented as part of the top-level PMIC
+  node.
+
+  The SPMI controller part is provided by
+  Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml.
+
+properties:
+  $nodename:
+    pattern: "pmic@[0-9a-f]"
+
+  compatible:
+    const: hisilicon,hi6421-spmi-pmic
+
+  reg:
+    maxItems: 1
+
+  spmi-channel:
+    description: number of the SPMI channel where the PMIC is connected
+
+  '#interrupt-cells':
+    const: 2
+
+  interrupt-controller:
+    description:
+      Identify that the PMIC is capable of behaving as an interrupt controller.
+
+  gpios:
+    maxItems: 1
+
+  irq-num:
+    description: Interrupt request number
+
+  'irq-array':
+    description: Interrupt request array
+
+  'irq-mask-addr':
+    description: Address for the interrupt request mask
+
+  'irq-addr':
+    description: Address for the interrupt request
+
+  regulators:
+    type: object
+
+    properties:
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
+    patternProperties:
+      '^ldo@[0-9]+$':
+        type: object
+
+        $ref: "/schemas/regulator/regulator.yaml#"
+
+        properties:
+          reg:
+            description: Enable register.
+
+          vsel-reg:
+            description: Voltage selector register.
+
+          enable-mask:
+            description: Bitmask used to enable the regulator.
+
+#          voltage-table:
+#            description: Table with the selector items for the voltage 
regulator.
+#            minItems: 2
+#            maxItems: 16
+
+          off-on-delay-us:
+            description: Time required for changing state to enabled in 
microseconds.
+
+          startup-delay-us:
+            description: Startup time in microseconds.
+
+          idle-mode-mask:
+            description: Bitmask used to put the regulator on idle mode.
+
+          eco-microamp:
+            description: Maximum current while on idle mode.
+
+        required:
+          - reg
+          - vsel-reg
+          - enable-mask
+          - voltage-table
+          - off-on-delay-us
+          - startup-delay-us
+
+required:
+  - compatible
+  - reg
+  - spmi-channel
+  - regulators
+
+examples:
+  - |
+    pmic: pmic@0 {
+      compatible = "hisilicon,hi6421-spmi-pmic";
+      slave_id = <0>;
+      reg = <0 0>;
+
+      #interrupt-cells = <2>;
+      interrupt-controller;
+      gpios = <&gpio28 0 0>;
+      irq-num = <16>;
+      irq-array = <2>;
+      irq-mask-addr = <0x202 2>;
+      irq-addr = <0x212 2>;
+
+      regulators {
+        ldo3: ldo3@16 {
+          reg = <0x16>;
+          vsel-reg = <0x51>;
+
+          regulator-name = "ldo3";
+          regulator-min-microvolt = <1500000>;
+          regulator-max-microvolt = <2000000>;
+          regulator-boot-on;
+
+          enable-mask = <0x01>;
+
+          voltage-table = <1500000>, <1550000>,
+              <1600000>, <1650000>,
+              <1700000>, <1725000>,
+              <1750000>, <1775000>,
+              <1800000>, <1825000>,
+              <1850000>, <1875000>,
+              <1900000>, <1925000>,
+              <1950000>, <2000000>;
+          off-on-delay-us = <20000>;
+          startup-delay-us = <120>;
+        };
+
+        ldo4: ldo4@17 { /* 40 PIN */
+          reg = <0x17>;
+          vsel-reg = <0x52>;
+
+          regulator-name = "ldo4";
+          regulator-min-microvolt = <1725000>;
+          regulator-max-microvolt = <1900000>;
+          regulator-boot-on;
+
+          enable-mask = <0x01>;
+          idle-mode-mask = <0x10>;
+          eco-microamp = <10000>;
+
+          hi6421-vsel = <0x52 0x07>;
+          voltage-table = <1725000>, <1750000>,
+              <1775000>, <1800000>,
+              <1825000>, <1850000>,
+              <1875000>, <1900000>;
+          off-on-delay-us = <20000>;
+          startup-delay-us = <120>;
+        };
+      };
+    };
diff --git 
a/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml 
b/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml
new file mode 100644
index 000000000000..d087f9067e4c
--- /dev/null
+++ b/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/spmi/hisilicon,hisi-spmi-controller.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: HiSilicon SPMI controller
+
+maintainers:
+  - Mauro Carvalho Chehab <mchehab+hua...@kernel.org>
+
+description: |
+  The HiSilicon SPMI controller is found on some Kirin-based designs.
+  It is a MIPI System Power Management (SPMI) controller.
+
+  The PMIC part is provided by
+  Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml.
+
+properties:
+  $nodename:
+    pattern: "spmi@[0-9a-f]"
+
+  compatible:
+    const: hisilicon,spmi-controller
+
+  reg:
+    maxItems: 1
+
+  "#address-cells":
+    const: 2
+
+  "#size-cells":
+    const: 0
+
+  spmi-channel:
+    const: number of the SPMI channel where the PMIC is connected
+
+patternProperties:
+  "^pmic@[0-9a-f]$":
+    $ref: "/schemas/mfd/hisilicon,hi6421-spmi-pmic.yaml#"
+
+examples:
+  - |
+    spmi: spmi@fff24000 {
+      compatible = "hisilicon,spmi-controller";
+      #address-cells = <2>;
+      #size-cells = <0>;
+      status = "ok";
+      reg = <0x0 0xfff24000 0x0 0x1000>;
+      spmi-channel = <2>;
+
+      /* pmic properties */
+
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 956ecd5ba426..6410df78e301 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7736,6 +7736,14 @@ F:       include/linux/hippidevice.h
 F:     include/uapi/linux/if_hippi.h
 F:     net/802/hippi.c
 
+HISILICON 6421v600 SPMI PMIC DRIVER
+M:     Mauro Carvalho Chehab <mchehab+hua...@kernel.org>
+L:     linux-kernel@vger.kernel.org
+S:     Maintained
+F:     drivers/mfd/hi6421-spmi-pmic.c
+F:     drivers/regulator/hi6421v600-regulator.c
+F:     drivers/spmi/spmi.c
+
 HISILICON DMA DRIVER
 M:     Zhou Wang <wangzh...@hisilicon.com>
 L:     dmaeng...@vger.kernel.org
diff --git a/arch/arm64/boot/dts/hisilicon/hi3670-hikey970.dts 
b/arch/arm64/boot/dts/hisilicon/hi3670-hikey970.dts
index 01234a175dcd..c8a72c0873bf 100644
--- a/arch/arm64/boot/dts/hisilicon/hi3670-hikey970.dts
+++ b/arch/arm64/boot/dts/hisilicon/hi3670-hikey970.dts
@@ -12,6 +12,7 @@
 
 #include "hi3670.dtsi"
 #include "hikey970-pinctrl.dtsi"
+#include "hikey970-pmic.dtsi"
 
 / {
        model = "HiKey970";
@@ -39,7 +40,7 @@ memory@0 {
                reg = <0x0 0x0 0x0 0x0>;
        };
 
-       sd_1v8: regulator-1v8 {
+       fixed_1v8: regulator-1v8 {
                compatible = "regulator-fixed";
                regulator-name = "fixed-1.8V";
                regulator-min-microvolt = <1800000>;
@@ -47,15 +48,6 @@ sd_1v8: regulator-1v8 {
                regulator-always-on;
        };
 
-       sd_3v3: regulator-3v3 {
-               compatible = "regulator-fixed";
-               regulator-name = "fixed-3.3V";
-               regulator-min-microvolt = <3300000>;
-               regulator-max-microvolt = <3300000>;
-               regulator-boot-on;
-               regulator-always-on;
-       };
-
        wlan_en: wlan-en-1-8v {
                compatible = "regulator-fixed";
                regulator-name = "wlan-en-regulator";
@@ -402,8 +394,8 @@ &dwmmc1 {
        pinctrl-0 = <&sd_pmx_func
                     &sd_clk_cfg_func
                     &sd_cfg_func>;
-       vmmc-supply = <&sd_3v3>;
-       vqmmc-supply = <&sd_1v8>;
+       vmmc-supply = <&ldo16>;
+       vqmmc-supply = <&ldo9>;
        status = "okay";
 };
 
diff --git a/arch/arm64/boot/dts/hisilicon/hikey970-pmic.dtsi 
b/arch/arm64/boot/dts/hisilicon/hikey970-pmic.dtsi
new file mode 100644
index 000000000000..2a6c366d9be6
--- /dev/null
+++ b/arch/arm64/boot/dts/hisilicon/hikey970-pmic.dtsi
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dts file for Hi6421v600 SPMI PMIC used at the HiKey970 Development Board
+ *
+ * Copyright (C) 2020, Huawei Tech. Co., Ltd.
+ */
+
+/ {
+       spmi: spmi@fff24000 {
+               compatible = "hisilicon,spmi-controller";
+               #address-cells = <2>;
+               #size-cells = <0>;
+               status = "ok";
+               reg = <0x0 0xfff24000 0x0 0x1000>;
+               spmi-channel = <2>;
+
+               pmic: pmic@0 {
+                       compatible = "hisilicon,hi6421-spmi-pmic";
+                       slave_id = <0>;
+                       reg = <0 0>;
+
+                       #interrupt-cells = <2>;
+                       interrupt-controller;
+                       gpios = <&gpio28 0 0>;
+                       irq-num = <16>;
+                       irq-array = <2>;
+                       irq-mask-addr = <0x202 2>;
+                       irq-addr = <0x212 2>;
+
+                       regulators {
+                               #address-cells = <1>;
+                               #size-cells = <0>;
+
+                               ldo3: ldo3@16 {
+                                       reg = <0x16>;
+                                       vsel-reg = <0x51>;
+
+                                       regulator-name = "ldo3";
+                                       regulator-min-microvolt = <1500000>;
+                                       regulator-max-microvolt = <2000000>;
+                                       regulator-boot-on;
+
+                                       enable-mask = <0x01>;
+
+                                       voltage-table = <1500000>, <1550000>,
+                                                       <1600000>, <1650000>,
+                                                       <1700000>, <1725000>,
+                                                       <1750000>, <1775000>,
+                                                       <1800000>, <1825000>,
+                                                       <1850000>, <1875000>,
+                                                       <1900000>, <1925000>,
+                                                       <1950000>, <2000000>;
+                                       off-on-delay-us = <20000>;
+                                       startup-delay-us = <120>;
+                               };
+
+                               ldo4: ldo4@17 { /* 40 PIN */
+                                       reg = <0x17>;
+                                       vsel-reg = <0x52>;
+
+                                       regulator-name = "ldo4";
+                                       regulator-min-microvolt = <1725000>;
+                                       regulator-max-microvolt = <1900000>;
+                                       regulator-boot-on;
+
+                                       enable-mask = <0x01>;
+                                       idle-mode-mask = <0x10>;
+                                       eco-microamp = <10000>;
+
+                                       hi6421-vsel = <0x52 0x07>;
+                                       voltage-table = <1725000>, <1750000>,
+                                                       <1775000>, <1800000>,
+                                                       <1825000>, <1850000>,
+                                                       <1875000>, <1900000>;
+                                       off-on-delay-us = <20000>;
+                                       startup-delay-us = <120>;
+                               };
+
+                               ldo9: ldo9@1C { /* SDCARD I/O */
+                                       reg = <0x1C>;
+                                       vsel-reg = <0x57>;
+
+                                       regulator-name = "ldo9";
+                                       regulator-min-microvolt = <1750000>;
+                                       regulator-max-microvolt = <3300000>;
+                                       regulator-boot-on;
+
+                                       enable-mask = <0x01>;
+                                       idle-mode-mask = <0x10>;
+                                       eco-microamp = <10000>;
+
+                                       voltage-table = <1750000>, <1800000>,
+                                                       <1825000>, <2800000>,
+                                                       <2850000>, <2950000>,
+                                                       <3000000>, <3300000>;
+                                       off-on-delay-us = <20000>;
+                                       startup-delay-us = <360>;
+                               };
+
+                               ldo15: ldo15@21 { /* UFS */
+                                       reg = <0x21>;
+                                       vsel-reg = <0x5c>;
+
+                                       regulator-name = "ldo15";
+                                       regulator-min-microvolt = <1800000>;
+                                       regulator-max-microvolt = <3000000>;
+                                       regulator-always-on;
+
+                                       enable-mask = <0x01>;
+                                       idle-mode-mask = <0x10>;
+                                       eco-microamp = <10000>;
+
+                                       voltage-table = <1800000>, <1850000>,
+                                                       <2400000>, <2600000>,
+                                                       <2700000>, <2850000>,
+                                                       <2950000>, <3000000>;
+                                       off-on-delay-us = <20000>;
+                                       startup-delay-us = <120>;
+                               };
+
+                               ldo16: ldo16@22 { /* SD */
+                                       reg = <0x22>;
+                                       vsel-reg = <0x5d>;
+
+                                       regulator-name = "ldo16";
+                                       regulator-min-microvolt = <1800000>;
+                                       regulator-max-microvolt = <3000000>;
+                                       regulator-boot-on;
+
+                                       enable-mask = <0x01>;
+                                       idle-mode-mask = <0x10>;
+                                       eco-microamp = <10000>;
+
+                                       voltage-table = <1800000>, <1850000>,
+                                                       <2400000>, <2600000>,
+                                                       <2700000>, <2850000>,
+                                                       <2950000>, <3000000>;
+                                       off-on-delay-us = <20000>;
+                                       startup-delay-us = <360>;
+                               };
+
+                               ldo17: ldo17@23 {
+                                       reg = <0x23>;
+                                       vsel-reg = <0x5e>;
+
+                                       regulator-name = "ldo17";
+                                       regulator-min-microvolt = <2500000>;
+                                       regulator-max-microvolt = <3300000>;
+
+                                       enable-mask = <0x01>;
+                                       idle-mode-mask = <0x10>;
+                                       eco-microamp = <10000>;
+
+                                       voltage-table = <2500000>, <2600000>,
+                                                       <2700000>, <2800000>,
+                                                       <3000000>, <3100000>,
+                                                       <3200000>, <3300000>;
+                                       off-on-delay-us = <20000>;
+                                       startup-delay-us = <120>;
+                               };
+
+                               ldo33: ldo33@32 { /* PEX8606 */
+                                       reg = <0x32>;
+                                       vsel-reg = <0x6d>;
+                                       regulator-name = "ldo33";
+                                       regulator-min-microvolt = <2500000>;
+                                       regulator-max-microvolt = <3300000>;
+                                       regulator-boot-on;
+
+                                       enable-mask = <0x01>;
+
+                                       voltage-table = <2500000>, <2600000>,
+                                                       <2700000>, <2800000>,
+                                                       <3000000>, <3100000>,
+                                                       <3200000>, <3300000>;
+                                       off-on-delay-us = <20000>;
+                                       startup-delay-us = <120>;
+                               };
+
+                               ldo34: ldo34@33 { /* GPS AUX IN VDD */
+                                       reg = <0x33>;
+                                       vsel-reg = <0x6e>;
+
+                                       regulator-name = "ldo34";
+                                       regulator-min-microvolt = <2600000>;
+                                       regulator-max-microvolt = <3300000>;
+
+                                       enable-mask = <0x01>;
+
+                                       voltage-table = <2600000>, <2700000>,
+                                                       <2800000>, <2900000>,
+                                                       <3000000>, <3100000>,
+                                                       <3200000>, <3300000>;
+                                       off-on-delay-us = <20000>;
+                                       startup-delay-us = <120>;
+                               };
+                       };
+               };
+       };
+};
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index a37d7d171382..04c249649532 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -493,10 +493,25 @@ config MFD_HI6421_PMIC
          Add support for HiSilicon Hi6421 PMIC. Hi6421 includes multi-
          functions, such as regulators, RTC, codec, Coulomb counter, etc.
          This driver includes core APIs _only_. You have to select
-         individul components like voltage regulators under corresponding
+         individual components like voltage regulators under corresponding
          menus in order to enable them.
          We communicate with the Hi6421 via memory-mapped I/O.
 
+config MFD_HI6421_SPMI
+       tristate "HiSilicon Hi6421v600 SPMI PMU/Codec IC"
+       depends on OF
+       select MFD_CORE
+       select REGMAP_MMIO
+       help
+         Add support for HiSilicon Hi6421v600 SPMI PMIC. Hi6421 includes
+         multi-functions, such as regulators, RTC, codec, Coulomb counter,
+         etc.
+
+         This driver includes core APIs _only_. You have to select
+         individual components like voltage regulators under corresponding
+         menus in order to enable them.
+         We communicate with the Hi6421v600 via a SPMI bus.
+
 config MFD_HI655X_PMIC
        tristate "HiSilicon Hi655X series PMU/Codec IC"
        depends on ARCH_HISI || COMPILE_TEST
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 9367a92f795a..2ac0727dafc9 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -233,6 +233,7 @@ obj-$(CONFIG_MFD_IPAQ_MICRO)        += ipaq-micro.o
 obj-$(CONFIG_MFD_IQS62X)       += iqs62x.o
 obj-$(CONFIG_MFD_MENF21BMC)    += menf21bmc.o
 obj-$(CONFIG_MFD_HI6421_PMIC)  += hi6421-pmic-core.o
+obj-$(CONFIG_MFD_HI6421_SPMI)  += hi6421-spmi-pmic.o
 obj-$(CONFIG_MFD_HI655X_PMIC)   += hi655x-pmic.o
 obj-$(CONFIG_MFD_DLN2)         += dln2.o
 obj-$(CONFIG_MFD_RT5033)       += rt5033.o
diff --git a/drivers/mfd/hi6421-spmi-pmic.c b/drivers/mfd/hi6421-spmi-pmic.c
new file mode 100644
index 000000000000..d8b84d64041e
--- /dev/null
+++ b/drivers/mfd/hi6421-spmi-pmic.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device driver for regulators in HISI PMIC IC
+ *
+ * Copyright (c) 2013 Linaro Ltd.
+ * Copyright (c) 2011 Hisilicon.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/mfd/hi6421-spmi-pmic.h>
+#include <linux/irq.h>
+#include <linux/spmi.h>
+#ifndef NO_IRQ
+#define NO_IRQ       0
+#endif
+
+/* 8-bit register offset in PMIC */
+#define HISI_MASK_STATE                        0xff
+
+#define HISI_IRQ_KEY_NUM               0
+#define HISI_IRQ_KEY_VALUE             0xc0
+#define HISI_IRQ_KEY_DOWN              7
+#define HISI_IRQ_KEY_UP                        6
+
+/*#define HISI_NR_IRQ                  25*/
+#define HISI_MASK_FIELD                0xFF
+#define HISI_BITS                      8
+
+/*define the first group interrupt register number*/
+#define HISI_PMIC_FIRST_GROUP_INT_NUM        2
+
+static const struct mfd_cell hi6421v600_devs[] = {
+       { .name = "hi6421v600-regulator", },
+};
+
+/*
+ * The PMIC register is only 8-bit.
+ * Hisilicon SoC use hardware to map PMIC register into SoC mapping.
+ * At here, we are accessing SoC register with 32-bit.
+ */
+u32 hi6421_spmi_pmic_read(struct hi6421_spmi_pmic *pmic, int reg)
+{
+       u32 ret;
+       u8 read_value = 0;
+       struct spmi_device *pdev;
+
+       pdev = to_spmi_device(pmic->dev);
+       if (!pdev) {
+               pr_err("%s: pdev get failed!\n", __func__);
+               return 0;
+       }
+
+       ret = spmi_ext_register_readl(pdev, reg,
+                                     (unsigned char *)&read_value, 1);
+       if (ret) {
+               pr_err("%s: spmi_ext_register_readl failed!\n", __func__);
+               return 0;
+       }
+       return (u32)read_value;
+}
+EXPORT_SYMBOL(hi6421_spmi_pmic_read);
+
+void hi6421_spmi_pmic_write(struct hi6421_spmi_pmic *pmic, int reg, u32 val)
+{
+       u32 ret;
+       struct spmi_device *pdev;
+
+       pdev = to_spmi_device(pmic->dev);
+       if (!pdev) {
+               pr_err("%s: pdev get failed!\n", __func__);
+               return;
+       }
+
+       ret = spmi_ext_register_writel(pdev, reg, (unsigned char *)&val, 1);
+       if (ret) {
+               pr_err("%s: spmi_ext_register_writel failed!\n", __func__);
+               return;
+       }
+}
+EXPORT_SYMBOL(hi6421_spmi_pmic_write);
+
+void hi6421_spmi_pmic_rmw(struct hi6421_spmi_pmic *pmic, int reg,
+                         u32 mask, u32 bits)
+{
+       u32 data;
+       unsigned long flags;
+
+       spin_lock_irqsave(&pmic->lock, flags);
+       data = hi6421_spmi_pmic_read(pmic, reg) & ~mask;
+       data |= mask & bits;
+       hi6421_spmi_pmic_write(pmic, reg, data);
+       spin_unlock_irqrestore(&pmic->lock, flags);
+}
+EXPORT_SYMBOL(hi6421_spmi_pmic_rmw);
+
+static irqreturn_t hi6421_spmi_irq_handler(int irq, void *data)
+{
+       struct hi6421_spmi_pmic *pmic = (struct hi6421_spmi_pmic *)data;
+       unsigned long pending;
+       int i, offset;
+
+       for (i = 0; i < pmic->irqarray; i++) {
+               pending = hi6421_spmi_pmic_read(pmic, (i + 
pmic->irq_addr.start_addr));
+               pending &= HISI_MASK_FIELD;
+               if (pending != 0)
+                       pr_debug("pending[%d]=0x%lx\n\r", i, pending);
+
+               hi6421_spmi_pmic_write(pmic, (i + pmic->irq_addr.start_addr),
+                                      pending);
+
+               /* solve powerkey order */
+               if ((i == HISI_IRQ_KEY_NUM) && ((pending & HISI_IRQ_KEY_VALUE) 
== HISI_IRQ_KEY_VALUE)) {
+                       generic_handle_irq(pmic->irqs[HISI_IRQ_KEY_DOWN]);
+                       generic_handle_irq(pmic->irqs[HISI_IRQ_KEY_UP]);
+                       pending &= (~HISI_IRQ_KEY_VALUE);
+               }
+
+               if (pending) {
+                       for_each_set_bit(offset, &pending, HISI_BITS)
+                               generic_handle_irq(pmic->irqs[offset + i * 
HISI_BITS]);
+               }
+       }
+
+       return IRQ_HANDLED;
+}
+
+static void hi6421_spmi_irq_mask(struct irq_data *d)
+{
+       struct hi6421_spmi_pmic *pmic = irq_data_get_irq_chip_data(d);
+       u32 data, offset;
+       unsigned long flags;
+
+       offset = (irqd_to_hwirq(d) >> 3);
+       offset += pmic->irq_mask_addr.start_addr;
+
+       spin_lock_irqsave(&pmic->lock, flags);
+       data = hi6421_spmi_pmic_read(pmic, offset);
+       data |= (1 << (irqd_to_hwirq(d) & 0x07));
+       hi6421_spmi_pmic_write(pmic, offset, data);
+       spin_unlock_irqrestore(&pmic->lock, flags);
+}
+
+static void hi6421_spmi_irq_unmask(struct irq_data *d)
+{
+       struct hi6421_spmi_pmic *pmic = irq_data_get_irq_chip_data(d);
+       u32 data, offset;
+       unsigned long flags;
+
+       offset = (irqd_to_hwirq(d) >> 3);
+       offset += pmic->irq_mask_addr.start_addr;
+
+       spin_lock_irqsave(&pmic->lock, flags);
+       data = hi6421_spmi_pmic_read(pmic, offset);
+       data &= ~(1 << (irqd_to_hwirq(d) & 0x07));
+       hi6421_spmi_pmic_write(pmic, offset, data);
+       spin_unlock_irqrestore(&pmic->lock, flags);
+}
+
+static struct irq_chip hi6421_spmi_pmu_irqchip = {
+       .name           = "hisi-irq",
+       .irq_mask       = hi6421_spmi_irq_mask,
+       .irq_unmask     = hi6421_spmi_irq_unmask,
+       .irq_disable    = hi6421_spmi_irq_mask,
+       .irq_enable     = hi6421_spmi_irq_unmask,
+};
+
+static int hi6421_spmi_irq_map(struct irq_domain *d, unsigned int virq,
+                              irq_hw_number_t hw)
+{
+       struct hi6421_spmi_pmic *pmic = d->host_data;
+
+       irq_set_chip_and_handler_name(virq, &hi6421_spmi_pmu_irqchip,
+                                     handle_simple_irq, "hisi");
+       irq_set_chip_data(virq, pmic);
+       irq_set_irq_type(virq, IRQ_TYPE_NONE);
+
+       return 0;
+}
+
+static const struct irq_domain_ops hi6421_spmi_domain_ops = {
+       .map    = hi6421_spmi_irq_map,
+       .xlate  = irq_domain_xlate_twocell,
+};
+
+static int get_pmic_device_tree_data(struct device_node *np,
+                                    struct hi6421_spmi_pmic *pmic)
+{
+       int ret = 0;
+
+       /*get pmic irq num*/
+       ret = of_property_read_u32_array(np, "irq-num",
+                                        &pmic->irqnum, 1);
+       if (ret) {
+               pr_err("no irq-num property set\n");
+               ret = -ENODEV;
+               return ret;
+       }
+
+       /*get pmic irq array number*/
+       ret = of_property_read_u32_array(np, "irq-array",
+                                        &pmic->irqarray, 1);
+       if (ret) {
+               pr_err("no irq-array property set\n");
+               ret = -ENODEV;
+               return ret;
+       }
+
+       /*SOC_PMIC_IRQ_MASK_0_ADDR*/
+       ret = of_property_read_u32_array(np, "irq-mask-addr",
+                                        (int *)&pmic->irq_mask_addr, 2);
+       if (ret) {
+               pr_err("no irq-mask-addr property set\n");
+               ret = -ENODEV;
+               return ret;
+       }
+
+       /*SOC_PMIC_IRQ0_ADDR*/
+       ret = of_property_read_u32_array(np, "irq-addr",
+                                        (int *)&pmic->irq_addr, 2);
+       if (ret) {
+               pr_err("no irq-addr property set\n");
+               ret = -ENODEV;
+               return ret;
+       }
+
+       return ret;
+}
+
+static void hi6421_spmi_pmic_irq_prc(struct hi6421_spmi_pmic *pmic)
+{
+       int i;
+
+       for (i = 0 ; i < pmic->irq_mask_addr.array; i++)
+               hi6421_spmi_pmic_write(pmic, pmic->irq_mask_addr.start_addr + i,
+                                      HISI_MASK_STATE);
+
+       for (i = 0 ; i < pmic->irq_addr.array; i++) {
+               unsigned int pending = hi6421_spmi_pmic_read(pmic, 
pmic->irq_addr.start_addr + i);
+
+               pr_debug("PMU IRQ address value:irq[0x%x] = 0x%x\n",
+                        pmic->irq_addr.start_addr + i, pending);
+               hi6421_spmi_pmic_write(pmic, pmic->irq_addr.start_addr + i,
+                                      HISI_MASK_STATE);
+       }
+}
+
+static int hi6421_spmi_pmic_probe(struct spmi_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+       struct hi6421_spmi_pmic *pmic = NULL;
+       enum of_gpio_flags flags;
+       int ret = 0;
+       int i;
+       unsigned int virq;
+
+       pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
+       if (!pmic)
+               return -ENOMEM;
+
+       /*TODO: get pmic dts info*/
+       ret = get_pmic_device_tree_data(np, pmic);
+       if (ret) {
+               dev_err(&pdev->dev, "Error reading hisi pmic dts\n");
+               return ret;
+       }
+
+       /* TODO: get and enable clk request */
+       spin_lock_init(&pmic->lock);
+
+       pmic->dev = dev;
+
+       pmic->gpio = of_get_gpio_flags(np, 0, &flags);
+       if (pmic->gpio < 0)
+               return pmic->gpio;
+
+       if (!gpio_is_valid(pmic->gpio))
+               return -EINVAL;
+
+       ret = gpio_request_one(pmic->gpio, GPIOF_IN, "pmic");
+       if (ret < 0) {
+               dev_err(dev, "failed to request gpio%d\n", pmic->gpio);
+               return ret;
+       }
+
+       pmic->irq = gpio_to_irq(pmic->gpio);
+
+       /* mask && clear IRQ status */
+       hi6421_spmi_pmic_irq_prc(pmic);
+
+       pmic->irqs = devm_kzalloc(dev, pmic->irqnum * sizeof(int), GFP_KERNEL);
+       if (!pmic->irqs)
+               goto irq_malloc;
+
+       pmic->domain = irq_domain_add_simple(np, pmic->irqnum, 0,
+                                            &hi6421_spmi_domain_ops, pmic);
+       if (!pmic->domain) {
+               dev_err(dev, "failed irq domain add simple!\n");
+               ret = -ENODEV;
+               goto irq_domain;
+       }
+
+       for (i = 0; i < pmic->irqnum; i++) {
+               virq = irq_create_mapping(pmic->domain, i);
+               if (virq == NO_IRQ) {
+                       pr_debug("Failed mapping hwirq\n");
+                       ret = -ENOSPC;
+                       goto irq_create_mapping;
+               }
+               pmic->irqs[i] = virq;
+               pr_info("[%s]. pmic->irqs[%d] = %d\n", __func__, i, 
pmic->irqs[i]);
+       }
+
+       ret = request_threaded_irq(pmic->irq, hi6421_spmi_irq_handler, NULL,
+                                  IRQF_TRIGGER_LOW | IRQF_SHARED | 
IRQF_NO_SUSPEND,
+                                  "pmic", pmic);
+       if (ret < 0) {
+               dev_err(dev, "could not claim pmic %d\n", ret);
+               ret = -ENODEV;
+               goto request_theaded_irq;
+       }
+
+       dev_set_drvdata(&pdev->dev, pmic);
+
+       /*
+        * The logic below will rely that the pmic is already stored at
+        * drvdata.
+        */
+       dev_dbg(&pdev->dev, "SPMI-PMIC: adding children for %pOF\n",
+               pdev->dev.of_node);
+       ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE,
+                                  hi6421v600_devs, ARRAY_SIZE(hi6421v600_devs),
+                                  NULL, 0, NULL);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to add child devices: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+
+request_theaded_irq:
+irq_create_mapping:
+irq_domain:
+irq_malloc:
+       gpio_free(pmic->gpio);
+       return ret;
+}
+
+static void hi6421_spmi_pmic_remove(struct spmi_device *pdev)
+{
+       struct hi6421_spmi_pmic *pmic = dev_get_drvdata(&pdev->dev);
+
+       free_irq(pmic->irq, pmic);
+       gpio_free(pmic->gpio);
+       devm_kfree(&pdev->dev, pmic);
+}
+
+static const struct of_device_id pmic_spmi_id_table[] = {
+       { .compatible = "hisilicon,hi6421-spmi-pmic" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, pmic_spmi_id_table);
+
+static struct spmi_driver hi6421_spmi_pmic_driver = {
+       .driver = {
+               .name   = "hi6421-spmi-pmic",
+               .of_match_table = pmic_spmi_id_table,
+       },
+       .probe  = hi6421_spmi_pmic_probe,
+       .remove = hi6421_spmi_pmic_remove,
+};
+module_spmi_driver(hi6421_spmi_pmic_driver);
+
+MODULE_DESCRIPTION("HiSilicon Hi6421v600 SPMI PMIC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index edb1c4f8b496..de8a78487bb9 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -356,6 +356,14 @@ config REGULATOR_HI6421V530
          provides 5 general purpose LDOs, and all of them come with support
          to either ECO (idle) or sleep mode.
 
+config REGULATOR_HI6421V600
+       tristate "HiSilicon Hi6421v600 PMIC voltage regulator support"
+       depends on MFD_HI6421_PMIC && OF
+       help
+         This driver provides support for the voltage regulators on
+         HiSilicon Hi6421v600 PMU / Codec IC.
+         This is used on Kirin 3670 boards, like HiKey 970.
+
 config REGULATOR_HI655X
        tristate "Hisilicon HI655X PMIC regulators support"
        depends on ARCH_HISI || COMPILE_TEST
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 0796e4a47afa..f59d5e3b5fd4 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_REGULATOR_FAN53555) += fan53555.o
 obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o
 obj-$(CONFIG_REGULATOR_HI6421) += hi6421-regulator.o
 obj-$(CONFIG_REGULATOR_HI6421V530) += hi6421v530-regulator.o
+obj-$(CONFIG_REGULATOR_HI6421V600) += hi6421v600-regulator.o
 obj-$(CONFIG_REGULATOR_HI655X) += hi655x-regulator.o
 obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o
 obj-$(CONFIG_REGULATOR_ISL9305) += isl9305.o
diff --git a/drivers/regulator/hi6421v600-regulator.c 
b/drivers/regulator/hi6421v600-regulator.c
new file mode 100644
index 000000000000..c80dfac1e4c3
--- /dev/null
+++ b/drivers/regulator/hi6421v600-regulator.c
@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device driver for regulators in Hisi IC
+ *
+ * Copyright (c) 2013 Linaro Ltd.
+ * Copyright (c) 2011 Hisilicon.
+ *
+ * Guodong Xu <guodong...@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/mfd/hi6421-spmi-pmic.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/version.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/spmi.h>
+
+#define rdev_dbg(rdev, fmt, arg...)    \
+                pr_debug("%s: %s: " fmt, (rdev)->desc->name, __func__, ##arg)
+
+struct hi6421v600_regulator {
+       struct regulator_desc rdesc;
+       struct hi6421_spmi_pmic *pmic;
+       u32 eco_mode_mask, eco_uA;
+};
+
+static DEFINE_MUTEX(enable_mutex);
+
+/* helper function to ensure when it returns it is at least 'delay_us'
+ * microseconds after 'since'.
+ */
+
+static int hi6421_spmi_regulator_is_enabled(struct regulator_dev *rdev)
+{
+       u32 reg_val;
+       struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+       struct hi6421_spmi_pmic *pmic = sreg->pmic;
+
+       reg_val = hi6421_spmi_pmic_read(pmic, rdev->desc->enable_reg);
+
+       rdev_dbg(rdev,
+                "enable_reg=0x%x, val= 0x%x, enable_state=%d\n",
+                rdev->desc->enable_reg,
+                reg_val, (reg_val & rdev->desc->enable_mask));
+
+       return ((reg_val & rdev->desc->enable_mask) != 0);
+}
+
+static int hi6421_spmi_regulator_enable(struct regulator_dev *rdev)
+{
+       struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+       struct hi6421_spmi_pmic *pmic = sreg->pmic;
+
+       /* cannot enable more than one regulator at one time */
+       mutex_lock(&enable_mutex);
+       usleep_range(HISI_REGS_ENA_PROTECT_TIME,
+                    HISI_REGS_ENA_PROTECT_TIME + 1000);
+
+       /* set enable register */
+       rdev_dbg(rdev,
+                "off_on_delay=%d us, enable_reg=0x%x, enable_mask=0x%x\n",
+                rdev->desc->off_on_delay, rdev->desc->enable_reg,
+                rdev->desc->enable_mask);
+
+       hi6421_spmi_pmic_rmw(pmic, rdev->desc->enable_reg,
+                            rdev->desc->enable_mask,
+                            rdev->desc->enable_mask);
+
+       mutex_unlock(&enable_mutex);
+
+       return 0;
+}
+
+static int hi6421_spmi_regulator_disable(struct regulator_dev *rdev)
+{
+       struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+       struct hi6421_spmi_pmic *pmic = sreg->pmic;
+
+       /* set enable register to 0 */
+       rdev_dbg(rdev, "enable_reg=0x%x, enable_mask=0x%x\n",
+                rdev->desc->enable_reg, rdev->desc->enable_mask);
+
+       hi6421_spmi_pmic_rmw(pmic, rdev->desc->enable_reg,
+                            rdev->desc->enable_mask, 0);
+
+       return 0;
+}
+
+static int hi6421_spmi_regulator_get_voltage_sel(struct regulator_dev *rdev)
+{
+       struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+       struct hi6421_spmi_pmic *pmic = sreg->pmic;
+       u32 reg_val, selector;
+
+       /* get voltage selector */
+       reg_val = hi6421_spmi_pmic_read(pmic, rdev->desc->vsel_reg);
+
+       selector = (reg_val & rdev->desc->vsel_mask) >> 
(ffs(rdev->desc->vsel_mask) - 1);
+
+       rdev_dbg(rdev,
+                "vsel_reg=0x%x, value=0x%x, entry=0x%x, voltage=%d mV\n",
+                rdev->desc->vsel_reg, reg_val, selector,
+               rdev->desc->ops->list_voltage(rdev, selector) / 1000);
+
+       return selector;
+}
+
+static int hi6421_spmi_regulator_set_voltage_sel(struct regulator_dev *rdev,
+                                                unsigned int selector)
+{
+       struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+       struct hi6421_spmi_pmic *pmic = sreg->pmic;
+       u32 reg_val;
+
+       /* unlikely to happen. sanity test done by regulator core */
+       if (unlikely(selector >= rdev->desc->n_voltages))
+               return -EINVAL;
+
+       reg_val = selector << (ffs(rdev->desc->vsel_mask) - 1);
+
+       /* set voltage selector */
+       rdev_dbg(rdev,
+                "vsel_reg=0x%x, mask=0x%x, value=0x%x, voltage=%d mV\n",
+                rdev->desc->vsel_reg, rdev->desc->vsel_mask, reg_val,
+                rdev->desc->ops->list_voltage(rdev, selector) / 1000);
+
+       hi6421_spmi_pmic_rmw(pmic, rdev->desc->vsel_reg,
+                            rdev->desc->vsel_mask, reg_val);
+
+       return 0;
+}
+
+static unsigned int hi6421_spmi_regulator_get_mode(struct regulator_dev *rdev)
+{
+       struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+       struct hi6421_spmi_pmic *pmic = sreg->pmic;
+       u32 reg_val;
+       unsigned int mode;
+
+       reg_val = hi6421_spmi_pmic_read(pmic, rdev->desc->enable_reg);
+
+       if (reg_val & sreg->eco_mode_mask)
+               mode = REGULATOR_MODE_IDLE;
+       else
+               mode = REGULATOR_MODE_NORMAL;
+
+       rdev_dbg(rdev,
+                "enable_reg=0x%x, eco_mode_mask=0x%x, reg_val=0x%x, %s mode\n",
+                rdev->desc->enable_reg, sreg->eco_mode_mask, reg_val,
+                mode == REGULATOR_MODE_IDLE ? "idle" : "normal");
+
+       return mode;
+}
+
+static int hi6421_spmi_regulator_set_mode(struct regulator_dev *rdev,
+                                         unsigned int mode)
+{
+       struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+       struct hi6421_spmi_pmic *pmic = sreg->pmic;
+       u32 val;
+
+       switch (mode) {
+       case REGULATOR_MODE_NORMAL:
+               val = 0;
+               break;
+       case REGULATOR_MODE_IDLE:
+               val = sreg->eco_mode_mask << (ffs(sreg->eco_mode_mask) - 1);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* set mode */
+       rdev_dbg(rdev, "enable_reg=0x%x, eco_mode_mask=0x%x, value=0x%x\n",
+                rdev->desc->enable_reg, sreg->eco_mode_mask, val);
+
+       hi6421_spmi_pmic_rmw(pmic, rdev->desc->enable_reg,
+                            sreg->eco_mode_mask, val);
+
+       return 0;
+}
+
+static unsigned int
+hi6421_spmi_regulator_get_optimum_mode(struct regulator_dev *rdev,
+                                      int input_uV, int output_uV,
+                                      int load_uA)
+{
+       struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+
+       if (load_uA || ((unsigned int)load_uA > sreg->eco_uA)) {
+               rdev_dbg(rdev, "normal mode");
+               return REGULATOR_MODE_NORMAL;
+       } else {
+               rdev_dbg(rdev, "idle mode");
+               return REGULATOR_MODE_IDLE;
+       }
+}
+
+static int hi6421_spmi_dt_parse(struct platform_device *pdev,
+                               struct hi6421v600_regulator *sreg,
+                        struct regulator_desc *rdesc)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+       unsigned int *v_table;
+       int ret;
+
+       ret = of_property_read_u32(np, "reg", &rdesc->enable_reg);
+       if (ret) {
+               dev_err(dev, "missing reg property\nn");
+               return ret;
+       }
+
+       ret = of_property_read_u32(np, "vsel-reg", &rdesc->vsel_reg);
+       if (ret) {
+               dev_err(dev, "missing vsel-reg property\n");
+               return ret;
+       }
+
+       ret = of_property_read_u32(np, "enable-mask", &rdesc->enable_mask);
+       if (ret) {
+               dev_err(dev, "missing enable-mask property\n");
+               return ret;
+       }
+
+       /*
+        * Not all regulators work on idle mode
+        */
+       ret = of_property_read_u32(np, "idle-mode-mask", &sreg->eco_mode_mask);
+       if (ret) {
+               dev_dbg(dev, "LDO doesn't support economy mode.\n");
+               sreg->eco_mode_mask = 0;
+               sreg->eco_uA = 0;
+       } else {
+               ret = of_property_read_u32(np, "eco-microamp",
+                                          &sreg->eco_uA);
+               if (ret) {
+                       dev_err(dev, "missing eco-microamp property\n");
+                       return ret;
+               }
+       }
+
+       /* parse .off-on-delay */
+       ret = of_property_read_u32(np, "off-on-delay-us",
+                                  &rdesc->off_on_delay);
+       if (ret) {
+               dev_err(dev, "missing off-on-delay-us property\n");
+               return ret;
+       }
+
+       /* parse .enable_time */
+       ret = of_property_read_u32(np, "startup-delay-us",
+                                  &rdesc->enable_time);
+       if (ret) {
+               dev_err(dev, "missing startup-delay-us property\n");
+               return ret;
+       }
+
+       /* FIXME: are there a better value for this? */
+       rdesc->ramp_delay = rdesc->enable_time;
+
+       /* parse volt_table */
+
+       rdesc->n_voltages = of_property_count_u32_elems(np, "voltage-table");
+
+       v_table = devm_kzalloc(dev, sizeof(unsigned int) * rdesc->n_voltages,
+                              GFP_KERNEL);
+       if (unlikely(!v_table))
+               return  -ENOMEM;
+       rdesc->volt_table = v_table;
+
+       ret = of_property_read_u32_array(np, "voltage-table",
+                                        v_table, rdesc->n_voltages);
+       if (ret) {
+               dev_err(dev, "missing voltage-table property\n");
+               return ret;
+       }
+
+       /*
+        * Instead of explicitly requiring a mask for the voltage selector,
+        * as they all start from bit zero (at least on the known LDOs),
+        * just use the number of voltages at the voltage table, getting the
+        * minimal mask that would pick everything.
+        */
+       rdesc->vsel_mask = (1 << (fls(rdesc->n_voltages) - 1)) - 1;
+
+       dev_dbg(dev, "voltage selector settings: reg: 0x%x, mask: 0x%x",
+               rdesc->vsel_reg, rdesc->vsel_mask);
+
+       return 0;
+}
+
+static struct regulator_ops hi6421_spmi_ldo_rops = {
+       .is_enabled = hi6421_spmi_regulator_is_enabled,
+       .enable = hi6421_spmi_regulator_enable,
+       .disable = hi6421_spmi_regulator_disable,
+       .list_voltage = regulator_list_voltage_table,
+       .map_voltage = regulator_map_voltage_iterate,
+       .get_voltage_sel = hi6421_spmi_regulator_get_voltage_sel,
+       .set_voltage_sel = hi6421_spmi_regulator_set_voltage_sel,
+       .get_mode = hi6421_spmi_regulator_get_mode,
+       .set_mode = hi6421_spmi_regulator_set_mode,
+       .get_optimum_mode = hi6421_spmi_regulator_get_optimum_mode,
+};
+
+/*
+ * Used only for parsing the DT properties
+ */
+
+static int hi6421_spmi_regulator_probe_ldo(struct platform_device *pdev,
+                                          struct device_node *np,
+                                          struct hi6421_spmi_pmic *pmic)
+{
+       struct device *dev = &pdev->dev;
+       struct regulator_desc *rdesc;
+       struct regulator_dev *rdev;
+       struct hi6421v600_regulator *sreg = NULL;
+       struct regulator_init_data *initdata;
+       struct regulator_config config = { };
+       struct regulation_constraints *constraint;
+       const char *supplyname = NULL;
+       int ret = 0;
+
+       initdata = of_get_regulator_init_data(dev, np, NULL);
+       if (!initdata) {
+               dev_err(dev, "failed to get regulator data\n");
+               return -EINVAL;
+       }
+
+       sreg = kzalloc(sizeof(*sreg), GFP_KERNEL);
+       if (!sreg)
+               return -ENOMEM;
+
+       sreg->pmic = pmic;
+       rdesc = &sreg->rdesc;
+
+       rdesc->name = initdata->constraints.name;
+       rdesc->ops = &hi6421_spmi_ldo_rops;
+       rdesc->type = REGULATOR_VOLTAGE;
+       rdesc->min_uV = initdata->constraints.min_uV;
+
+       supplyname = of_get_property(np, "supply_name", NULL);
+       if (supplyname)
+               initdata->supply_regulator = supplyname;
+
+       /* parse device tree data for regulator specific */
+       ret = hi6421_spmi_dt_parse(pdev, sreg, rdesc);
+       if (ret)
+               goto probe_end;
+
+       /* hisi regulator supports two modes */
+       constraint = &initdata->constraints;
+
+       constraint->valid_modes_mask = REGULATOR_MODE_NORMAL;
+       if (sreg->eco_mode_mask) {
+               constraint->valid_modes_mask |= REGULATOR_MODE_IDLE;
+               constraint->valid_ops_mask |= REGULATOR_CHANGE_MODE;
+       }
+
+       config.dev = &pdev->dev;
+       config.init_data = initdata;
+       config.driver_data = sreg;
+       config.of_node = pdev->dev.of_node;
+
+       /* register regulator */
+       rdev = regulator_register(rdesc, &config);
+       if (IS_ERR(rdev)) {
+               dev_err(dev, "failed to register %s\n",
+                       rdesc->name);
+               ret = PTR_ERR(rdev);
+               goto probe_end;
+       }
+
+       rdev_dbg(rdev, "valid_modes_mask: 0x%x, valid_ops_mask: 0x%x\n",
+                constraint->valid_modes_mask, constraint->valid_ops_mask);
+
+       dev_set_drvdata(dev, rdev);
+probe_end:
+       if (ret)
+               kfree(sreg);
+       return ret;
+}
+
+static int hi6421_spmi_regulator_probe(struct platform_device *pdev)
+{
+       struct device *pmic_dev = pdev->dev.parent;
+       struct device_node *np = pmic_dev->of_node;
+       struct device_node *regulators, *child;
+       struct platform_device *new_pdev;
+       struct hi6421_spmi_pmic *pmic;
+       int ret;
+
+       dev_dbg(&pdev->dev, "probing hi6421v600 regulator\n");
+       /*
+        * This driver is meant to be called by hi6421-spmi-core,
+        * which should first set drvdata. If this doesn't happen, hit
+        * a warn on and return.
+        */
+       pmic = dev_get_drvdata(pmic_dev);
+       if (WARN_ON(!pmic))
+               return -ENODEV;
+
+       regulators = of_get_child_by_name(np, "regulators");
+       if (!regulators) {
+               dev_err(&pdev->dev, "regulator node not found\n");
+               return -ENODEV;
+       }
+
+       /*
+        * Parse all LDO regulator nodes
+        */
+       for_each_child_of_node(regulators, child) {
+               dev_dbg(&pdev->dev, "adding child %pOF\n", child);
+
+               new_pdev = platform_device_alloc(child->name, -1);
+               new_pdev->dev.parent = pmic_dev;
+               new_pdev->dev.of_node = of_node_get(child);
+
+               ret = platform_device_add(new_pdev);
+               if (ret < 0) {
+                       platform_device_put(new_pdev);
+                       continue;
+               }
+
+               ret = hi6421_spmi_regulator_probe_ldo(new_pdev, child, pmic);
+               if (ret < 0)
+                       platform_device_put(new_pdev);
+       }
+
+       of_node_put(regulators);
+
+       return 0;
+}
+
+static int hi6421_spmi_regulator_remove(struct platform_device *pdev)
+{
+       struct regulator_dev *rdev = dev_get_drvdata(&pdev->dev);
+       struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+
+       regulator_unregister(rdev);
+
+       /* TODO: should i worry about that? devm_kzalloc */
+       if (rdev->desc->volt_table)
+               devm_kfree(&pdev->dev, (unsigned int *)rdev->desc->volt_table);
+
+       kfree(sreg);
+
+       return 0;
+}
+
+static const struct platform_device_id hi6421v600_regulator_table[] = {
+       { .name = "hi6421v600-regulator" },
+       {},
+};
+MODULE_DEVICE_TABLE(platform, hi6421v600_regulator_table);
+
+static struct platform_driver hi6421v600_regulator_driver = {
+       .id_table = hi6421v600_regulator_table,
+       .driver = {
+               .name   = "hi6421v600-regulator",
+       },
+       .probe  = hi6421_spmi_regulator_probe,
+       .remove = hi6421_spmi_regulator_remove,
+};
+module_platform_driver(hi6421v600_regulator_driver);
+
+MODULE_DESCRIPTION("Hi6421v600 regulator driver");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig
index a53bad541f1a..b44e2ab6bf81 100644
--- a/drivers/spmi/Kconfig
+++ b/drivers/spmi/Kconfig
@@ -25,4 +25,13 @@ config SPMI_MSM_PMIC_ARB
          This is required for communicating with Qualcomm PMICs and
          other devices that have the SPMI interface.
 
+config SPMI_HISI3670
+       tristate "Hisilicon 3670 SPMI Controller"
+       select IRQ_DOMAIN_HIERARCHY
+       depends on HAS_IOMEM
+       help
+         If you say yes to this option, support will be included for the
+         built-in SPMI PMIC Arbiter interface on Hisilicon 3670
+         processors.
+
 endif
diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile
index 55a94cadeffe..694853e391cb 100644
--- a/drivers/spmi/Makefile
+++ b/drivers/spmi/Makefile
@@ -5,3 +5,5 @@
 obj-$(CONFIG_SPMI)     += spmi.o
 
 obj-$(CONFIG_SPMI_MSM_PMIC_ARB)        += spmi-pmic-arb.o
+
+obj-$(CONFIG_SPMI_HISI3670) += hisi-spmi-controller.o
diff --git a/drivers/spmi/hisi-spmi-controller.c 
b/drivers/spmi/hisi-spmi-controller.c
new file mode 100644
index 000000000000..153bcdb0cde4
--- /dev/null
+++ b/drivers/spmi/hisi-spmi-controller.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/spmi.h>
+
+#define SPMI_CONTROLLER_NAME           "spmi_controller"
+
+/*
+ * SPMI register addr
+ */
+#define SPMI_CHANNEL_OFFSET                            0x0300
+#define SPMI_SLAVE_OFFSET                              0x20
+
+#define SPMI_APB_SPMI_CMD_BASE_ADDR                    0x0100
+
+#define SPMI_APB_SPMI_WDATA0_BASE_ADDR                 0x0104
+#define SPMI_APB_SPMI_WDATA1_BASE_ADDR                 0x0108
+#define SPMI_APB_SPMI_WDATA2_BASE_ADDR                 0x010c
+#define SPMI_APB_SPMI_WDATA3_BASE_ADDR                 0x0110
+
+#define SPMI_APB_SPMI_STATUS_BASE_ADDR                 0x0200
+
+#define SPMI_APB_SPMI_RDATA0_BASE_ADDR                 0x0204
+#define SPMI_APB_SPMI_RDATA1_BASE_ADDR                 0x0208
+#define SPMI_APB_SPMI_RDATA2_BASE_ADDR                 0x020c
+#define SPMI_APB_SPMI_RDATA3_BASE_ADDR                 0x0210
+
+#define SPMI_PER_DATAREG_BYTE                          4
+/*
+ * SPMI cmd register
+ */
+#define SPMI_APB_SPMI_CMD_EN                           BIT(31)
+#define SPMI_APB_SPMI_CMD_TYPE_OFFSET                  24
+#define SPMI_APB_SPMI_CMD_LENGTH_OFFSET                        20
+#define SPMI_APB_SPMI_CMD_SLAVEID_OFFSET               16
+#define SPMI_APB_SPMI_CMD_ADDR_OFFSET                  0
+
+/* Command Opcodes */
+
+enum spmi_controller_cmd_op_code {
+       SPMI_CMD_REG_ZERO_WRITE = 0,
+       SPMI_CMD_REG_WRITE = 1,
+       SPMI_CMD_REG_READ = 2,
+       SPMI_CMD_EXT_REG_WRITE = 3,
+       SPMI_CMD_EXT_REG_READ = 4,
+       SPMI_CMD_EXT_REG_WRITE_L = 5,
+       SPMI_CMD_EXT_REG_READ_L = 6,
+       SPMI_CMD_REG_RESET = 7,
+       SPMI_CMD_REG_SLEEP = 8,
+       SPMI_CMD_REG_SHUTDOWN = 9,
+       SPMI_CMD_REG_WAKEUP = 10,
+};
+
+/*
+ * SPMI status register
+ */
+#define SPMI_APB_TRANS_DONE                    BIT(0)
+#define SPMI_APB_TRANS_FAIL                    BIT(2)
+
+/* Command register fields */
+#define SPMI_CONTROLLER_CMD_MAX_BYTE_COUNT     16
+
+/* Maximum number of support PMIC peripherals */
+#define SPMI_CONTROLLER_TIMEOUT_US             1000
+#define SPMI_CONTROLLER_MAX_TRANS_BYTES                16
+
+/*
+ * @base base address of the PMIC Arbiter core registers.
+ * @rdbase, @wrbase base address of the PMIC Arbiter read core registers.
+ *     For HW-v1 these are equal to base.
+ *     For HW-v2, the value is the same in eeraly probing, in order to read
+ *     PMIC_ARB_CORE registers, then chnls, and obsrvr are set to
+ *     PMIC_ARB_CORE_REGISTERS and PMIC_ARB_CORE_REGISTERS_OBS respectivly.
+ * @intr base address of the SPMI interrupt control registers
+ * @ppid_2_chnl_tbl lookup table f(SID, Periph-ID) -> channel num
+ *      entry is only valid if corresponding bit is set in valid_ppid_bitmap.
+ * @valid_ppid_bitmap bit is set only for valid ppids.
+ * @fmt_cmd formats a command to be set into PMIC_ARBq_CHNLn_CMD
+ * @chnl_ofst calculates offset of the base of a channel reg space
+ * @ee execution environment id
+ * @irq_acc0_init_val initial value of the interrupt accumulator at probe time.
+ *      Use for an HW workaround. On handling interrupts, the first accumulator
+ *      register will be compared against this value, and bits which are set at
+ *      boot will be ignored.
+ * @reserved_chnl entry of ppid_2_chnl_tbl that this driver should never touch.
+ *      value is positive channel number or negative to mark it unused.
+ */
+struct spmi_controller_dev {
+       struct spmi_controller  *controller;
+       struct device           *dev;
+       void __iomem            *base;
+       spinlock_t              lock;
+       u32                     channel;
+};
+
+static int spmi_controller_wait_for_done(struct device *dev,
+                                        struct spmi_controller_dev *ctrl_dev,
+                                        void __iomem *base, u8 sid, u16 addr)
+{
+       u32 status = 0;
+       u32 timeout = SPMI_CONTROLLER_TIMEOUT_US;
+       u32 offset;
+
+       offset  = SPMI_APB_SPMI_STATUS_BASE_ADDR;
+       offset += SPMI_CHANNEL_OFFSET * ctrl_dev->channel + SPMI_SLAVE_OFFSET * 
sid;
+
+       while (timeout--) {
+               status = readl(base + offset);
+
+               if (status & SPMI_APB_TRANS_DONE) {
+                       if (status & SPMI_APB_TRANS_FAIL) {
+                               dev_err(dev, "%s: transaction failed (0x%x)\n",
+                                       __func__, status);
+                               return -EIO;
+                       }
+                       dev_dbg(dev, "%s: status 0x%x\n", __func__, status);
+                       return 0;
+               }
+               udelay(1);
+       }
+
+       dev_err(dev, "%s: timeout, status 0x%x\n", __func__, status);
+       return -ETIMEDOUT;
+}
+
+static int spmi_read_cmd(struct spmi_controller *ctrl,
+                        u8 opc, u8 sid, u16 addr, u8 *__buf, size_t bc)
+{
+       struct spmi_controller_dev *spmi_controller = 
dev_get_drvdata(&ctrl->dev);
+       unsigned long flags;
+       u8 *buf = __buf;
+       u32 cmd, data;
+       int rc;
+       u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel;
+       u8 op_code, i;
+
+       if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) {
+               dev_err(&ctrl->dev,
+                       "spmi_controller supports 1..%d bytes per trans, 
but:%ld requested",
+                       SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
+               return  -EINVAL;
+       }
+
+       /* Check the opcode */
+       if (opc == SPMI_CMD_READ) {
+               op_code = SPMI_CMD_REG_READ;
+       } else if (opc == SPMI_CMD_EXT_READ) {
+               op_code = SPMI_CMD_EXT_REG_READ;
+       } else if (opc == SPMI_CMD_EXT_READL) {
+               op_code = SPMI_CMD_EXT_REG_READ_L;
+       } else {
+               dev_err(&ctrl->dev, "invalid read cmd 0x%x", opc);
+               return -EINVAL;
+       }
+
+       cmd = SPMI_APB_SPMI_CMD_EN |
+            (op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) |
+            ((bc - 1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) |
+            ((sid & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) |  /* slvid */
+            ((addr & 0xffff)  << SPMI_APB_SPMI_CMD_ADDR_OFFSET); /* slave_addr 
*/
+
+       spin_lock_irqsave(&spmi_controller->lock, flags);
+
+       writel(cmd, spmi_controller->base + chnl_ofst + 
SPMI_APB_SPMI_CMD_BASE_ADDR);
+
+       rc = spmi_controller_wait_for_done(&ctrl->dev, spmi_controller,
+                                          spmi_controller->base, sid, addr);
+       if (rc)
+               goto done;
+
+       i = 0;
+       do {
+               data = readl(spmi_controller->base + chnl_ofst + 
SPMI_SLAVE_OFFSET * sid + SPMI_APB_SPMI_RDATA0_BASE_ADDR + i * 
SPMI_PER_DATAREG_BYTE);
+               data = be32_to_cpu((__be32)data);
+               if ((bc - i * SPMI_PER_DATAREG_BYTE) >> 2) {
+                       memcpy(buf, &data, sizeof(data));
+                       buf += sizeof(data);
+               } else {
+                       memcpy(buf, &data, bc % SPMI_PER_DATAREG_BYTE);
+                       buf += (bc % SPMI_PER_DATAREG_BYTE);
+               }
+               i++;
+       } while (bc > i * SPMI_PER_DATAREG_BYTE);
+
+done:
+       spin_unlock_irqrestore(&spmi_controller->lock, flags);
+       if (rc)
+               dev_err(&ctrl->dev,
+                       "spmi read wait timeout op:0x%x sid:%d addr:0x%x 
bc:%ld\n",
+                       opc, sid, addr, bc + 1);
+       else
+               dev_dbg(&ctrl->dev, "%s: id:%d addr:0x%x, read value: %*ph\n",
+                       __func__, sid, addr, (int)bc, __buf);
+
+       return rc;
+}
+
+static int spmi_write_cmd(struct spmi_controller *ctrl,
+                         u8 opc, u8 sid, u16 addr, const u8 *__buf, size_t bc)
+{
+       struct spmi_controller_dev *spmi_controller = 
dev_get_drvdata(&ctrl->dev);
+       const u8 *buf = __buf;
+       unsigned long flags;
+       u32 cmd, data;
+       int rc;
+       u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel;
+       u8 op_code, i;
+
+       if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) {
+               dev_err(&ctrl->dev,
+                       "spmi_controller supports 1..%d bytes per trans, 
but:%ld requested",
+                       SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
+               return  -EINVAL;
+       }
+
+       /* Check the opcode */
+       if (opc == SPMI_CMD_WRITE) {
+               op_code = SPMI_CMD_REG_WRITE;
+       } else if (opc == SPMI_CMD_EXT_WRITE) {
+               op_code = SPMI_CMD_EXT_REG_WRITE;
+       } else if (opc == SPMI_CMD_EXT_WRITEL) {
+               op_code = SPMI_CMD_EXT_REG_WRITE_L;
+       } else {
+               dev_err(&ctrl->dev, "invalid write cmd 0x%x", opc);
+               return -EINVAL;
+       }
+
+       cmd = SPMI_APB_SPMI_CMD_EN |
+             (op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) |
+             ((bc - 1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) |
+             ((sid & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) |  /* slvid */
+             ((addr & 0xffff)  << SPMI_APB_SPMI_CMD_ADDR_OFFSET); /* 
slave_addr */
+
+       /* Write data to FIFOs */
+       spin_lock_irqsave(&spmi_controller->lock, flags);
+
+       i = 0;
+       do {
+               data = 0;
+               if ((bc - i * SPMI_PER_DATAREG_BYTE) >> 2) {
+                       memcpy(&data, buf, sizeof(data));
+                       buf += sizeof(data);
+               } else {
+                       memcpy(&data, buf, bc % SPMI_PER_DATAREG_BYTE);
+                       buf += (bc % SPMI_PER_DATAREG_BYTE);
+               }
+
+               writel((u32)cpu_to_be32(data),
+                      spmi_controller->base + chnl_ofst + 
SPMI_APB_SPMI_WDATA0_BASE_ADDR + SPMI_PER_DATAREG_BYTE * i);
+               i++;
+       } while (bc > i * SPMI_PER_DATAREG_BYTE);
+
+       /* Start the transaction */
+       writel(cmd, spmi_controller->base + chnl_ofst + 
SPMI_APB_SPMI_CMD_BASE_ADDR);
+
+       rc = spmi_controller_wait_for_done(&ctrl->dev, spmi_controller,
+                                          spmi_controller->base, sid, addr);
+       spin_unlock_irqrestore(&spmi_controller->lock, flags);
+
+       if (rc)
+               dev_err(&ctrl->dev, "spmi write wait timeout op:0x%x sid:%d 
addr:0x%x bc:%ld\n",
+                       opc, sid, addr, bc);
+       else
+               dev_dbg(&ctrl->dev, "%s: id:%d addr:0x%x, wrote value: %*ph\n",
+                       __func__, sid, addr, (int)bc, __buf);
+
+       return rc;
+}
+
+static int spmi_controller_probe(struct platform_device *pdev)
+{
+       struct spmi_controller_dev *spmi_controller;
+       struct spmi_controller *ctrl;
+       struct resource *iores;
+       int ret = 0;
+
+       dev_info(&pdev->dev, "HISI SPMI probe\n");
+
+       ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*spmi_controller));
+       if (!ctrl) {
+               dev_err(&pdev->dev, "can not allocate spmi_controller data\n");
+               return -ENOMEM;
+       }
+       spmi_controller = spmi_controller_get_drvdata(ctrl);
+       spmi_controller->controller = ctrl;
+
+       /* NOTE: driver uses the static register mapping */
+       iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!iores) {
+               dev_err(&pdev->dev, "can not get resource!\n");
+               return -EINVAL;
+       }
+
+       spmi_controller->base = ioremap(iores->start, resource_size(iores));
+       if (!spmi_controller->base) {
+               dev_err(&pdev->dev, "can not remap base addr!\n");
+               return -EADDRNOTAVAIL;
+       }
+       dev_dbg(&pdev->dev, "spmi_add_controller base addr=0x%lx!\n",
+               (unsigned long)spmi_controller->base);
+
+       /* Get properties from the device tree */
+       ret = of_property_read_u32(pdev->dev.of_node, "spmi-channel",
+                                  &spmi_controller->channel);
+       if (ret) {
+               dev_err(&pdev->dev, "can not get channel\n");
+               return -ENODEV;
+       }
+
+       platform_set_drvdata(pdev, spmi_controller);
+       dev_set_drvdata(&ctrl->dev, spmi_controller);
+
+       spin_lock_init(&spmi_controller->lock);
+
+       ctrl->nr = spmi_controller->channel;
+       ctrl->dev.parent = pdev->dev.parent;
+       ctrl->dev.of_node = of_node_get(pdev->dev.of_node);
+
+       /* Callbacks */
+       ctrl->read_cmd = spmi_read_cmd;
+       ctrl->write_cmd = spmi_write_cmd;
+
+       ret = spmi_controller_add(ctrl);
+       if (ret)
+               goto err_add_controller;
+
+       dev_info(&pdev->dev, "spmi_add_controller initialized\n");
+       return 0;
+
+err_add_controller:
+       dev_err(&pdev->dev, "spmi_add_controller failed!\n");
+       platform_set_drvdata(pdev, NULL);
+       return ret;
+}
+
+static int spmi_del_controller(struct platform_device *pdev)
+{
+       struct spmi_controller *ctrl = platform_get_drvdata(pdev);
+
+       platform_set_drvdata(pdev, NULL);
+       spmi_controller_remove(ctrl);
+       return 0;
+}
+
+static const struct of_device_id spmi_controller_match_table[] = {
+       {       .compatible = "hisilicon,spmi-controller",
+       },
+       {}
+};
+MODULE_DEVICE_TABLE(of, spmi_controller_match_table);
+
+static struct platform_driver spmi_controller_driver = {
+       .probe          = spmi_controller_probe,
+       .remove         = spmi_del_controller,
+       .driver         = {
+               .name   = SPMI_CONTROLLER_NAME,
+               .of_match_table = spmi_controller_match_table,
+       },
+};
+
+static int __init spmi_controller_init(void)
+{
+       return platform_driver_register(&spmi_controller_driver);
+}
+postcore_initcall(spmi_controller_init);
+
+static void __exit spmi_controller_exit(void)
+{
+       platform_driver_unregister(&spmi_controller_driver);
+}
+module_exit(spmi_controller_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:spmi_controlller");
diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c
index c16b60f645a4..253340e10dab 100644
--- a/drivers/spmi/spmi.c
+++ b/drivers/spmi/spmi.c
@@ -23,6 +23,7 @@ static DEFINE_IDA(ctrl_ida);
 static void spmi_dev_release(struct device *dev)
 {
        struct spmi_device *sdev = to_spmi_device(dev);
+
        kfree(sdev);
 }
 
@@ -33,6 +34,7 @@ static const struct device_type spmi_dev_type = {
 static void spmi_ctrl_release(struct device *dev)
 {
        struct spmi_controller *ctrl = to_spmi_controller(dev);
+
        ida_simple_remove(&ctrl_ida, ctrl->nr);
        kfree(ctrl);
 }
@@ -487,7 +489,7 @@ static void of_spmi_register_devices(struct spmi_controller 
*ctrl)
                        continue;
 
                sdev->dev.of_node = node;
-               sdev->usid = (u8) reg[0];
+               sdev->usid = (u8)reg[0];
 
                err = spmi_device_add(sdev);
                if (err) {
@@ -531,6 +533,7 @@ EXPORT_SYMBOL_GPL(spmi_controller_add);
 static int spmi_ctrl_remove_device(struct device *dev, void *data)
 {
        struct spmi_device *spmidev = to_spmi_device(dev);
+
        if (dev->type == &spmi_dev_type)
                spmi_device_remove(spmidev);
        return 0;
@@ -545,13 +548,10 @@ static int spmi_ctrl_remove_device(struct device *dev, 
void *data)
  */
 void spmi_controller_remove(struct spmi_controller *ctrl)
 {
-       int dummy;
-
        if (!ctrl)
                return;
 
-       dummy = device_for_each_child(&ctrl->dev, NULL,
-                                     spmi_ctrl_remove_device);
+       device_for_each_child(&ctrl->dev, NULL, spmi_ctrl_remove_device);
        device_del(&ctrl->dev);
 }
 EXPORT_SYMBOL_GPL(spmi_controller_remove);
diff --git a/include/linux/mfd/hi6421-spmi-pmic.h 
b/include/linux/mfd/hi6421-spmi-pmic.h
new file mode 100644
index 000000000000..aeff96c4a37e
--- /dev/null
+++ b/include/linux/mfd/hi6421-spmi-pmic.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Header file for device driver Hi6421 PMIC
+ *
+ * Copyright (c) 2013 Linaro Ltd.
+ * Copyright (C) 2011 Hisilicon.
+ *
+ * Guodong Xu <guodong...@linaro.org>
+ */
+
+#ifndef        __HISI_PMIC_H
+#define        __HISI_PMIC_H
+
+#include <linux/irqdomain.h>
+
+#define HISI_REGS_ENA_PROTECT_TIME     (0)     /* in microseconds */
+#define HISI_ECO_MODE_ENABLE           (1)
+#define HISI_ECO_MODE_DISABLE          (0)
+
+struct hi6421_spmi_irq_mask_info {
+       int start_addr;
+       int array;
+};
+
+struct hi6421_spmi_irq_info {
+       int start_addr;
+       int array;
+};
+
+struct hi6421_spmi_pmic {
+       struct resource                         *res;
+       struct device                           *dev;
+       void __iomem                            *regs;
+       spinlock_t                              lock;
+       struct irq_domain                       *domain;
+       int                                     irq;
+       int                                     gpio;
+       unsigned int                            *irqs;
+       int                                     irqnum;
+       int                                     irqarray;
+
+       struct hi6421_spmi_irq_mask_info        irq_mask_addr;
+       struct hi6421_spmi_irq_info             irq_addr;
+};
+
+u32 hi6421_spmi_pmic_read(struct hi6421_spmi_pmic *pmic, int reg);
+void hi6421_spmi_pmic_write(struct hi6421_spmi_pmic *pmic, int reg, u32 val);
+void hi6421_spmi_pmic_rmw(struct hi6421_spmi_pmic *pmic, int reg, u32 mask, 
u32 bits);
+
+enum hi6421_spmi_pmic_irq_list {
+       OTMP = 0,
+       VBUS_CONNECT,
+       VBUS_DISCONNECT,
+       ALARMON_R,
+       HOLD_6S,
+       HOLD_1S,
+       POWERKEY_UP,
+       POWERKEY_DOWN,
+       OCP_SCP_R,
+       COUL_R,
+       SIM0_HPD_R,
+       SIM0_HPD_F,
+       SIM1_HPD_R,
+       SIM1_HPD_F,
+       PMIC_IRQ_LIST_MAX,
+};
+#endif         /* __HISI_PMIC_H */


Thanks,
Mauro

Reply via email to