summaryrefslogtreecommitdiffstats
path: root/drivers/staging/hikey9xx
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2020-10-15 09:46:23 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2020-10-15 09:46:23 -0700
commitade7afe3e606f9f6ff0e6deefce140157f75540b (patch)
tree14be1cde214ed46179c23c007cbdc2e98bc2a381 /drivers/staging/hikey9xx
parent3e4fb4346c781068610d03c12b16c0cfb0fd24a3 (diff)
parente1f13c879a7c21bd207dc6242455e8e3a1e88b40 (diff)
downloadlinux-ade7afe3e606f9f6ff0e6deefce140157f75540b.tar.bz2
Merge tag 'staging-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
Pull staging / IIO driver updates from Greg KH: "Here is the large set of staging and IIO driver updates for 5.10-rc1. Included in here are: - new IIO drivers - new IIO driver frameworks - various IIO driver fixes and updates - IIO device tree conversions to yaml - so many minor staging driver coding style cleanups - most cdev driver moved out of staging - no staging drivers added or removed Full details are in the shortlog. All of these have been in linux-next for a while with no reported issues" * tag 'staging-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (476 commits) staging: comedi: check validity of wMaxPacketSize of usb endpoints found staging: wfx: improve robustness of wfx_get_hw_rate() staging: wfx: drop unicode characters from strings staging: wfx: gpiod_get_value() can return an error staging: wfx: increase robustness of hif_generic_confirm() staging: wfx: wfx_init_common() returns NULL on error staging: wfx: standardize the error when vif does not exist staging: wfx: check memory allocation staging: wfx: improve error handling of hif_join() staging: dpaa2-switch: add a dpaa2_switch prefix to all functions in ethsw.c staging: dpaa2-switch: add a dpaa2_switch_ prefix to all functions in ethsw-ethtool.c staging: rtl8188eu: Fix long lines dt-bindings: staging: wfx: silabs,wfx yaml conversion staging: wfx: update copyrights dates staging: wfx: fix QoS priority for slow buses staging: wfx: fix BA sessions for older firmwares staging: wfx: remove remaining code of 'secure link' feature staging: wfx: fix handling of MMIC error staging: vchiq: Fix list_for_each exit tests staging: greybus: use __force when assigning __u8 value to snd_ctl_elem_type_t ...
Diffstat (limited to 'drivers/staging/hikey9xx')
-rw-r--r--drivers/staging/hikey9xx/Kconfig49
-rw-r--r--drivers/staging/hikey9xx/Makefile7
-rw-r--r--drivers/staging/hikey9xx/TODO5
-rw-r--r--drivers/staging/hikey9xx/hi6421-spmi-pmic.c342
-rw-r--r--drivers/staging/hikey9xx/hi6421v600-regulator.c478
-rw-r--r--drivers/staging/hikey9xx/hisi-spmi-controller.c358
-rw-r--r--drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml159
-rw-r--r--drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml62
-rw-r--r--drivers/staging/hikey9xx/phy-hi3670-usb3.c671
-rw-r--r--drivers/staging/hikey9xx/phy-hi3670-usb3.yaml72
10 files changed, 2203 insertions, 0 deletions
diff --git a/drivers/staging/hikey9xx/Kconfig b/drivers/staging/hikey9xx/Kconfig
new file mode 100644
index 000000000000..b29f5d5df134
--- /dev/null
+++ b/drivers/staging/hikey9xx/Kconfig
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# to be placed at drivers/phy
+config PHY_HI3670_USB
+ tristate "hi3670 USB PHY support"
+ depends on (ARCH_HISI && ARM64) || COMPILE_TEST
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ Enable this to support the HISILICON HI3670 USB PHY.
+
+ To compile this driver as a module, choose M here.
+
+# to be placed at drivers/spmi
+config SPMI_HISI3670
+ tristate "Hisilicon 3670 SPMI Controller"
+ select IRQ_DOMAIN_HIERARCHY
+ depends on HAS_IOMEM
+ depends on SPMI
+ help
+ If you say yes to this option, support will be included for the
+ built-in SPMI PMIC Arbiter interface on Hisilicon 3670
+ processors.
+
+# to be placed at drivers/mfd
+config MFD_HI6421_SPMI
+ tristate "HiSilicon Hi6421v600 SPMI PMU/Codec IC"
+ depends on OF
+ depends on SPMI
+ select MFD_CORE
+ 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.
+
+# to be placed at drivers/regulator
+config REGULATOR_HI6421V600
+ tristate "HiSilicon Hi6421v600 PMIC voltage regulator support"
+ depends on MFD_HI6421_SPMI && OF
+ depends on REGULATOR
+ 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.
diff --git a/drivers/staging/hikey9xx/Makefile b/drivers/staging/hikey9xx/Makefile
new file mode 100644
index 000000000000..1924fadac952
--- /dev/null
+++ b/drivers/staging/hikey9xx/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_PHY_HI3670_USB) += phy-hi3670-usb3.o
+
+obj-$(CONFIG_SPMI_HISI3670) += hisi-spmi-controller.o
+obj-$(CONFIG_MFD_HI6421_SPMI) += hi6421-spmi-pmic.o
+obj-$(CONFIG_REGULATOR_HI6421V600) += hi6421v600-regulator.o
diff --git a/drivers/staging/hikey9xx/TODO b/drivers/staging/hikey9xx/TODO
new file mode 100644
index 000000000000..65e7996a3066
--- /dev/null
+++ b/drivers/staging/hikey9xx/TODO
@@ -0,0 +1,5 @@
+ToDo list:
+
+- Port other drivers needed by Hikey 960/970;
+- Test drivers on Hikey 960;
+- Validate device tree bindings.
diff --git a/drivers/staging/hikey9xx/hi6421-spmi-pmic.c b/drivers/staging/hikey9xx/hi6421-spmi-pmic.c
new file mode 100644
index 000000000000..64b30d263c8d
--- /dev/null
+++ b/drivers/staging/hikey9xx/hi6421-spmi-pmic.c
@@ -0,0 +1,342 @@
+// 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/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/hi6421-spmi-pmic.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+
+/* 8-bit register offset in PMIC */
+#define HISI_MASK_STATE 0xff
+
+#define HISI_IRQ_ARRAY 2
+#define HISI_IRQ_NUM (HISI_IRQ_ARRAY * 8)
+
+#define SOC_PMIC_IRQ_MASK_0_ADDR 0x0202
+#define SOC_PMIC_IRQ0_ADDR 0x0212
+
+#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_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.
+ */
+int hi6421_spmi_pmic_read(struct hi6421_spmi_pmic *pmic, int reg)
+{
+ struct spmi_device *pdev;
+ u8 read_value = 0;
+ u32 ret;
+
+ pdev = to_spmi_device(pmic->dev);
+ if (!pdev) {
+ pr_err("%s: pdev get failed!\n", __func__);
+ return -ENODEV;
+ }
+
+ ret = spmi_ext_register_readl(pdev, reg, &read_value, 1);
+ if (ret) {
+ pr_err("%s: spmi_ext_register_readl failed!\n", __func__);
+ return ret;
+ }
+ return read_value;
+}
+EXPORT_SYMBOL(hi6421_spmi_pmic_read);
+
+int hi6421_spmi_pmic_write(struct hi6421_spmi_pmic *pmic, int reg, u32 val)
+{
+ struct spmi_device *pdev;
+ u32 ret;
+
+ pdev = to_spmi_device(pmic->dev);
+ if (!pdev) {
+ pr_err("%s: pdev get failed!\n", __func__);
+ return -ENODEV;
+ }
+
+ ret = spmi_ext_register_writel(pdev, reg, (unsigned char *)&val, 1);
+ if (ret)
+ pr_err("%s: spmi_ext_register_writel failed!\n", __func__);
+
+ return ret;
+}
+EXPORT_SYMBOL(hi6421_spmi_pmic_write);
+
+int hi6421_spmi_pmic_rmw(struct hi6421_spmi_pmic *pmic, int reg,
+ u32 mask, u32 bits)
+{
+ unsigned long flags;
+ u32 data;
+ int ret;
+
+ spin_lock_irqsave(&pmic->lock, flags);
+ data = hi6421_spmi_pmic_read(pmic, reg) & ~mask;
+ data |= mask & bits;
+ ret = hi6421_spmi_pmic_write(pmic, reg, data);
+ spin_unlock_irqrestore(&pmic->lock, flags);
+
+ return ret;
+}
+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 < HISI_IRQ_ARRAY; i++) {
+ pending = hi6421_spmi_pmic_read(pmic, (i + SOC_PMIC_IRQ0_ADDR));
+ pending &= HISI_MASK_FIELD;
+ if (pending != 0)
+ pr_debug("pending[%d]=0x%lx\n\r", i, pending);
+
+ hi6421_spmi_pmic_write(pmic, (i + SOC_PMIC_IRQ0_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 += SOC_PMIC_IRQ_MASK_0_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 += SOC_PMIC_IRQ_MASK_0_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 void hi6421_spmi_pmic_irq_prc(struct hi6421_spmi_pmic *pmic)
+{
+ int i, pending;
+
+ for (i = 0 ; i < HISI_IRQ_ARRAY; i++)
+ hi6421_spmi_pmic_write(pmic, SOC_PMIC_IRQ_MASK_0_ADDR + i,
+ HISI_MASK_STATE);
+
+ for (i = 0 ; i < HISI_IRQ_ARRAY; i++) {
+ pending = hi6421_spmi_pmic_read(pmic, SOC_PMIC_IRQ0_ADDR + i);
+
+ pr_debug("PMU IRQ address value:irq[0x%x] = 0x%x\n",
+ SOC_PMIC_IRQ0_ADDR + i, pending);
+ hi6421_spmi_pmic_write(pmic, SOC_PMIC_IRQ0_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;
+ unsigned int virq;
+ int ret, i;
+
+ pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
+ if (!pmic)
+ return -ENOMEM;
+
+ spin_lock_init(&pmic->lock);
+
+ pmic->dev = dev;
+
+ pmic->gpio = of_get_gpio(np, 0);
+ if (pmic->gpio < 0)
+ return pmic->gpio;
+
+ if (!gpio_is_valid(pmic->gpio))
+ return -EINVAL;
+
+ ret = devm_gpio_request_one(dev, 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);
+
+ hi6421_spmi_pmic_irq_prc(pmic);
+
+ pmic->irqs = devm_kzalloc(dev, HISI_IRQ_NUM * sizeof(int), GFP_KERNEL);
+ if (!pmic->irqs)
+ goto irq_malloc;
+
+ pmic->domain = irq_domain_add_simple(np, HISI_IRQ_NUM, 0,
+ &hi6421_spmi_domain_ops, pmic);
+ if (!pmic->domain) {
+ dev_err(dev, "failed irq domain add simple!\n");
+ ret = -ENODEV;
+ goto irq_malloc;
+ }
+
+ for (i = 0; i < HISI_IRQ_NUM; i++) {
+ virq = irq_create_mapping(pmic->domain, i);
+ if (!virq) {
+ dev_err(dev, "Failed mapping hwirq\n");
+ ret = -ENOSPC;
+ goto irq_malloc;
+ }
+ pmic->irqs[i] = virq;
+ dev_dbg(dev, "%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 IRQ: error %d\n", ret);
+ goto irq_malloc;
+ }
+
+ 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)
+ return 0;
+
+ dev_err(dev, "Failed to add child devices: %d\n", ret);
+
+irq_malloc:
+ free_irq(pmic->irq, pmic);
+
+ 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);
+}
+
+static const struct of_device_id pmic_spmi_id_table[] = {
+ { .compatible = "hisilicon,hi6421-spmi" },
+ { }
+};
+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/staging/hikey9xx/hi6421v600-regulator.c b/drivers/staging/hikey9xx/hi6421v600-regulator.c
new file mode 100644
index 000000000000..614b03c9ddfb
--- /dev/null
+++ b/drivers/staging/hikey9xx/hi6421v600-regulator.c
@@ -0,0 +1,478 @@
+// 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.xu@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/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/mfd/hi6421-spmi-pmic.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+#include <linux/time.h>
+#include <linux/uaccess.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)
+{
+ struct hi6421v600_regulator *sreg = rdev_get_drvdata(rdev);
+ struct hi6421_spmi_pmic *pmic = sreg->pmic;
+ u32 reg_val;
+
+ 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;
+
+ 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;
+ unsigned int mode;
+ u32 reg_val;
+
+ 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))
+ return REGULATOR_MODE_NORMAL;
+
+ 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\n");
+ 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\n",
+ rdesc->vsel_reg, rdesc->vsel_mask);
+
+ return 0;
+}
+
+static const 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,
+};
+
+static int hi6421_spmi_regulator_probe_ldo(struct platform_device *pdev,
+ struct device_node *np,
+ struct hi6421_spmi_pmic *pmic)
+{
+ struct regulation_constraints *constraint;
+ struct regulator_init_data *initdata;
+ struct regulator_config config = { };
+ struct hi6421v600_regulator *sreg;
+ struct device *dev = &pdev->dev;
+ struct regulator_desc *rdesc;
+ struct regulator_dev *rdev;
+ const char *supplyname;
+ int ret;
+
+ initdata = of_get_regulator_init_data(dev, np, NULL);
+ if (!initdata) {
+ dev_err(dev, "failed to get regulator data\n");
+ return -EINVAL;
+ }
+
+ sreg = devm_kzalloc(dev, 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)
+ return ret;
+
+ /* 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);
+ return PTR_ERR(rdev);
+ }
+
+ 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);
+
+ return 0;
+}
+
+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;
+
+ /*
+ * 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);
+
+ 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/staging/hikey9xx/hisi-spmi-controller.c b/drivers/staging/hikey9xx/hisi-spmi-controller.c
new file mode 100644
index 000000000000..f831c43f4783
--- /dev/null
+++ b/drivers/staging/hikey9xx/hisi-spmi-controller.c
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+
+/*
+ * 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
+
+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 timeout = SPMI_CONTROLLER_TIMEOUT_US;
+ u32 status, offset;
+
+ offset = SPMI_APB_SPMI_STATUS_BASE_ADDR;
+ offset += SPMI_CHANNEL_OFFSET * ctrl_dev->channel + SPMI_SLAVE_OFFSET * sid;
+
+ do {
+ 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);
+ } while (timeout--);
+
+ 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 slave_id, u16 slave_addr, u8 *__buf, size_t bc)
+{
+ struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev);
+ u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel;
+ unsigned long flags;
+ u8 *buf = __buf;
+ u32 cmd, data;
+ int rc;
+ u8 op_code, i;
+
+ if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) {
+ dev_err(&ctrl->dev,
+ "spmi_controller supports 1..%d bytes per trans, but:%zu requested\n",
+ SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
+ return -EINVAL;
+ }
+
+ switch (opc) {
+ case SPMI_CMD_READ:
+ op_code = SPMI_CMD_REG_READ;
+ break;
+ case SPMI_CMD_EXT_READ:
+ op_code = SPMI_CMD_EXT_REG_READ;
+ break;
+ case SPMI_CMD_EXT_READL:
+ op_code = SPMI_CMD_EXT_REG_READ_L;
+ break;
+ default:
+ dev_err(&ctrl->dev, "invalid read cmd 0x%x\n", 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) |
+ ((slave_id & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) | /* slvid */
+ ((slave_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, slave_id, slave_addr);
+ if (rc)
+ goto done;
+
+ for (i = 0; bc > i * SPMI_PER_DATAREG_BYTE; i++) {
+ data = readl(spmi_controller->base + chnl_ofst +
+ SPMI_SLAVE_OFFSET * slave_id +
+ 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);
+ }
+ }
+
+done:
+ spin_unlock_irqrestore(&spmi_controller->lock, flags);
+ if (rc)
+ dev_err(&ctrl->dev,
+ "spmi read wait timeout op:0x%x slave_id:%d slave_addr:0x%x bc:%zu\n",
+ opc, slave_id, slave_addr, bc + 1);
+ else
+ dev_dbg(&ctrl->dev, "%s: id:%d slave_addr:0x%x, read value: %*ph\n",
+ __func__, slave_id, slave_addr, (int)bc, __buf);
+
+ return rc;
+}
+
+static int spmi_write_cmd(struct spmi_controller *ctrl,
+ u8 opc, u8 slave_id, u16 slave_addr, const u8 *__buf, size_t bc)
+{
+ struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev);
+ u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel;
+ const u8 *buf = __buf;
+ unsigned long flags;
+ u32 cmd, data;
+ int rc;
+ u8 op_code, i;
+
+ if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) {
+ dev_err(&ctrl->dev,
+ "spmi_controller supports 1..%d bytes per trans, but:%zu requested\n",
+ SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
+ return -EINVAL;
+ }
+
+ switch (opc) {
+ case SPMI_CMD_WRITE:
+ op_code = SPMI_CMD_REG_WRITE;
+ break;
+ case SPMI_CMD_EXT_WRITE:
+ op_code = SPMI_CMD_EXT_REG_WRITE;
+ break;
+ case SPMI_CMD_EXT_WRITEL:
+ op_code = SPMI_CMD_EXT_REG_WRITE_L;
+ break;
+ default:
+ dev_err(&ctrl->dev, "invalid write cmd 0x%x\n", 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) |
+ ((slave_id & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) |
+ ((slave_addr & 0xffff) << SPMI_APB_SPMI_CMD_ADDR_OFFSET);
+
+ /* Write data to FIFOs */
+ spin_lock_irqsave(&spmi_controller->lock, flags);
+
+ for (i = 0; bc > i * SPMI_PER_DATAREG_BYTE; i++) {
+ 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);
+ }
+
+ /* 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, slave_id,
+ slave_addr);
+ spin_unlock_irqrestore(&spmi_controller->lock, flags);
+
+ if (rc)
+ dev_err(&ctrl->dev, "spmi write wait timeout op:0x%x slave_id:%d slave_addr:0x%x bc:%zu\n",
+ opc, slave_id, slave_addr, bc);
+ else
+ dev_dbg(&ctrl->dev, "%s: id:%d slave_addr:0x%x, wrote value: %*ph\n",
+ __func__, slave_id, slave_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;
+
+ 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;
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!iores) {
+ dev_err(&pdev->dev, "can not get resource!\n");
+ return -EINVAL;
+ }
+
+ spmi_controller->base = devm_ioremap(&pdev->dev, iores->start,
+ resource_size(iores));
+ if (!spmi_controller->base) {
+ dev_err(&pdev->dev, "can not remap base addr!\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ 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)
+ dev_err(&pdev->dev, "spmi_add_controller failed with error %d!\n", ret);
+
+ return ret;
+}
+
+static int spmi_del_controller(struct platform_device *pdev)
+{
+ struct spmi_controller *ctrl = platform_get_drvdata(pdev);
+
+ spmi_controller_remove(ctrl);
+ kfree(ctrl);
+ return 0;
+}
+
+static const struct of_device_id spmi_controller_match_table[] = {
+ {
+ .compatible = "hisilicon,kirin970-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 = "hisi_spmi_controller",
+ .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_controller");
diff --git a/drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml b/drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml
new file mode 100644
index 000000000000..80e74c261e05
--- /dev/null
+++ b/drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml
@@ -0,0 +1,159 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/hisilicon,hi6421-spmi-pmic.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: HiSilicon 6421v600 SPMI PMIC
+
+maintainers:
+ - Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
+
+description: |
+ HiSilicon 6421v600 should be connected inside a MIPI System Power Management
+ (SPMI) bus. It provides 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
+ drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml.
+
+properties:
+ $nodename:
+ pattern: "pmic@[0-9a-f]"
+
+ compatible:
+ const: hisilicon,hi6421v600-spmi
+
+ reg:
+ maxItems: 1
+
+ '#interrupt-cells':
+ const: 2
+
+ interrupt-controller:
+ description:
+ Identify that the PMIC is capable of behaving as an interrupt controller.
+
+ gpios:
+ maxItems: 1
+
+ regulators:
+ type: object
+
+ properties:
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ patternProperties:
+ '^ldo[0-9]+@[0-9a-f]$':
+ type: object
+
+ $ref: "/schemas/regulator/regulator.yaml#"
+
+ properties:
+ reg:
+ description: Enable register.
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ 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
+ - regulators
+
+examples:
+ - |
+ /* pmic properties */
+
+ pmic: pmic@0 {
+ compatible = "hisilicon,hi6421-spmi";
+ reg = <0 0>;
+
+ #interrupt-cells = <2>;
+ interrupt-controller;
+ gpios = <&gpio28 0 0>;
+
+ 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>;
+ };
+ };
+ };
diff --git a/drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml b/drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml
new file mode 100644
index 000000000000..f2a56fa4e78e
--- /dev/null
+++ b/drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml
@@ -0,0 +1,62 @@
+# 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+huawei@kernel.org>
+
+description: |
+ The HiSilicon SPMI BUS controller is found on some Kirin-based designs.
+ It is a MIPI System Power Management (SPMI) controller.
+
+ The PMIC part is provided by
+ drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml.
+
+properties:
+ $nodename:
+ pattern: "spmi@[0-9a-f]"
+
+ compatible:
+ const: hisilicon,kirin970-spmi-controller
+
+ reg:
+ maxItems: 1
+
+ spmi-channel:
+ description: |
+ number of the Kirin 970 SPMI channel where the SPMI devices are connected.
+
+required:
+ - compatible
+ - reg
+ - spmi-channel
+
+patternProperties:
+ "^pmic@[0-9a-f]$":
+ description: |
+ PMIC properties, which are specific to the used SPMI PMIC device(s).
+ When used in combination with HiSilicon 6421v600, the properties
+ are documented at
+ drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml.
+
+examples:
+ - |
+ bus {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ spmi: spmi@fff24000 {
+ compatible = "hisilicon,kirin970-spmi-controller";
+ status = "ok";
+ reg = <0x0 0xfff24000 0x0 0x1000>;
+ spmi-channel = <2>;
+
+ pmic@0 {
+ /* pmic properties */
+ };
+ };
+ };
diff --git a/drivers/staging/hikey9xx/phy-hi3670-usb3.c b/drivers/staging/hikey9xx/phy-hi3670-usb3.c
new file mode 100644
index 000000000000..4fc013911a78
--- /dev/null
+++ b/drivers/staging/hikey9xx/phy-hi3670-usb3.c
@@ -0,0 +1,671 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Phy provider for USB 3.1 controller on HiSilicon Kirin970 platform
+ *
+ * Copyright (C) 2017-2020 Hilisicon Electronics Co., Ltd.
+ * http://www.huawei.com
+ *
+ * Authors: Yu Chen <chenyu56@huawei.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define SCTRL_SCDEEPSLEEPED (0x0)
+#define USB_CLK_SELECTED BIT(20)
+
+#define PERI_CRG_PEREN0 (0x00)
+#define PERI_CRG_PERDIS0 (0x04)
+#define PERI_CRG_PEREN4 (0x40)
+#define PERI_CRG_PERDIS4 (0x44)
+#define PERI_CRG_PERRSTEN4 (0x90)
+#define PERI_CRG_PERRSTDIS4 (0x94)
+#define PERI_CRG_ISODIS (0x148)
+#define PERI_CRG_PEREN6 (0x410)
+#define PERI_CRG_PERDIS6 (0x414)
+
+#define USB_REFCLK_ISO_EN BIT(25)
+
+#define GT_CLK_USB2PHY_REF BIT(19)
+
+#define PCTRL_PERI_CTRL3 (0x10)
+#define PCTRL_PERI_CTRL3_MSK_START (16)
+#define USB_TCXO_EN BIT(1)
+
+#define PCTRL_PERI_CTRL24 (0x64)
+#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25)
+
+#define USB3OTG_CTRL0 (0x00)
+#define USB3OTG_CTRL3 (0x0C)
+#define USB3OTG_CTRL4 (0x10)
+#define USB3OTG_CTRL5 (0x14)
+#define USB3OTG_CTRL7 (0x1C)
+#define USB_MISC_CFG50 (0x50)
+#define USB_MISC_CFG54 (0x54)
+#define USB_MISC_CFG58 (0x58)
+#define USB_MISC_CFG5C (0x5C)
+#define USB_MISC_CFGA0 (0xA0)
+#define TCA_CLK_RST (0x200)
+#define TCA_INTR_EN (0x204)
+#define TCA_INTR_STS (0x208)
+#define TCA_GCFG (0x210)
+#define TCA_TCPC (0x214)
+#define TCA_SYSMODE_CFG (0x218)
+#define TCA_VBUS_CTRL (0x240)
+
+#define CTRL0_USB3_VBUSVLD BIT(7)
+#define CTRL0_USB3_VBUSVLD_SEL BIT(6)
+
+#define CTRL3_USB2_VBUSVLDEXT0 BIT(6)
+#define CTRL3_USB2_VBUSVLDEXTSEL0 BIT(5)
+
+#define CTRL5_USB2_SIDDQ BIT(0)
+
+#define CTRL7_USB2_REFCLKSEL_MASK (3 << 3)
+#define CTRL7_USB2_REFCLKSEL_ABB (3 << 3)
+#define CTRL7_USB2_REFCLKSEL_PAD (2 << 3)
+
+#define CFG50_USB3_PHY_TEST_POWERDOWN BIT(23)
+
+#define CFG54_USB31PHY_CR_ADDR_MASK (0xFFFF)
+#define CFG54_USB31PHY_CR_ADDR_SHIFT (16)
+#define CFG54_USB3PHY_REF_USE_PAD BIT(12)
+#define CFG54_PHY0_PMA_PWR_STABLE BIT(11)
+#define CFG54_PHY0_PCS_PWR_STABLE BIT(9)
+#define CFG54_USB31PHY_CR_ACK BIT(7)
+#define CFG54_USB31PHY_CR_WR_EN BIT(5)
+#define CFG54_USB31PHY_CR_SEL BIT(4)
+#define CFG54_USB31PHY_CR_RD_EN BIT(3)
+#define CFG54_USB31PHY_CR_CLK BIT(2)
+#define CFG54_USB3_PHY0_ANA_PWR_EN BIT(1)
+
+#define CFG58_USB31PHY_CR_DATA_MASK (0xFFFF)
+#define CFG58_USB31PHY_CR_DATA_RD_START (16)
+
+#define CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN BIT(1)
+
+#define CFGA0_VAUX_RESET BIT(9)
+#define CFGA0_USB31C_RESET BIT(8)
+#define CFGA0_USB2PHY_REFCLK_SELECT BIT(4)
+#define CFGA0_USB3PHY_RESET BIT(1)
+#define CFGA0_USB2PHY_POR BIT(0)
+
+#define INTR_EN_XA_TIMEOUT_EVT_EN BIT(1)
+#define INTR_EN_XA_ACK_EVT_EN BIT(0)
+
+#define CLK_RST_TCA_REF_CLK_EN BIT(1)
+#define CLK_RST_SUSPEND_CLK_EN BIT(0)
+
+#define GCFG_ROLE_HSTDEV BIT(4)
+#define GCFG_OP_MODE (3 << 0)
+#define GCFG_OP_MODE_CTRL_SYNC_MODE BIT(0)
+
+#define TCPC_VALID BIT(4)
+#define TCPC_LOW_POWER_EN BIT(3)
+#define TCPC_MUX_CONTROL_MASK (3 << 0)
+#define TCPC_MUX_CONTROL_USB31 BIT(0)
+
+#define SYSMODE_CFG_TYPEC_DISABLE BIT(3)
+
+#define VBUS_CTRL_POWERPRESENT_OVERRD (3 << 2)
+#define VBUS_CTRL_VBUSVALID_OVERRD (3 << 0)
+
+#define KIRIN970_USB_DEFAULT_PHY_PARAM (0xFDFEE4)
+#define KIRIN970_USB_DEFAULT_PHY_VBOOST (0x5)
+
+#define TX_VBOOST_LVL_REG (0xf)
+#define TX_VBOOST_LVL_START (6)
+#define TX_VBOOST_LVL_ENABLE BIT(9)
+
+struct hi3670_priv {
+ struct device *dev;
+ struct regmap *peri_crg;
+ struct regmap *pctrl;
+ struct regmap *sctrl;
+ struct regmap *usb31misc;
+
+ u32 eye_diagram_param;
+ u32 tx_vboost_lvl;
+
+ u32 peri_crg_offset;
+ u32 pctrl_offset;
+ u32 usb31misc_offset;
+};
+
+static int hi3670_phy_cr_clk(struct regmap *usb31misc)
+{
+ int ret;
+
+ /* Clock up */
+ ret = regmap_update_bits(usb31misc, USB_MISC_CFG54,
+ CFG54_USB31PHY_CR_CLK, CFG54_USB31PHY_CR_CLK);
+ if (ret)
+ return ret;
+
+ /* Clock down */
+ ret = regmap_update_bits(usb31misc, USB_MISC_CFG54,
+ CFG54_USB31PHY_CR_CLK, 0);
+
+ return ret;
+}
+
+static int hi3670_phy_cr_set_sel(struct regmap *usb31misc)
+{
+ return regmap_update_bits(usb31misc, USB_MISC_CFG54,
+ CFG54_USB31PHY_CR_SEL, CFG54_USB31PHY_CR_SEL);
+}
+
+static int hi3670_phy_cr_start(struct regmap *usb31misc, int direction)
+{
+ int ret;
+
+ if (direction)
+ ret = regmap_update_bits(usb31misc, USB_MISC_CFG54,
+ CFG54_USB31PHY_CR_WR_EN,
+ CFG54_USB31PHY_CR_WR_EN);
+ else
+ ret = regmap_update_bits(usb31misc, USB_MISC_CFG54,
+ CFG54_USB31PHY_CR_RD_EN,
+ CFG54_USB31PHY_CR_RD_EN);
+
+ if (ret)
+ return ret;
+
+ ret = hi3670_phy_cr_clk(usb31misc);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(usb31misc, USB_MISC_CFG54,
+ CFG54_USB31PHY_CR_RD_EN | CFG54_USB31PHY_CR_WR_EN, 0);
+
+ return ret;
+}
+
+static int hi3670_phy_cr_wait_ack(struct regmap *usb31misc)
+{
+ u32 reg;
+ int retry = 100000;
+ int ret;
+
+ while (retry-- > 0) {
+ ret = regmap_read(usb31misc, USB_MISC_CFG54, &reg);
+ if (ret)
+ return ret;
+ if ((reg & CFG54_USB31PHY_CR_ACK) == CFG54_USB31PHY_CR_ACK)
+ return 0;
+
+ ret = hi3670_phy_cr_clk(usb31misc);
+ if (ret)
+ return ret;
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int hi3670_phy_cr_set_addr(struct regmap *usb31misc, u32 addr)
+{
+ u32 reg;
+ int ret;
+
+ ret = regmap_read(usb31misc, USB_MISC_CFG54, &reg);
+ if (ret)
+ return ret;
+
+ reg &= ~(CFG54_USB31PHY_CR_ADDR_MASK << CFG54_USB31PHY_CR_ADDR_SHIFT);
+ reg |= ((addr & CFG54_USB31PHY_CR_ADDR_MASK) << CFG54_USB31PHY_CR_ADDR_SHIFT);
+ ret = regmap_write(usb31misc, USB_MISC_CFG54, reg);
+
+ return ret;
+}
+
+static int hi3670_phy_cr_read(struct regmap *usb31misc, u32 addr, u32 *val)
+{
+ int reg;
+ int i;
+ int ret;
+
+ for (i = 0; i < 100; i++) {
+ ret = hi3670_phy_cr_clk(usb31misc);
+ if (ret)
+ return ret;
+ }
+
+ ret = hi3670_phy_cr_set_sel(usb31misc);
+ if (ret)
+ return ret;
+
+ ret = hi3670_phy_cr_set_addr(usb31misc, addr);
+ if (ret)
+ return ret;
+
+ ret = hi3670_phy_cr_start(usb31misc, 0);
+ if (ret)
+ return ret;
+
+ ret = hi3670_phy_cr_wait_ack(usb31misc);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(usb31misc, USB_MISC_CFG58, &reg);
+ if (ret)
+ return ret;
+
+ *val = (reg >> CFG58_USB31PHY_CR_DATA_RD_START) &
+ CFG58_USB31PHY_CR_DATA_MASK;
+
+ return 0;
+}
+
+static int hi3670_phy_cr_write(struct regmap *usb31misc, u32 addr, u32 val)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < 100; i++) {
+ ret = hi3670_phy_cr_clk(usb31misc);
+ if (ret)
+ return ret;
+ }
+
+ ret = hi3670_phy_cr_set_sel(usb31misc);
+ if (ret)
+ return ret;
+
+ ret = hi3670_phy_cr_set_addr(usb31misc, addr);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(usb31misc, USB_MISC_CFG58,
+ val & CFG58_USB31PHY_CR_DATA_MASK);
+ if (ret)
+ return ret;
+
+ ret = hi3670_phy_cr_start(usb31misc, 1);
+ if (ret)
+ return ret;
+
+ ret = hi3670_phy_cr_wait_ack(usb31misc);
+
+ return ret;
+}
+
+static int hi3670_phy_set_params(struct hi3670_priv *priv)
+{
+ u32 reg;
+ int ret;
+ int retry = 3;
+
+ ret = regmap_write(priv->usb31misc, USB3OTG_CTRL4,
+ priv->eye_diagram_param);
+ if (ret) {
+ dev_err(priv->dev, "set USB3OTG_CTRL4 failed\n");
+ return ret;
+ }
+
+ while (retry-- > 0) {
+ ret = hi3670_phy_cr_read(priv->usb31misc,
+ TX_VBOOST_LVL_REG, &reg);
+ if (!ret)
+ break;
+
+ if (ret != -ETIMEDOUT) {
+ dev_err(priv->dev, "read TX_VBOOST_LVL_REG failed\n");
+ return ret;
+ }
+ }
+ if (ret)
+ return ret;
+
+ reg |= (TX_VBOOST_LVL_ENABLE | (priv->tx_vboost_lvl << TX_VBOOST_LVL_START));
+ ret = hi3670_phy_cr_write(priv->usb31misc, TX_VBOOST_LVL_REG, reg);
+ if (ret)
+ dev_err(priv->dev, "write TX_VBOOST_LVL_REG failed\n");
+
+ return ret;
+}
+
+static int hi3670_is_abbclk_seleted(struct hi3670_priv *priv)
+{
+ u32 reg;
+
+ if (!priv->sctrl) {
+ dev_err(priv->dev, "priv->sctrl is null!\n");
+ return 1;
+ }
+
+ if (regmap_read(priv->sctrl, SCTRL_SCDEEPSLEEPED, &reg)) {
+ dev_err(priv->dev, "SCTRL_SCDEEPSLEEPED read failed!\n");
+ return 1;
+ }
+
+ if ((reg & USB_CLK_SELECTED) == 0)
+ return 1;
+
+ return 0;
+}
+
+static int hi3670_config_phy_clock(struct hi3670_priv *priv)
+{
+ u32 val, mask;
+ int ret;
+
+ if (hi3670_is_abbclk_seleted(priv)) {
+ /* usb refclk iso disable */
+ ret = regmap_write(priv->peri_crg, PERI_CRG_ISODIS,
+ USB_REFCLK_ISO_EN);
+ if (ret)
+ goto out;
+
+ /* enable usb_tcxo_en */
+ ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3,
+ USB_TCXO_EN |
+ (USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START));
+
+ /* select usbphy clk from abb */
+ mask = SC_CLK_USB3PHY_3MUX1_SEL;
+ ret = regmap_update_bits(priv->pctrl,
+ PCTRL_PERI_CTRL24, mask, 0);
+ if (ret)
+ goto out;
+
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0,
+ CFGA0_USB2PHY_REFCLK_SELECT, 0);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(priv->usb31misc, USB3OTG_CTRL7, &val);
+ if (ret)
+ goto out;
+ val &= ~CTRL7_USB2_REFCLKSEL_MASK;
+ val |= CTRL7_USB2_REFCLKSEL_ABB;
+ ret = regmap_write(priv->usb31misc, USB3OTG_CTRL7, val);
+ if (ret)
+ goto out;
+
+ return 0;
+ }
+
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG54,
+ CFG54_USB3PHY_REF_USE_PAD,
+ CFG54_USB3PHY_REF_USE_PAD);
+ if (ret)
+ goto out;
+
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0,
+ CFGA0_USB2PHY_REFCLK_SELECT,
+ CFGA0_USB2PHY_REFCLK_SELECT);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(priv->usb31misc, USB3OTG_CTRL7, &val);
+ if (ret)
+ goto out;
+ val &= ~CTRL7_USB2_REFCLKSEL_MASK;
+ val |= CTRL7_USB2_REFCLKSEL_PAD;
+ ret = regmap_write(priv->usb31misc, USB3OTG_CTRL7, val);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(priv->peri_crg,
+ PERI_CRG_PEREN6, GT_CLK_USB2PHY_REF);
+ if (ret)
+ goto out;
+
+ return 0;
+out:
+ dev_err(priv->dev, "failed to config phy clock ret: %d\n", ret);
+ return ret;
+}
+
+static int hi3670_config_tca(struct hi3670_priv *priv)
+{
+ u32 val, mask;
+ int ret;
+
+ ret = regmap_write(priv->usb31misc, TCA_INTR_STS, 0xffff);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(priv->usb31misc, TCA_INTR_EN,
+ INTR_EN_XA_TIMEOUT_EVT_EN | INTR_EN_XA_ACK_EVT_EN);
+ if (ret)
+ goto out;
+
+ mask = CLK_RST_TCA_REF_CLK_EN | CLK_RST_SUSPEND_CLK_EN;
+ ret = regmap_update_bits(priv->usb31misc, TCA_CLK_RST, mask, 0);
+ if (ret)
+ goto out;
+
+ ret = regmap_update_bits(priv->usb31misc, TCA_GCFG,
+ GCFG_ROLE_HSTDEV | GCFG_OP_MODE,
+ GCFG_ROLE_HSTDEV | GCFG_OP_MODE_CTRL_SYNC_MODE);
+ if (ret)
+ goto out;
+
+ ret = regmap_update_bits(priv->usb31misc, TCA_SYSMODE_CFG,
+ SYSMODE_CFG_TYPEC_DISABLE, 0);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(priv->usb31misc, TCA_TCPC, &val);
+ if (ret)
+ goto out;
+ val &= ~(TCPC_VALID | TCPC_LOW_POWER_EN | TCPC_MUX_CONTROL_MASK);
+ val |= (TCPC_VALID | TCPC_MUX_CONTROL_USB31);
+ ret = regmap_write(priv->usb31misc, TCA_TCPC, val);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(priv->usb31misc, TCA_VBUS_CTRL,
+ VBUS_CTRL_POWERPRESENT_OVERRD | VBUS_CTRL_VBUSVALID_OVERRD);
+ if (ret)
+ goto out;
+
+ return 0;
+out:
+ dev_err(priv->dev, "failed to config phy clock ret: %d\n", ret);
+ return ret;
+}
+
+static int hi3670_phy_init(struct phy *phy)
+{
+ struct hi3670_priv *priv = phy_get_drvdata(phy);
+ u32 val;
+ int ret;
+
+ /* assert controller */
+ val = CFGA0_VAUX_RESET | CFGA0_USB31C_RESET |
+ CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR;
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, 0);
+ if (ret)
+ goto out;
+
+ ret = hi3670_config_phy_clock(priv);
+ if (ret)
+ goto out;
+
+ /* Exit from IDDQ mode */
+ ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL5,
+ CTRL5_USB2_SIDDQ, 0);
+ if (ret)
+ goto out;
+
+ /* Release USB31 PHY out of TestPowerDown mode */
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG50,
+ CFG50_USB3_PHY_TEST_POWERDOWN, 0);
+ if (ret)
+ goto out;
+
+ /* Deassert phy */
+ val = CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR;
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, val);
+ if (ret)
+ goto out;
+
+ usleep_range(100, 120);
+
+ /* Tell the PHY power is stable */
+ val = CFG54_USB3_PHY0_ANA_PWR_EN | CFG54_PHY0_PCS_PWR_STABLE |
+ CFG54_PHY0_PMA_PWR_STABLE;
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG54,
+ val, val);
+ if (ret)
+ goto out;
+
+ ret = hi3670_config_tca(priv);
+ if (ret)
+ goto out;
+
+ /* Enable SSC */
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG5C,
+ CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN,
+ CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN);
+ if (ret)
+ goto out;
+
+ /* Deassert controller */
+ val = CFGA0_VAUX_RESET | CFGA0_USB31C_RESET;
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, val);
+ if (ret)
+ goto out;
+
+ usleep_range(100, 120);
+
+ /* Set fake vbus valid signal */
+ val = CTRL0_USB3_VBUSVLD | CTRL0_USB3_VBUSVLD_SEL;
+ ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL0, val, val);
+ if (ret)
+ goto out;
+
+ val = CTRL3_USB2_VBUSVLDEXT0 | CTRL3_USB2_VBUSVLDEXTSEL0;
+ ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL3, val, val);
+ if (ret)
+ goto out;
+
+ usleep_range(100, 120);
+
+ ret = hi3670_phy_set_params(priv);
+ if (ret)
+ goto out;
+
+ return 0;
+out:
+ dev_err(priv->dev, "failed to init phy ret: %d\n", ret);
+ return ret;
+}
+
+static int hi3670_phy_exit(struct phy *phy)
+{
+ struct hi3670_priv *priv = phy_get_drvdata(phy);
+ u32 mask;
+ int ret;
+
+ /* Assert phy */
+ mask = CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR;
+ ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, mask, 0);
+ if (ret)
+ goto out;
+
+ if (hi3670_is_abbclk_seleted(priv)) {
+ /* disable usb_tcxo_en */
+ ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3,
+ USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START);
+ } else {
+ ret = regmap_write(priv->peri_crg, PERI_CRG_PERDIS6,
+ GT_CLK_USB2PHY_REF);
+ if (ret)
+ goto out;
+ }
+
+ return 0;
+out:
+ dev_err(priv->dev, "failed to exit phy ret: %d\n", ret);
+ return ret;
+}
+
+static struct phy_ops hi3670_phy_ops = {
+ .init = hi3670_phy_init,
+ .exit = hi3670_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static int hi3670_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct phy *phy;
+ struct hi3670_priv *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->peri_crg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "hisilicon,pericrg-syscon");
+ if (IS_ERR(priv->peri_crg)) {
+ dev_err(dev, "no hisilicon,pericrg-syscon\n");
+ return PTR_ERR(priv->peri_crg);
+ }
+
+ priv->pctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "hisilicon,pctrl-syscon");
+ if (IS_ERR(priv->pctrl)) {
+ dev_err(dev, "no hisilicon,pctrl-syscon\n");
+ return PTR_ERR(priv->pctrl);
+ }
+
+ priv->sctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "hisilicon,sctrl-syscon");
+ if (IS_ERR(priv->sctrl)) {
+ dev_err(dev, "no hisilicon,sctrl-syscon\n");
+ return PTR_ERR(priv->sctrl);
+ }
+
+ /* node of hi3670 phy is a sub-node of usb3_otg_bc */
+ priv->usb31misc = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(priv->usb31misc)) {
+ dev_err(dev, "no hisilicon,usb3-otg-bc-syscon\n");
+ return PTR_ERR(priv->usb31misc);
+ }
+
+ if (of_property_read_u32(dev->of_node, "hisilicon,eye-diagram-param",
+ &priv->eye_diagram_param))
+ priv->eye_diagram_param = KIRIN970_USB_DEFAULT_PHY_PARAM;
+
+ if (of_property_read_u32(dev->of_node, "hisilicon,tx-vboost-lvl",
+ &priv->tx_vboost_lvl))
+ priv->tx_vboost_lvl = KIRIN970_USB_DEFAULT_PHY_VBOOST;
+
+ phy = devm_phy_create(dev, NULL, &hi3670_phy_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, priv);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id hi3670_phy_of_match[] = {
+ { .compatible = "hisilicon,hi3670-usb-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, hi3670_phy_of_match);
+
+static struct platform_driver hi3670_phy_driver = {
+ .probe = hi3670_phy_probe,
+ .driver = {
+ .name = "hi3670-usb-phy",
+ .of_match_table = hi3670_phy_of_match,
+ }
+};
+module_platform_driver(hi3670_phy_driver);
+
+MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Hilisicon Kirin970 USB31 PHY Driver");
diff --git a/drivers/staging/hikey9xx/phy-hi3670-usb3.yaml b/drivers/staging/hikey9xx/phy-hi3670-usb3.yaml
new file mode 100644
index 000000000000..125a5d6546ae
--- /dev/null
+++ b/drivers/staging/hikey9xx/phy-hi3670-usb3.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/hisilicon,hi3670-usb3.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Hisilicon Kirin970 USB PHY
+
+maintainers:
+ - Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
+description: |+
+ Bindings for USB3 PHY on HiSilicon Kirin 970.
+
+properties:
+ compatible:
+ const: hisilicon,hi3670-usb-phy
+
+ "#phy-cells":
+ const: 0
+
+ hisilicon,pericrg-syscon:
+ $ref: '/schemas/types.yaml#/definitions/phandle'
+ description: phandle of syscon used to control iso refclk.
+
+ hisilicon,pctrl-syscon:
+ $ref: '/schemas/types.yaml#/definitions/phandle'
+ description: phandle of syscon used to control usb tcxo.
+
+ hisilicon,sctrl-syscon:
+ $ref: '/schemas/types.yaml#/definitions/phandle'
+ description: phandle of syscon used to control phy deep sleep.
+
+ hisilicon,eye-diagram-param:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Eye diagram for phy.
+
+ hisilicon,tx-vboost-lvl:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: TX level vboost for phy.
+
+required:
+ - compatible
+ - hisilicon,pericrg-syscon
+ - hisilicon,pctrl-syscon
+ - hisilicon,sctrl-syscon
+ - hisilicon,eye-diagram-param
+ - hisilicon,tx-vboost-lvl
+ - "#phy-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ bus {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ usb3_otg_bc: usb3_otg_bc@ff200000 {
+ compatible = "syscon", "simple-mfd";
+ reg = <0x0 0xff200000 0x0 0x1000>;
+
+ usb_phy {
+ compatible = "hisilicon,hi3670-usb-phy";
+ #phy-cells = <0>;
+ hisilicon,pericrg-syscon = <&crg_ctrl>;
+ hisilicon,pctrl-syscon = <&pctrl>;
+ hisilicon,sctrl-syscon = <&sctrl>;
+ hisilicon,eye-diagram-param = <0xfdfee4>;
+ hisilicon,tx-vboost-lvl = <0x5>;
+ };
+ };
+ };