[PATCH 02/33] spmi, regulator, mfd: add drivers for hikey970 SPMI PMIC

From: Mauro Carvalho Chehab
Date: Tue Aug 11 2020 - 11:43:05 EST


From: Mayulong <mayulong1@xxxxxxxxxx>

Add the SPMI controller code and the MFD/regulator drivers needed
to support the PMIC used at the Hikey970 board.

[mchehab+huawei@xxxxxxxxxx: keep just the sources, removing Kbuild changes]

Signed-off-by: Mayulong <mayulong1@xxxxxxxxxx>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@xxxxxxxxxx>
---
drivers/mfd/hisi_pmic_spmi.c | 759 ++++++++++++++++++++++++
drivers/regulator/hisi_regulator_spmi.c | 741 +++++++++++++++++++++++
drivers/spmi/hisi-spmi-controller.c | 390 ++++++++++++
include/linux/mfd/hisi_pmic.h | 165 ++++++
4 files changed, 2055 insertions(+)
create mode 100644 drivers/mfd/hisi_pmic_spmi.c
create mode 100644 drivers/regulator/hisi_regulator_spmi.c
create mode 100644 drivers/spmi/hisi-spmi-controller.c
create mode 100644 include/linux/mfd/hisi_pmic.h

diff --git a/drivers/mfd/hisi_pmic_spmi.c b/drivers/mfd/hisi_pmic_spmi.c
new file mode 100644
index 000000000000..6bb0bc4b203b
--- /dev/null
+++ b/drivers/mfd/hisi_pmic_spmi.c
@@ -0,0 +1,759 @@
+/*
+ * 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/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/hisi_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 PMIC_FPGA_FLAG 1
+
+/*define the first group interrupt register number*/
+#define HISI_PMIC_FIRST_GROUP_INT_NUM 2
+
+static struct bit_info g_pmic_vbus = {0};
+#ifndef BIT
+#define BIT(x) (0x1U << (x))
+#endif
+
+static struct hisi_pmic *g_pmic;
+static unsigned int g_extinterrupt_flag = 0;
+static struct of_device_id of_hisi_pmic_match_tbl[] = {
+ {
+ .compatible = "hisilicon-hisi-pmic-spmi",
+ },
+ { /* end */ }
+};
+
+/*
+ * 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 hisi_pmic_read(struct hisi_pmic *pmic, int reg)
+{
+ u32 ret;
+ u8 read_value = 0;
+ struct spmi_device *pdev;
+
+ if (NULL == g_pmic) {
+ pr_err(" g_pmic is NULL\n");
+ return 0;
+ }
+
+ pdev = to_spmi_device(g_pmic->dev);
+ if (NULL == pdev) {
+ pr_err("%s:pdev get failed!\n", __func__);
+ return 0;
+ }
+
+ ret = spmi_ext_register_readl(pdev, reg, (unsigned char*)&read_value, 1);/*lint !e734 !e732 */
+ if (ret) {
+ pr_err("%s:spmi_ext_register_readl failed!\n", __func__);
+ return ret;
+ }
+ return (u32)read_value;
+}
+EXPORT_SYMBOL(hisi_pmic_read);
+
+void hisi_pmic_write(struct hisi_pmic *pmic, int reg, u32 val)
+{
+ u32 ret;
+ struct spmi_device *pdev;
+
+ if (NULL == g_pmic) {
+ pr_err(" g_pmic is NULL\n");
+ return;
+ }
+
+ pdev = to_spmi_device(g_pmic->dev);
+ if (NULL == pdev) {
+ pr_err("%s:pdev get failed!\n", __func__);
+ return;
+ }
+
+ ret = spmi_ext_register_writel(pdev, reg, (unsigned char*)&val, 1);/*lint !e734 !e732 */
+ if (ret) {
+ pr_err("%s:spmi_ext_register_writel failed!\n", __func__);
+ return ;
+ }
+}
+EXPORT_SYMBOL(hisi_pmic_write);
+
+#ifdef CONFIG_HISI_DIEID
+u32 hisi_pmic_read_sub_pmu(u8 sid, int reg)
+{
+ u32 ret;
+ u8 read_value = 0;
+ struct spmi_device *pdev;
+
+ if(strstr(saved_command_line, "androidboot.swtype=factory"))
+ {
+ if (NULL == g_pmic) {
+ pr_err(" g_pmic is NULL\n");
+ return -1;/*lint !e570 */
+ }
+
+ pdev = to_spmi_device(g_pmic->dev);
+ if (NULL == pdev) {
+ pr_err("%s:pdev get failed!\n", __func__);
+ return -1;/*lint !e570 */
+ }
+
+ ret = spmi_ext_register_readl(pdev->ctrl, sid, reg, (unsigned char*)&read_value, 1);/*lint !e734 !e732 */
+ if (ret) {
+ pr_err("%s:spmi_ext_register_readl failed!\n", __func__);
+ return ret;
+ }
+ return (u32)read_value;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(hisi_pmic_read_sub_pmu);
+
+void hisi_pmic_write_sub_pmu(u8 sid, int reg, u32 val)
+{
+ u32 ret;
+ struct spmi_device *pdev;
+ if(strstr(saved_command_line, "androidboot.swtype=factory"))
+ {
+ if (NULL == g_pmic) {
+ pr_err(" g_pmic is NULL\n");
+ return;
+ }
+
+ pdev = to_spmi_device(g_pmic->dev);
+ if (NULL == pdev) {
+ pr_err("%s:pdev get failed!\n", __func__);
+ return;
+ }
+
+ ret = spmi_ext_register_writel(pdev->ctrl, sid, reg, (unsigned char*)&val, 1);/*lint !e734 !e732 */
+ if (ret) {
+ pr_err("%s:spmi_ext_register_writel failed!\n", __func__);
+ return ;
+ }
+ }
+
+ return ;
+}
+EXPORT_SYMBOL(hisi_pmic_write_sub_pmu);
+#endif
+
+void hisi_pmic_rmw(struct hisi_pmic *pmic, int reg,
+ u32 mask, u32 bits)
+{
+ u32 data;
+ unsigned long flags;
+
+ if (NULL == g_pmic) {
+ pr_err(" g_pmic is NULL\n");
+ return;
+ }
+
+ spin_lock_irqsave(&g_pmic->lock, flags);
+ data = hisi_pmic_read(pmic, reg) & ~mask;
+ data |= mask & bits;
+ hisi_pmic_write(pmic, reg, data);
+ spin_unlock_irqrestore(&g_pmic->lock, flags);
+}
+EXPORT_SYMBOL(hisi_pmic_rmw);
+
+unsigned int hisi_pmic_reg_read(int addr)
+{
+ return (unsigned int)hisi_pmic_read(g_pmic, addr);
+}
+EXPORT_SYMBOL(hisi_pmic_reg_read);
+
+void hisi_pmic_reg_write(int addr, int val)
+{
+ hisi_pmic_write(g_pmic, addr, val);
+}
+EXPORT_SYMBOL(hisi_pmic_reg_write);
+
+void hisi_pmic_reg_write_lock(int addr, int val)
+{
+ unsigned long flags;
+
+ if (NULL == g_pmic) {
+ pr_err(" g_pmic is NULL\n");
+ return;
+ }
+
+ spin_lock_irqsave(&g_pmic->lock, flags);
+ hisi_pmic_write(g_pmic, g_pmic->normal_lock.addr, g_pmic->normal_lock.val);
+ hisi_pmic_write(g_pmic, g_pmic->debug_lock.addr, g_pmic->debug_lock.val);
+ hisi_pmic_write(g_pmic, addr, val);
+ hisi_pmic_write(g_pmic, g_pmic->normal_lock.addr, 0);
+ hisi_pmic_write(g_pmic, g_pmic->debug_lock.addr, 0);
+ spin_unlock_irqrestore(&g_pmic->lock, flags);
+}
+
+int hisi_pmic_array_read(int addr, char *buff, unsigned int len)
+{
+ unsigned int i;
+
+ if ((len > 32) || (NULL == buff)) {
+ return -EINVAL;
+ }
+
+ /*
+ * Here is a bug in the pmu die.
+ * the coul driver will read 4 bytes,
+ * but the ssi bus only read 1 byte, and the pmu die
+ * will make sampling 1/10669us about vol cur,so the driver
+ * read the data is not the same sampling
+ */
+ for (i = 0; i < len; i++)
+ {
+ *(buff + i) = hisi_pmic_reg_read(addr+i);
+ }
+
+ return 0;
+}
+
+int hisi_pmic_array_write(int addr, char *buff, unsigned int len)
+{
+ unsigned int i;
+
+ if ((len > 32) || (NULL == buff)) {
+ return -EINVAL;
+ }
+
+ for (i = 0; i < len; i++)
+ {
+ hisi_pmic_reg_write(addr+i, *(buff + i));
+ }
+
+ return 0;
+}
+
+static irqreturn_t hisi_irq_handler(int irq, void *data)
+{
+ struct hisi_pmic *pmic = (struct hisi_pmic *)data;
+ unsigned long pending;
+ int i, offset;
+
+ for (i = 0; i < pmic->irqarray; i++) {
+ pending = hisi_pmic_reg_read((i + pmic->irq_addr.start_addr));
+ pending &= HISI_MASK_FIELD;
+ if (pending != 0) {
+ pr_info("pending[%d]=0x%lx\n\r", i, pending);
+ }
+
+ hisi_pmic_reg_write((i + pmic->irq_addr.start_addr), pending);
+
+ /*solve powerkey order*/
+ if ((HISI_IRQ_KEY_NUM == i) && ((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]);/*lint !e679 */
+ }
+ }
+
+ /*Handle the second group irq if analysis the second group irq from dtsi*/
+ if (1 == g_extinterrupt_flag){
+ for (i = 0; i < pmic->irqarray1; i++) {
+ pending = hisi_pmic_reg_read((i + pmic->irq_addr1.start_addr));
+ pending &= HISI_MASK_FIELD;
+ if (pending != 0) {
+ pr_info("pending[%d]=0x%lx\n\r", i, pending);
+ }
+
+ hisi_pmic_reg_write((i + pmic->irq_addr1.start_addr), pending);
+
+ if (pending) {
+ for_each_set_bit(offset, &pending, HISI_BITS)
+ generic_handle_irq(pmic->irqs[offset + (i+HISI_PMIC_FIRST_GROUP_INT_NUM) * HISI_BITS]);/*lint !e679 */
+ }
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void hisi_irq_mask(struct irq_data *d)
+{
+ struct hisi_pmic *pmic = irq_data_get_irq_chip_data(d);
+ u32 data, offset;
+ unsigned long flags;
+
+ if (NULL == g_pmic) {
+ pr_err(" g_pmic is NULL\n");
+ return;
+ }
+
+ offset = (irqd_to_hwirq(d) >> 3);
+ if (1==g_extinterrupt_flag){
+ if ( offset < HISI_PMIC_FIRST_GROUP_INT_NUM)
+ offset += pmic->irq_mask_addr.start_addr;
+ else/*Change addr when irq num larger than 16 because interrupt addr is nonsequence*/
+ offset = offset+(pmic->irq_mask_addr1.start_addr)-HISI_PMIC_FIRST_GROUP_INT_NUM;
+ }else{
+ offset += pmic->irq_mask_addr.start_addr;
+ }
+ spin_lock_irqsave(&g_pmic->lock, flags);
+ data = hisi_pmic_reg_read(offset);
+ data |= (1 << (irqd_to_hwirq(d) & 0x07));
+ hisi_pmic_reg_write(offset, data);
+ spin_unlock_irqrestore(&g_pmic->lock, flags);
+}
+
+static void hisi_irq_unmask(struct irq_data *d)
+{
+ struct hisi_pmic *pmic = irq_data_get_irq_chip_data(d);
+ u32 data, offset;
+ unsigned long flags;
+
+ if (NULL == g_pmic) {
+ pr_err(" g_pmic is NULL\n");
+ return;
+ }
+
+ offset = (irqd_to_hwirq(d) >> 3);
+ if (1==g_extinterrupt_flag){
+ if ( offset < HISI_PMIC_FIRST_GROUP_INT_NUM)
+ offset += pmic->irq_mask_addr.start_addr;
+ else
+ offset = offset+(pmic->irq_mask_addr1.start_addr)-HISI_PMIC_FIRST_GROUP_INT_NUM;
+ }else{
+ offset += pmic->irq_mask_addr.start_addr;
+ }
+ spin_lock_irqsave(&g_pmic->lock, flags);
+ data = hisi_pmic_reg_read(offset);
+ data &= ~(1 << (irqd_to_hwirq(d) & 0x07)); /*lint !e502 */
+ hisi_pmic_reg_write(offset, data);
+ spin_unlock_irqrestore(&g_pmic->lock, flags);
+}
+
+static struct irq_chip hisi_pmu_irqchip = {
+ .name = "hisi-irq",
+ .irq_mask = hisi_irq_mask,
+ .irq_unmask = hisi_irq_unmask,
+ .irq_disable = hisi_irq_mask,
+ .irq_enable = hisi_irq_unmask,
+};
+
+static int hisi_irq_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct hisi_pmic *pmic = d->host_data;
+
+ irq_set_chip_and_handler_name(virq, &hisi_pmu_irqchip,
+ handle_simple_irq, "hisi");
+ irq_set_chip_data(virq, pmic);
+ irq_set_irq_type(virq, IRQ_TYPE_NONE);
+
+ return 0;
+}
+
+static struct irq_domain_ops hisi_domain_ops = {
+ .map = hisi_irq_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+/*lint -e570 -e64*/
+static int get_pmic_device_tree_data(struct device_node *np, struct hisi_pmic *pmic)
+{
+ int ret = 0;
+
+ /*get pmic irq num*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-num",
+ &(pmic->irqnum), 1);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-irq-num property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ /*get pmic irq array number*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-array",
+ &(pmic->irqarray), 1);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-irq-array property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ /*SOC_PMIC_IRQ_MASK_0_ADDR*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-mask-addr",
+ (int *)&pmic->irq_mask_addr, 2);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-irq-mask-addr property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ /*SOC_PMIC_IRQ0_ADDR*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-addr",
+ (int *)&pmic->irq_addr, 2);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-irq-addr property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-vbus",
+ (u32 *)&g_pmic_vbus, 2);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-vbus property\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ /*pmic lock*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-lock",
+ (int *)&pmic->normal_lock, 2);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-lock property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ /*pmic debug lock*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-debug-lock",
+ (int *)&pmic->debug_lock, 2);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-debug-lock property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ return ret;
+}/*lint -restore*/
+
+
+/*lint -e570 -e64*/
+static int get_pmic_device_tree_data1(struct device_node *np, struct hisi_pmic *pmic)
+{
+ int ret = 0;
+
+ /*get pmic irq num*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-num1",
+ &(pmic->irqnum1), 1);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-irq-num1 property set\n");
+ ret = -ENODEV;
+ pmic->irqnum1 = 0;
+ return ret;
+ }
+
+ /*get pmic irq array number*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-array1",
+ &(pmic->irqarray1), 1);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-irq-array1 property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ /*SOC_PMIC_IRQ_MASK_0_ADDR*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-mask-addr1",
+ (int *)&pmic->irq_mask_addr1, 2);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-irq-mask-addr1 property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ /*SOC_PMIC_IRQ0_ADDR*/
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-addr1",
+ (int *)&pmic->irq_addr1, 2);
+ if (ret) {
+ pr_err("no hisilicon,hisi-pmic-irq-addr1 property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ g_extinterrupt_flag = 1;
+ return ret;
+}/*lint -restore*/
+
+int hisi_get_pmic_irq_byname(unsigned int pmic_irq_list)
+{
+ if ( NULL == g_pmic ) {
+ pr_err("[%s]g_pmic is NULL\n", __func__);
+ return -1;
+ }
+
+ if (pmic_irq_list > (unsigned int)g_pmic->irqnum) {
+ pr_err("[%s]input pmic irq number is error.\n", __func__);
+ return -1;
+ }
+ pr_info("%s:g_pmic->irqs[%d]=%d\n", __func__, pmic_irq_list, g_pmic->irqs[pmic_irq_list]);
+ return (int)g_pmic->irqs[pmic_irq_list];
+}
+EXPORT_SYMBOL(hisi_get_pmic_irq_byname);
+
+int hisi_pmic_get_vbus_status(void)
+{
+ if (0 == g_pmic_vbus.addr)
+ return -1;
+
+ if (hisi_pmic_reg_read(g_pmic_vbus.addr) & BIT(g_pmic_vbus.bit))
+ return 1;
+
+ return 0;
+}
+EXPORT_SYMBOL(hisi_pmic_get_vbus_status);
+
+static void hisi_pmic_irq_prc(struct hisi_pmic *pmic)
+{
+ int i;
+ for (i = 0 ; i < pmic->irq_mask_addr.array; i++) {
+ hisi_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 = hisi_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);
+ hisi_pmic_write(pmic, pmic->irq_addr.start_addr + i, HISI_MASK_STATE);
+ }
+
+}
+
+static void hisi_pmic_irq1_prc(struct hisi_pmic *pmic)
+{
+ int i;
+ if(1 == g_extinterrupt_flag){
+ for (i = 0 ; i < pmic->irq_mask_addr1.array; i++) {
+ hisi_pmic_write(pmic, pmic->irq_mask_addr1.start_addr + i, HISI_MASK_STATE);
+ }
+
+ for (i = 0 ; i < pmic->irq_addr1.array; i++) {
+ unsigned int pending1 = hisi_pmic_read(pmic, pmic->irq_addr1.start_addr + i);
+ pr_debug("PMU IRQ address1 value:irq[0x%x] = 0x%x\n", pmic->irq_addr1.start_addr + i, pending1);
+ hisi_pmic_write(pmic, pmic->irq_addr1.start_addr + i, HISI_MASK_STATE);
+ }
+ }
+}
+
+static int hisi_pmic_probe(struct spmi_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct hisi_pmic *pmic = NULL;
+ enum of_gpio_flags flags;
+ int ret = 0;
+ int i;
+ unsigned int fpga_flag = 0;
+ unsigned int virq;
+
+ pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
+ if (!pmic) {
+ dev_err(dev, "cannot allocate hisi_pmic device info\n");
+ 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;
+ }
+
+ /*get pmic dts the second group irq*/
+ ret = get_pmic_device_tree_data1(np, pmic);
+ if (ret) {
+ dev_err(&pdev->dev, "the platform don't support ext-interrupt.\n");
+ }
+
+ /* TODO: get and enable clk request */
+ spin_lock_init(&pmic->lock);
+
+ pmic->dev = dev;
+ g_pmic = pmic;
+ ret = of_property_read_u32_array(np, "hisilicon,pmic_fpga_flag", &fpga_flag, 1);
+ if (ret) {
+ pr_err("no hisilicon,pmic_fpga_flag property set\n");
+ }
+ if (PMIC_FPGA_FLAG == fpga_flag) {
+ goto after_irq_register;
+ }
+
+ 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 */
+ hisi_pmic_irq_prc(pmic);
+ /*clear && mask the new adding irq*/
+ hisi_pmic_irq1_prc(pmic);
+
+ pmic->irqnum += pmic->irqnum1;
+
+ pmic->irqs = (unsigned int *)devm_kmalloc(dev, pmic->irqnum * sizeof(int), GFP_KERNEL);
+ if (!pmic->irqs) {
+ pr_err("%s:Failed to alloc memory for pmic irq number!\n", __func__);
+ goto irq_malloc;
+ }
+ memset(pmic->irqs, 0, pmic->irqnum);
+
+ pmic->domain = irq_domain_add_simple(np, pmic->irqnum, 0,
+ &hisi_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, hisi_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;
+ }
+
+after_irq_register:
+ return 0;
+
+
+request_theaded_irq:
+irq_create_mapping:
+irq_domain:
+irq_malloc:
+ gpio_free(pmic->gpio);
+ g_pmic = NULL;
+ return ret;
+}
+
+static void hisi_pmic_remove(struct spmi_device *pdev)
+{
+
+ struct hisi_pmic *pmic = dev_get_drvdata(&pdev->dev);
+
+ free_irq(pmic->irq, pmic);
+ gpio_free(pmic->gpio);
+ devm_kfree(&pdev->dev, pmic);
+
+}
+static int hisi_pmic_suspend(struct device *dev, pm_message_t state)
+{
+ struct hisi_pmic *pmic = dev_get_drvdata(dev);
+
+ if (NULL == pmic) {
+ pr_err("%s:pmic is NULL\n", __func__);
+ return -ENOMEM;
+ }
+
+ pr_info("%s:+\n", __func__);
+ pr_info("%s:-\n", __func__);
+
+ return 0;
+}/*lint !e715 */
+
+static int hisi_pmic_resume(struct device *dev)
+{
+ struct hisi_pmic *pmic = dev_get_drvdata(dev);
+
+ if (NULL == pmic) {
+ pr_err("%s:pmic is NULL\n", __func__);
+ return -ENOMEM;
+ }
+
+ pr_info("%s:+\n", __func__);
+ pr_info("%s:-\n", __func__);
+
+ return 0;
+}
+
+MODULE_DEVICE_TABLE(spmi, pmic_spmi_id);
+static struct spmi_driver hisi_pmic_driver = {
+ .driver = {
+ .name = "hisi_pmic",
+ .owner = THIS_MODULE,
+ .of_match_table = of_hisi_pmic_match_tbl,
+ .suspend = hisi_pmic_suspend,
+ .resume = hisi_pmic_resume,
+ },
+ .probe = hisi_pmic_probe,
+ .remove = hisi_pmic_remove,
+};
+
+static int __init hisi_pmic_init(void)
+{
+ return spmi_driver_register(&hisi_pmic_driver);
+}
+
+static void __exit hisi_pmic_exit(void)
+{
+ spmi_driver_unregister(&hisi_pmic_driver);
+}
+
+
+subsys_initcall_sync(hisi_pmic_init);
+module_exit(hisi_pmic_exit);
+
+MODULE_DESCRIPTION("PMIC driver");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/regulator/hisi_regulator_spmi.c b/drivers/regulator/hisi_regulator_spmi.c
new file mode 100644
index 000000000000..941bfe32bf5b
--- /dev/null
+++ b/drivers/regulator/hisi_regulator_spmi.c
@@ -0,0 +1,741 @@
+/*
+ * Device driver for regulators in Hisi IC
+ *
+ * Copyright (c) 2013 Linaro Ltd.
+ * Copyright (c) 2011 Hisilicon.
+ *
+ * Guodong Xu <guodong.xu@xxxxxxxxxx>
+ *
+ * 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/hisi_pmic.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/version.h>
+#ifdef CONFIG_HISI_PMIC_DEBUG
+#include <linux/debugfs.h>
+#endif
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/spmi.h>
+
+#if 1
+#define BRAND_DEBUG(args...) pr_debug(args);
+#else
+#define BRAND_DEBUG(args...)
+#endif
+
+struct hisi_regulator_register_info {
+ u32 ctrl_reg;
+ u32 enable_mask;
+ u32 eco_mode_mask;
+ u32 vset_reg;
+ u32 vset_mask;
+};
+
+struct hisi_regulator {
+ const char *name;
+ struct hisi_regulator_register_info register_info;
+ struct timeval last_off_time;
+ u32 off_on_delay;
+ u32 eco_uA;
+ struct regulator_desc rdesc;
+ int (*dt_parse)(struct hisi_regulator *, struct spmi_device *);
+};
+
+static DEFINE_MUTEX(enable_mutex);
+struct timeval last_enabled;
+
+
+static inline struct hisi_pmic *rdev_to_pmic(struct regulator_dev *dev)
+{
+ /* regulator_dev parent to->
+ * hisi regulator platform device_dev parent to->
+ * hisi pmic platform device_dev
+ */
+ return dev_get_drvdata(rdev_get_dev(dev)->parent->parent);
+}
+
+/* helper function to ensure when it returns it is at least 'delay_us'
+ * microseconds after 'since'.
+ */
+static void ensured_time_after(struct timeval since, u32 delay_us)
+{
+ struct timeval now;
+ u64 elapsed_ns64, delay_ns64;
+ u32 actual_us32;
+
+ delay_ns64 = delay_us * NSEC_PER_USEC;
+ do_gettimeofday(&now);
+ elapsed_ns64 = timeval_to_ns(&now) - timeval_to_ns(&since);
+ if (delay_ns64 > elapsed_ns64) {
+ actual_us32 = ((u32)(delay_ns64 - elapsed_ns64) /
+ NSEC_PER_USEC);
+ if (actual_us32 >= 1000) {
+ mdelay(actual_us32 / 1000); /*lint !e647 */
+ udelay(actual_us32 % 1000);
+ } else if (actual_us32 > 0) {
+ udelay(actual_us32);
+ }
+ }
+ return;
+}
+
+static int hisi_regulator_is_enabled(struct regulator_dev *dev)
+{
+ u32 reg_val;
+ struct hisi_regulator *sreg = rdev_get_drvdata(dev);
+ struct hisi_pmic *pmic = rdev_to_pmic(dev);
+
+ reg_val = hisi_pmic_read(pmic, sreg->register_info.ctrl_reg);
+ BRAND_DEBUG("<[%s]: ctrl_reg=0x%x,enable_state=%d>\n", __func__, sreg->register_info.ctrl_reg,\
+ (reg_val & sreg->register_info.enable_mask));
+
+ return ((reg_val & sreg->register_info.enable_mask) != 0);
+}
+
+static int hisi_regulator_enable(struct regulator_dev *dev)
+{
+ struct hisi_regulator *sreg = rdev_get_drvdata(dev);
+ struct hisi_pmic *pmic = rdev_to_pmic(dev);
+
+ /* keep a distance of off_on_delay from last time disabled */
+ ensured_time_after(sreg->last_off_time, sreg->off_on_delay);
+
+ BRAND_DEBUG("<[%s]: off_on_delay=%dus>\n", __func__, sreg->off_on_delay);
+
+ /* cannot enable more than one regulator at one time */
+ mutex_lock(&enable_mutex);
+ ensured_time_after(last_enabled, HISI_REGS_ENA_PROTECT_TIME);
+
+ /* set enable register */
+ hisi_pmic_rmw(pmic, sreg->register_info.ctrl_reg,
+ sreg->register_info.enable_mask,
+ sreg->register_info.enable_mask);
+ BRAND_DEBUG("<[%s]: ctrl_reg=0x%x,enable_mask=0x%x>\n", __func__, sreg->register_info.ctrl_reg,\
+ sreg->register_info.enable_mask);
+
+ do_gettimeofday(&last_enabled);
+ mutex_unlock(&enable_mutex);
+
+ return 0;
+}
+
+static int hisi_regulator_disable(struct regulator_dev *dev)
+{
+ struct hisi_regulator *sreg = rdev_get_drvdata(dev);
+ struct hisi_pmic *pmic = rdev_to_pmic(dev);
+
+ /* set enable register to 0 */
+ hisi_pmic_rmw(pmic, sreg->register_info.ctrl_reg,
+ sreg->register_info.enable_mask, 0);
+
+ do_gettimeofday(&sreg->last_off_time);
+
+ return 0;
+}
+
+static int hisi_regulator_get_voltage(struct regulator_dev *dev)
+{
+ struct hisi_regulator *sreg = rdev_get_drvdata(dev);
+ struct hisi_pmic *pmic = rdev_to_pmic(dev);
+ u32 reg_val, selector;
+
+ /* get voltage selector */
+ reg_val = hisi_pmic_read(pmic, sreg->register_info.vset_reg);
+ BRAND_DEBUG("<[%s]: vset_reg=0x%x>\n", __func__, sreg->register_info.vset_reg);
+
+ selector = (reg_val & sreg->register_info.vset_mask) >>
+ (ffs(sreg->register_info.vset_mask) - 1);
+
+ return sreg->rdesc.ops->list_voltage(dev, selector);
+}
+
+static int hisi_regulator_set_voltage(struct regulator_dev *dev,
+ int min_uV, int max_uV, unsigned *selector)
+{
+ struct hisi_regulator *sreg = rdev_get_drvdata(dev);
+ struct hisi_pmic *pmic = rdev_to_pmic(dev);
+ u32 vsel;
+ int ret = 0;
+
+ for (vsel = 0; vsel < sreg->rdesc.n_voltages; vsel++) {
+ int uV = sreg->rdesc.volt_table[vsel];
+ /* Break at the first in-range value */
+ if (min_uV <= uV && uV <= max_uV)
+ break;
+ }
+
+ /* unlikely to happen. sanity test done by regulator core */
+ if (unlikely(vsel == sreg->rdesc.n_voltages))
+ return -EINVAL;
+
+ *selector = vsel;
+ /* set voltage selector */
+ hisi_pmic_rmw(pmic, sreg->register_info.vset_reg,
+ sreg->register_info.vset_mask,
+ vsel << (ffs(sreg->register_info.vset_mask) - 1));
+
+ BRAND_DEBUG("<[%s]: vset_reg=0x%x, vset_mask=0x%x, value=0x%x>\n", __func__,\
+ sreg->register_info.vset_reg,\
+ sreg->register_info.vset_mask,\
+ vsel << (ffs(sreg->register_info.vset_mask) - 1)\
+ );
+
+ return ret;
+}
+
+static unsigned int hisi_regulator_get_mode(struct regulator_dev *dev)
+{
+ struct hisi_regulator *sreg = rdev_get_drvdata(dev);
+ struct hisi_pmic *pmic = rdev_to_pmic(dev);
+ u32 reg_val;
+
+ reg_val = hisi_pmic_read(pmic, sreg->register_info.ctrl_reg);
+ BRAND_DEBUG("<[%s]: reg_val=%d, ctrl_reg=0x%x, eco_mode_mask=0x%x>\n", __func__, reg_val,\
+ sreg->register_info.ctrl_reg,\
+ sreg->register_info.eco_mode_mask\
+ );
+
+ if (reg_val & sreg->register_info.eco_mode_mask)
+ return REGULATOR_MODE_IDLE;
+ else
+ return REGULATOR_MODE_NORMAL;
+}
+
+static int hisi_regulator_set_mode(struct regulator_dev *dev,
+ unsigned int mode)
+{
+ struct hisi_regulator *sreg = rdev_get_drvdata(dev);
+ struct hisi_pmic *pmic = rdev_to_pmic(dev);
+ u32 eco_mode;
+
+ switch (mode) {
+ case REGULATOR_MODE_NORMAL:
+ eco_mode = HISI_ECO_MODE_DISABLE;
+ break;
+ case REGULATOR_MODE_IDLE:
+ eco_mode = HISI_ECO_MODE_ENABLE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set mode */
+ hisi_pmic_rmw(pmic, sreg->register_info.ctrl_reg,
+ sreg->register_info.eco_mode_mask,
+ eco_mode << (ffs(sreg->register_info.eco_mode_mask) - 1));
+
+ BRAND_DEBUG("<[%s]: ctrl_reg=0x%x, eco_mode_mask=0x%x, value=0x%x>\n", __func__,\
+ sreg->register_info.ctrl_reg,\
+ sreg->register_info.eco_mode_mask,\
+ eco_mode << (ffs(sreg->register_info.eco_mode_mask) - 1)\
+ );
+ return 0;
+}
+
+
+unsigned int hisi_regulator_get_optimum_mode(struct regulator_dev *dev,
+ int input_uV, int output_uV, int load_uA)
+{
+ struct hisi_regulator *sreg = rdev_get_drvdata(dev);
+
+ if ((load_uA == 0) || ((unsigned int)load_uA > sreg->eco_uA))
+ return REGULATOR_MODE_NORMAL;
+ else
+ return REGULATOR_MODE_IDLE;
+}
+
+static int hisi_dt_parse_common(struct hisi_regulator *sreg,
+ struct spmi_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct regulator_desc *rdesc = &sreg->rdesc;
+ unsigned int register_info[3] = {0};
+ int ret = 0;
+
+ /* parse .register_info.ctrl_reg */
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-ctrl",
+ register_info, 3);
+ if (ret) {
+ dev_err(dev, "no hisilicon,hisi-ctrl property set\n");
+ goto dt_parse_common_end;
+ }
+ sreg->register_info.ctrl_reg = register_info[0];
+ sreg->register_info.enable_mask = register_info[1];
+ sreg->register_info.eco_mode_mask = register_info[2];
+
+ /* parse .register_info.vset_reg */
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-vset",
+ register_info, 2);
+ if (ret) {
+ dev_err(dev, "no hisilicon,hisi-vset property set\n");
+ goto dt_parse_common_end;
+ }
+ sreg->register_info.vset_reg = register_info[0];
+ sreg->register_info.vset_mask = register_info[1];
+
+ /* parse .off-on-delay */
+ ret = of_property_read_u32(np, "hisilicon,hisi-off-on-delay-us",
+ &sreg->off_on_delay);
+ if (ret) {
+ dev_err(dev, "no hisilicon,hisi-off-on-delay-us property set\n");
+ goto dt_parse_common_end;
+ }
+
+ /* parse .enable_time */
+ ret = of_property_read_u32(np, "hisilicon,hisi-enable-time-us",
+ &rdesc->enable_time);
+ if (ret) {
+ dev_err(dev, "no hisilicon,hisi-enable-time-us property set\n");
+ goto dt_parse_common_end;
+ }
+
+ /* parse .eco_uA */
+ ret = of_property_read_u32(np, "hisilicon,hisi-eco-microamp",
+ &sreg->eco_uA);
+ if (ret) {
+ sreg->eco_uA = 0;
+ ret = 0;
+ }
+
+dt_parse_common_end:
+ return ret;
+}
+
+static int hisi_dt_parse_ldo(struct hisi_regulator *sreg,
+ struct spmi_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct regulator_desc *rdesc = &sreg->rdesc;
+ unsigned int *v_table;
+ int ret = 0;
+
+ /* parse .n_voltages, and .volt_table */
+ ret = of_property_read_u32(np, "hisilicon,hisi-n-voltages",
+ &rdesc->n_voltages);
+ if (ret) {
+ dev_err(dev, "no hisilicon,hisi-n-voltages property set\n");
+ goto dt_parse_ldo_end;
+ }
+
+ /* alloc space for .volt_table */
+ v_table = devm_kzalloc(dev, sizeof(unsigned int) * rdesc->n_voltages,
+ GFP_KERNEL);
+ if (unlikely(!v_table)) {
+ ret = -ENOMEM;
+ dev_err(dev, "no memory for .volt_table\n");
+ goto dt_parse_ldo_end;
+ }
+
+ ret = of_property_read_u32_array(np, "hisilicon,hisi-vset-table",
+ v_table, rdesc->n_voltages);
+ if (ret) {
+ dev_err(dev, "no hisilicon,hisi-vset-table property set\n");
+ goto dt_parse_ldo_end1;
+ }
+ rdesc->volt_table = v_table;
+
+ /* parse hisi regulator's dt common part */
+ ret = hisi_dt_parse_common(sreg, pdev);
+ if (ret) {
+ dev_err(dev, "failure in hisi_dt_parse_common\n");
+ goto dt_parse_ldo_end1;
+ }
+
+ return ret;
+
+dt_parse_ldo_end1:
+dt_parse_ldo_end:
+ return ret;
+}
+
+static struct regulator_ops hisi_ldo_rops = {
+ .is_enabled = hisi_regulator_is_enabled,
+ .enable = hisi_regulator_enable,
+ .disable = hisi_regulator_disable,
+ .list_voltage = regulator_list_voltage_table,
+ .get_voltage = hisi_regulator_get_voltage,
+ .set_voltage = hisi_regulator_set_voltage,
+ .get_mode = hisi_regulator_get_mode,
+ .set_mode = hisi_regulator_set_mode,
+ .get_optimum_mode = hisi_regulator_get_optimum_mode,
+};
+
+static const struct hisi_regulator hisi_regulator_ldo = {
+ .rdesc = {
+ .ops = &hisi_ldo_rops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+ .dt_parse = hisi_dt_parse_ldo,
+};
+
+static struct of_device_id of_hisi_regulator_match_tbl[] = {
+ {
+ .compatible = "hisilicon-hisi-ldo",
+ .data = &hisi_regulator_ldo,
+ },
+ { /* end */ }
+};
+
+#ifdef CONFIG_HISI_PMIC_DEBUG
+extern void get_current_regulator_dev(struct seq_file *s);
+extern void set_regulator_state(char *ldo_name, int value);
+extern void get_regulator_state(char *ldo_name);
+extern int set_regulator_voltage(char *ldo_name, unsigned int vol_value);
+
+u32 pmu_atoi(char *s)
+{
+ char *p = s;
+ char c;
+ u64 ret = 0;
+ if (s == NULL)
+ return 0;
+ while ((c = *p++) != '\0') {
+ if ('0' <= c && c <= '9') {
+ ret *= 10;
+ ret += (u64)((unsigned char)c - '0');
+ if (ret > U32_MAX)
+ return 0;
+ } else {
+ break;
+ }
+ }
+ return (u32)ret;
+}
+static int dbg_hisi_regulator_show(struct seq_file *s, void *data)
+{
+ seq_printf(s, "\n\r");
+ seq_printf(s, "%-13s %-15s %-15s %-15s %-15s\n\r",
+ "LDO_NAME", "ON/OFF", "Use_count", "Open_count", "Always_on");
+ seq_printf(s, "-----------------------------------------"
+ "-----------------------------------------------\n\r");
+ get_current_regulator_dev(s);
+ return 0;
+}
+
+static int dbg_hisi_regulator_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dbg_hisi_regulator_show, inode->i_private);
+}
+
+static const struct file_operations debug_regulator_state_fops = {
+ .open = dbg_hisi_regulator_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int dbg_control_regulator_show(struct seq_file *s, void *data)
+{
+ printk(" \n\r \
+ ---------------------------------------------------------------------------------\n\r \
+ |usage: |\n\r \
+ | S = state R = read V = voltage |\n\r \
+ | set ldo state and voltage |\n\r \
+ | get ldo state and current voltage |\n\r \
+ |example: |\n\r \
+ | echo S ldo16 0 > control_regulator :disable ldo16 |\n\r \
+ | echo S ldo16 1 > control_regulator :enable ldo16 |\n\r \
+ | echo R ldo16 > control_regulator :get ldo16 state and voltage |\n\r \
+ | echo V ldo16 xxx > control_regulator :set ldo16 voltage |\n\r \
+ ---------------------------------------------------------------------------------\n\r");
+ return 0;
+}
+static ssize_t dbg_control_regulator_set_value(struct file *filp, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ char tmp[128] = {0};
+ char ptr[128] = {0};
+ char *vol = NULL;
+ char num = 0;
+ unsigned int i;
+ int next_flag = 1;
+
+ if (count >= 128) {
+ pr_info("error! buffer size big than internal buffer\n");
+ return -EFAULT;
+ }
+
+ if (copy_from_user(tmp, buffer, count)) {
+ pr_info("error!\n");
+ return -EFAULT;
+ }
+
+ if (tmp[0] == 'R' || tmp[0] == 'r') {
+ for (i = 2; i < (count - 1); i++) {
+ ptr[i - 2] = tmp[i];
+ }
+ ptr[i - 2] = '\0';
+ get_regulator_state(ptr);
+ } else if (tmp[0] == 'S' || tmp[0] == 's') {
+ for (i = 2; i < (count - 1); i++) {
+ if (tmp[i] == ' ') {
+ next_flag = 0;
+ ptr[i - 2] = '\0';
+ continue;
+ }
+ if (next_flag) {
+ ptr[i - 2] = tmp[i];
+ } else {
+ num = tmp[i] - 48;
+ }
+ }
+ set_regulator_state(ptr, num);
+ } else if (tmp[0] == 'V' || tmp[0] == 'v') {
+ for (i = 2; i < (count - 1); i++) {
+ if (tmp[i] == ' ') {
+ next_flag = 0;
+ ptr[i - 2] = '\0';
+ continue;
+ }
+ if (next_flag) {
+ ptr[i - 2] = tmp[i];
+ } else {
+ vol = &tmp[i];
+ break;
+ }
+ }
+ set_regulator_voltage(ptr, pmu_atoi(vol));
+ }
+
+ *ppos += count;
+
+ return count;
+}
+
+static int dbg_control_regulator_open(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return single_open(file, dbg_control_regulator_show, &inode->i_private);
+}
+
+static const struct file_operations set_control_regulator_fops = {
+ .open = dbg_control_regulator_open,
+ .read = seq_read,
+ .write = dbg_control_regulator_set_value,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+#endif
+
+static int hisi_regulator_probe(struct spmi_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct regulator_desc *rdesc;
+ struct regulator_dev *rdev;
+ struct hisi_regulator *sreg = NULL;
+ struct regulator_init_data *initdata;
+ struct regulator_config config = { };
+ const struct of_device_id *match;
+ struct regulation_constraints *constraint;
+ const char *supplyname = NULL;
+#ifdef CONFIG_HISI_PMIC_DEBUG
+ struct dentry *d;
+ static int debugfs_flag;
+#endif
+ unsigned int temp_modes;
+
+ const struct hisi_regulator *template = NULL;
+ int ret = 0;
+ /* to check which type of regulator this is */
+ match = of_match_device(of_hisi_regulator_match_tbl, &pdev->dev);
+ if (NULL == match) {
+ pr_err("get hisi regulator fail!\n\r");
+ return -EINVAL;
+ }
+
+ template = match->data;
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 13, 0))
+ initdata = of_get_regulator_init_data(dev, np, NULL);
+#else
+ initdata = of_get_regulator_init_data(dev, np);
+#endif
+ if (NULL == initdata) {
+ pr_err("get regulator init data error !\n");
+ return -EINVAL;
+ }
+
+ /* hisi regulator supports two modes */
+ constraint = &initdata->constraints;
+
+ ret = of_property_read_u32_array(np, "hisilicon,valid-modes-mask",
+ &(constraint->valid_modes_mask), 1);
+ if (ret) {
+ pr_err("no hisilicon,valid-modes-mask property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+ ret = of_property_read_u32_array(np, "hisilicon,valid-idle-mask",
+ &temp_modes, 1);
+ if (ret) {
+ pr_err("no hisilicon,valid-modes-mask property set\n");
+ ret = -ENODEV;
+ return ret;
+ }
+ constraint->valid_ops_mask |= temp_modes;
+
+ sreg = kmemdup(template, sizeof(*sreg), GFP_KERNEL);
+ if (!sreg) {
+ pr_err("template kememdup is fail. \n");
+ return -ENOMEM;
+ }
+ sreg->name = initdata->constraints.name;
+ rdesc = &sreg->rdesc;
+ rdesc->name = sreg->name;
+ rdesc->min_uV = initdata->constraints.min_uV;
+ supplyname = of_get_property(np, "hisilicon,supply_name", NULL);
+ if (supplyname != NULL) {
+ initdata->supply_regulator = supplyname;
+ }
+
+ /* to parse device tree data for regulator specific */
+ ret = sreg->dt_parse(sreg, pdev);
+ if (ret) {
+ dev_err(dev, "device tree parameter parse error!\n");
+ goto hisi_probe_end;
+ }
+
+ 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 hisi_probe_end;
+ }
+
+ BRAND_DEBUG("[%s]:valid_modes_mask[0x%x], valid_ops_mask[0x%x]\n", rdesc->name,\
+ constraint->valid_modes_mask, constraint->valid_ops_mask);
+
+ dev_set_drvdata(dev, rdev);
+#ifdef CONFIG_HISI_PMIC_DEBUG
+ if (debugfs_flag == 0) {
+ d = debugfs_create_dir("hisi_regulator_debugfs", NULL);
+ if (!d) {
+ dev_err(dev, "failed to create hisi regulator debugfs dir !\n");
+ ret = -ENOMEM;
+ goto hisi_probe_fail;
+ }
+ (void) debugfs_create_file("regulator_state", S_IRUSR,
+ d, NULL, &debug_regulator_state_fops);
+
+ (void) debugfs_create_file("control_regulator", S_IRUSR,
+ d, NULL, &set_control_regulator_fops);
+ debugfs_flag = 1;
+ }
+#endif
+
+#ifdef CONFIG_HISI_PMIC_DEBUG
+hisi_probe_fail:
+ if (ret)
+ regulator_unregister(rdev);
+#endif
+hisi_probe_end:
+ if (ret)
+ kfree(sreg);
+ return ret;
+}
+
+static void hisi_regulator_remove(struct spmi_device *pdev)
+{
+ struct regulator_dev *rdev = dev_get_drvdata(&pdev->dev);
+ struct hisi_regulator *sreg = rdev_get_drvdata(rdev);
+
+ regulator_unregister(rdev);
+
+ /* TODO: should i worry about that? devm_kzalloc */
+ if (sreg->rdesc.volt_table)
+ devm_kfree(&pdev->dev, (unsigned int *)sreg->rdesc.volt_table);
+
+ kfree(sreg);
+}
+static int hisi_regulator_suspend(struct device *dev, pm_message_t state)
+{
+ struct hisi_regulator *hisi_regulator = dev_get_drvdata(dev);
+
+ if (NULL == hisi_regulator) {
+ pr_err("%s:regulator is NULL\n", __func__);
+ return -ENOMEM;
+ }
+
+ pr_info("%s:+\n", __func__);
+ pr_info("%s:-\n", __func__);
+
+ return 0;
+}/*lint !e715 */
+
+static int hisi_regulator_resume(struct device *dev)
+{
+ struct hisi_regulator *hisi_regulator = dev_get_drvdata(dev);
+
+ if (NULL == hisi_regulator) {
+ pr_err("%s:regulator is NULL\n", __func__);
+ return -ENOMEM;
+ }
+
+ pr_info("%s:+\n", __func__);
+ pr_info("%s:-\n", __func__);
+
+ return 0;
+}
+
+static struct spmi_driver hisi_pmic_driver = {
+ .driver = {
+ .name = "hisi_regulator",
+ .owner = THIS_MODULE,
+ .of_match_table = of_hisi_regulator_match_tbl,
+ .suspend = hisi_regulator_suspend,
+ .resume = hisi_regulator_resume,
+ },
+ .probe = hisi_regulator_probe,
+ .remove = hisi_regulator_remove,
+};
+
+static int __init hisi_regulator_init(void)
+{
+ return spmi_driver_register(&hisi_pmic_driver);
+}
+
+static void __exit hisi_regulator_exit(void)
+{
+ spmi_driver_unregister(&hisi_pmic_driver);
+}
+
+fs_initcall(hisi_regulator_init);
+module_exit(hisi_regulator_exit);
+
+MODULE_DESCRIPTION("Hisi regulator driver");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/spmi/hisi-spmi-controller.c b/drivers/spmi/hisi-spmi-controller.c
new file mode 100644
index 000000000000..987526c8b49f
--- /dev/null
+++ b/drivers/spmi/hisi-spmi-controller.c
@@ -0,0 +1,390 @@
+
+#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>
+#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
+/*lint -e750 -esym(750,*)*/
+#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
+/*lint +e750 -esym(750,*)*/
+
+#define SPMI_PER_DATAREG_BYTE 4
+/*
+ * SPMI cmd register
+ */
+#define SPMI_APB_SPMI_CMD_EN (1 << 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
+
+#define Tranverse32(X) ((((u32)(X) & 0xff000000) >> 24) | \
+ (((u32)(X) & 0x00ff0000) >> 8) | \
+ (((u32)(X) & 0x0000ff00) << 8) | \
+ (((u32)(X) & 0x000000ff) << 24))
+
+/* Command Opcodes */
+/*lint -e749 -esym(749,*)*/
+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,
+};
+/*lint +e749 -esym(749,*)*/
+
+/*
+ * SPMI status register
+ */
+#define SPMI_APB_TRANS_DONE (1 << 0)
+#define SPMI_APB_TRANS_FAIL (1 << 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)
+
+#define SPMI_WRITEL( dev, reg, addr ) \
+ do { \
+ writel( ( reg ), ( addr ) ); \
+ } while (0)
+
+#define SPMI_READL( dev, reg, addr ) \
+ do { \
+ reg = readl( addr ); \
+ } while (0)
+
+/*
+ * @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 spmi_controller_dev *ctrl_dev,
+ void __iomem *base, u8 sid, u16 addr)
+{
+ u32 status = 0;
+ u32 timeout = SPMI_CONTROLLER_TIMEOUT_US;
+ u32 offset = SPMI_APB_SPMI_STATUS_BASE_ADDR + SPMI_CHANNEL_OFFSET * ctrl_dev->channel
+ + SPMI_SLAVE_OFFSET * sid;
+
+ while (timeout--) {
+ SPMI_READL(ctrl_dev->dev, status, base + offset);/*lint !e732 */
+
+ if (status & SPMI_APB_TRANS_DONE) {
+ if (status & SPMI_APB_TRANS_FAIL) {
+ dev_err(ctrl_dev->dev,
+ "%s: transaction failed (0x%x)\n",
+ __func__, status);
+ return -EIO;
+ }
+ return 0;
+ }
+ udelay(1);/*lint !e778 !e774 !e747*/
+ }
+
+ dev_err(ctrl_dev->dev,
+ "%s: timeout, status 0x%x\n",
+ __func__, status);
+ return -ETIMEDOUT;/*lint !e438*/
+}/*lint !e715 !e529*/
+
+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;
+ 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(spmi_controller->dev
+ , "spmi_controller supports 1..%d bytes per trans, but:%ld requested"
+ , SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
+ return -EINVAL;
+ }
+
+ /* Check the opcode */
+ if (SPMI_CMD_READ == opc)
+ op_code = SPMI_CMD_REG_READ;
+ else if (SPMI_CMD_EXT_READ == opc)
+ op_code = SPMI_CMD_EXT_REG_READ;
+ else if (SPMI_CMD_EXT_READL == opc)
+ op_code = SPMI_CMD_EXT_REG_READ_L;
+ else {
+ dev_err(spmi_controller->dev, "invalid read cmd 0x%x", opc);
+ return -EINVAL;
+ }
+
+ cmd = SPMI_APB_SPMI_CMD_EN |/*lint !e648 !e701 */ /* cmd_en */
+ (op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) |/*lint !e648 !e701 */ /* cmd_type */
+ ((bc-1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) |/*lint !e648 !e701 */ /* byte_cnt */
+ ((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);/*lint !e550 */
+
+ SPMI_WRITEL(spmi_controller->dev, cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR);
+
+
+ rc = spmi_controller_wait_for_done(spmi_controller, spmi_controller->base, sid, addr);
+ if (rc)
+ goto done;
+
+ i = 0;
+ do {
+ SPMI_READL(spmi_controller->dev, data, spmi_controller->base + chnl_ofst + SPMI_SLAVE_OFFSET*sid + SPMI_APB_SPMI_RDATA0_BASE_ADDR + i*SPMI_PER_DATAREG_BYTE);/*lint !e732 */
+ data = Tranverse32(data);
+ if ((bc - i*SPMI_PER_DATAREG_BYTE ) >> 2) {/*lint !e702 */
+ memcpy(buf, &data, sizeof(data));
+ buf += sizeof(data);
+ } else {
+ memcpy(buf, &data, bc%SPMI_PER_DATAREG_BYTE);/*lint !e747 */
+ 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(spmi_controller->dev, "spmi read wait timeout op:0x%x sid:%d addr:0x%x bc:%ld\n",
+ opc, sid, addr, bc + 1);
+ return rc;
+}/*lint !e550 !e529*/
+
+/*lint -e438 -esym(438,*)*/
+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);
+ unsigned long flags;
+ u32 cmd;
+ u32 data = 0;
+ int rc;
+ u32 chnl_ofst = SPMI_CHANNEL_OFFSET*spmi_controller->channel;
+ u8 op_code, i;
+
+
+ if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) {
+ dev_err(spmi_controller->dev
+ , "spmi_controller supports 1..%d bytes per trans, but:%ld requested"
+ , SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
+ return -EINVAL;
+ }
+
+ /* Check the opcode */
+ if (SPMI_CMD_WRITE == opc)
+ op_code = SPMI_CMD_REG_WRITE;
+ else if (SPMI_CMD_EXT_WRITE == opc)
+ op_code = SPMI_CMD_EXT_REG_WRITE;
+ else if (SPMI_CMD_EXT_WRITEL == opc)
+ op_code = SPMI_CMD_EXT_REG_WRITE_L;
+ else {
+ dev_err(spmi_controller->dev, "invalid write cmd 0x%x", opc);
+ return -EINVAL;
+ }
+
+ cmd = SPMI_APB_SPMI_CMD_EN |/*lint !e648 !e701 */ /* cmd_en */
+ (op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) |/*lint !e648 !e701 */ /* cmd_type */
+ ((bc-1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) |/*lint !e648 !e701 */ /* byte_cnt */
+ ((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);/*lint !e550 */
+
+ i = 0;
+ do {
+ memset(&data, 0, sizeof(data));
+ if ((bc - i*SPMI_PER_DATAREG_BYTE ) >> 2) {/*lint !e702 */
+ memcpy(&data, buf, sizeof(data));
+ buf +=sizeof(data);
+ } else {
+ memcpy(&data, buf, bc%SPMI_PER_DATAREG_BYTE);/*lint !e747 */
+ buf +=(bc%SPMI_PER_DATAREG_BYTE);
+ }
+
+ data = Tranverse32(data);
+ SPMI_WRITEL(spmi_controller->dev, 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 */
+ SPMI_WRITEL(spmi_controller->dev, cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR);
+
+ rc = spmi_controller_wait_for_done(spmi_controller, spmi_controller->base, sid, addr);
+ spin_unlock_irqrestore(&spmi_controller->lock, flags);
+
+ if (rc)
+ dev_err(spmi_controller->dev, "spmi write wait timeout op:0x%x sid:%d addr:0x%x bc:%ld\n",
+ opc, sid, addr, bc);
+
+ return rc;
+}/*lint !e438 !e550 !e529*/
+/*lint +e438 -esym(438,*)*/
+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;
+
+ printk(KERN_INFO "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; /*lint !e429*/
+ }
+ 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; /*lint !e429*/
+ }
+
+ 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; /*lint !e429*/
+ }
+ dev_dbg(&pdev->dev, "spmi_add_controller base addr=0x%lx!\n", (long unsigned int)spmi_controller->base);/*lint !e774*/
+
+ /* Get properties from the device tree */
+ ret = of_property_read_u32(pdev->dev.of_node, "spmi-channel",
+ &spmi_controller->channel);/*lint !e838*/
+ if (ret) {
+ dev_err(&pdev->dev, "can not get chanel \n");
+ return -ENODEV; /*lint !e429*/
+ }
+
+ 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!\n");
+ goto err_add_controller;
+ }
+err_add_controller:
+ platform_set_drvdata(pdev, NULL);
+ return ret; /*lint !e429*/
+}
+
+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 struct of_device_id spmi_controller_match_table[] = {
+ { .compatible = "hisilicon,spmi-controller",
+ },/*lint !e785*/
+ {}/*lint !e785*/
+};
+
+static struct platform_driver spmi_controller_driver = {
+ .probe = spmi_controller_probe,
+ .remove = spmi_del_controller,
+ .driver = {
+ .name = SPMI_CONTROLLER_NAME,
+ .owner = THIS_MODULE,/*lint !e64*/
+ .of_match_table = spmi_controller_match_table,
+ },/*lint !e785*/
+};/*lint !e785*/
+/*lint -e528 -esym(528,*)*/
+static int __init spmi_controller_init(void)
+{
+ return platform_driver_register(&spmi_controller_driver);/*lint !e64*/
+}
+postcore_initcall(spmi_controller_init);
+
+static void __exit spmi_controller_exit(void)
+{
+ platform_driver_unregister(&spmi_controller_driver);
+}
+module_exit(spmi_controller_exit);
+/*lint -e753 -esym(753,*)*/
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0");/*lint !e785 !e64 !e528*/
+MODULE_ALIAS("platform:spmi_controlller");
+/*lint -e753 +esym(753,*)*/
+/*lint -e528 +esym(528,*)*/
+
diff --git a/include/linux/mfd/hisi_pmic.h b/include/linux/mfd/hisi_pmic.h
new file mode 100644
index 000000000000..939b36f617c1
--- /dev/null
+++ b/include/linux/mfd/hisi_pmic.h
@@ -0,0 +1,165 @@
+/*
+ * Header file for device driver Hi6421 PMIC
+ *
+ * Copyright (c) 2013 Linaro Ltd.
+ * Copyright (C) 2011 Hisilicon.
+ *
+ * Guodong Xu <guodong.xu@xxxxxxxxxx>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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)
+
+typedef int (*pmic_ocp_callback)(char *);
+extern int hisi_pmic_special_ocp_register(char *power_name, pmic_ocp_callback handler);
+
+struct irq_mask_info {
+ int start_addr;
+ int array;
+};
+
+struct irq_info {
+ int start_addr;
+ int array;
+};
+
+struct bit_info {
+ int addr;
+ int bit;
+};
+
+struct write_lock {
+ int addr;
+ int val;
+};
+
+struct hisi_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 irq_mask_info irq_mask_addr;
+ struct irq_info irq_addr;
+ int irqnum1;
+ int irqarray1;
+ struct irq_mask_info irq_mask_addr1;
+ struct irq_info irq_addr1;
+ struct write_lock normal_lock;
+ struct write_lock debug_lock;
+};
+
+/* 0:disable; 1:enable */
+unsigned int get_uv_mntn_status(void);
+void clear_uv_mntn_resered_reg_bit(void);
+void set_uv_mntn_resered_reg_bit(void);
+
+#if defined(CONFIG_HISI_PMIC) || defined(CONFIG_HISI_PMIC_PMU_SPMI)
+/* Register Access Helpers */
+u32 hisi_pmic_read(struct hisi_pmic *pmic, int reg);
+void hisi_pmic_write(struct hisi_pmic *pmic, int reg, u32 val);
+void hisi_pmic_rmw(struct hisi_pmic *pmic, int reg, u32 mask, u32 bits);
+unsigned int hisi_pmic_reg_read(int addr);
+void hisi_pmic_reg_write(int addr, int val);
+void hisi_pmic_reg_write_lock(int addr, int val);
+int hisi_pmic_array_read(int addr, char *buff, unsigned int len);
+int hisi_pmic_array_write(int addr, char *buff, unsigned int len);
+extern int hisi_get_pmic_irq_byname(unsigned int pmic_irq_list);
+extern int hisi_pmic_get_vbus_status(void);
+#if defined(CONFIG_HISI_DIEID)
+u32 hisi_pmic_read_sub_pmu(u8 sid ,int reg);
+void hisi_pmic_write_sub_pmu(u8 sid ,int reg, u32 val);
+#endif
+#else
+static inline u32 hisi_pmic_read(struct hisi_pmic *pmic, int reg) { return 0; }
+static inline void hisi_pmic_write(struct hisi_pmic *pmic, int reg, u32 val) {}
+static inline void hisi_pmic_rmw(struct hisi_pmic *pmic, int reg, u32 mask, u32 bits) {}
+static inline unsigned int hisi_pmic_reg_read(int addr) { return 0; }
+static inline void hisi_pmic_reg_write(int addr, int val) {}
+static inline void hisi_pmic_reg_write_lock(int addr, int val) {}
+static inline int hisi_pmic_array_read(int addr, char *buff, unsigned int len) { return 0; }
+static inline int hisi_pmic_array_write(int addr, char *buff, unsigned int len) { return 0; }
+static inline int hisi_get_pmic_irq_byname(unsigned int pmic_irq_list) { return -1; }
+static inline int hisi_pmic_get_vbus_status(void) { return 1; }
+static inline u32 hisi_pmic_read_sub_pmu(u8 sid ,int reg) { return 0; }
+static inline void hisi_pmic_write_sub_pmu(u8 sid ,int reg, u32 val) {}
+#endif
+
+#ifdef CONFIG_HISI_HI6421V500_PMU
+enum pmic_irq_list {
+ POR_D45MR = 0,
+ VBUS_CONNECT,
+ VBUS_DISCONNECT,
+ ALARMON_R,
+ HOLD_6S,
+ HOLD_1S,
+ POWERKEY_UP,
+ POWERKEY_DOWN,
+ OCP_SCP_R,
+ COUL_R,
+ VSYS_OV,
+ VSYS_UV,
+ VSYS_PWROFF_ABS,
+ VSYS_PWROFF_DEB,
+ THSD_OTMP140,
+ THSD_OTMP125,
+ HRESETN,
+ SIM0_HPD_R = 24,
+ SIM0_HPD_F,
+ SIM0_HPD_H,
+ SIM0_HPD_L,
+ SIM1_HPD_R,
+ SIM1_HPD_F,
+ SIM1_HPD_H,
+ SIM1_HPD_L,
+ PMIC_IRQ_LIST_MAX,
+};
+#else
+enum 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
+
+#ifdef CONFIG_HISI_SR_DEBUG
+extern void get_ip_regulator_state(void);
+#endif
+#endif /* __HISI_PMIC_H */
+
--
2.26.2