summaryrefslogtreecommitdiff
path: root/drivers/gpio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpio')
-rw-r--r--drivers/gpio/Kconfig19
-rw-r--r--drivers/gpio/Makefile3
-rw-r--r--drivers/gpio/gpio-msm-smp2p-test.c762
-rw-r--r--drivers/gpio/gpio-msm-smp2p.c835
-rw-r--r--drivers/gpio/gpiolib-acpi.c2
-rw-r--r--drivers/gpio/gpiolib.c9
-rw-r--r--drivers/gpio/qpnp-pin.c1714
7 files changed, 3341 insertions, 3 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 469dc378adeb..d4729fa59edb 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -338,6 +338,25 @@ config GPIO_PXA
help
Say yes here to support the PXA GPIO device
+config GPIO_QPNP_PIN
+ tristate "Qualcomm Technologies, Inc. QPNP GPIO support"
+ depends on SPMI
+ help
+ Say 'y' here to include support for the Qualcomm Technologies, Inc.
+ QPNP GPIO driver. This driver supports Device Tree and allows a
+ device_node to be registered as a gpio-controller. It does not handle
+ GPIO interrupts directly; they are handled via the SPMI arbiter
+ interrupt driver.
+
+config GPIO_QPNP_PIN_DEBUG
+ bool "Qualcomm Technologies, Inc. QPNP GPIO debug support"
+ depends on GPIO_QPNP_PIN && DEBUG_FS
+ help
+ Say 'y' here to include debug support for the Qualcomm Technologies,
+ Inc. QPNP GPIO driver. This provides a userspace debug interface to
+ get and set all of the supported features of PMIC GPIO and MPP pins
+ including those which are managed by the gpio framework.
+
config GPIO_RCAR
tristate "Renesas R-Car GPIO"
depends on ARCH_SHMOBILE || COMPILE_TEST
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 986dbd838cea..ac55dd34cfcb 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o
obj-$(CONFIG_GPIO_PCH) += gpio-pch.o
obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o
obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o
+obj-$(CONFIG_GPIO_QPNP_PIN) += qpnp-pin.o
obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o
obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o
@@ -119,3 +120,5 @@ obj-$(CONFIG_GPIO_XTENSA) += gpio-xtensa.o
obj-$(CONFIG_GPIO_ZEVIO) += gpio-zevio.o
obj-$(CONFIG_GPIO_ZYNQ) += gpio-zynq.o
obj-$(CONFIG_GPIO_ZX) += gpio-zx.o
+obj-$(CONFIG_MSM_SMP2P) += gpio-msm-smp2p.o
+obj-$(CONFIG_MSM_SMP2P_TEST) += gpio-msm-smp2p-test.o
diff --git a/drivers/gpio/gpio-msm-smp2p-test.c b/drivers/gpio/gpio-msm-smp2p-test.c
new file mode 100644
index 000000000000..5907513b43c0
--- /dev/null
+++ b/drivers/gpio/gpio-msm-smp2p-test.c
@@ -0,0 +1,762 @@
+/* drivers/gpio/gpio-msm-smp2p-test.c
+ *
+ * Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/gpio.h>
+#include <linux/debugfs.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/bitmap.h>
+#include "../soc/qcom/smp2p_private.h"
+#include "../soc/qcom/smp2p_test_common.h"
+
+/* Interrupt callback data */
+struct gpio_info {
+ int gpio_base_id;
+ int irq_base_id;
+
+ bool initialized;
+ struct completion cb_completion;
+ int cb_count;
+ DECLARE_BITMAP(triggered_irqs, SMP2P_BITS_PER_ENTRY);
+};
+
+/* GPIO Inbound/Outbound callback info */
+struct gpio_inout {
+ struct gpio_info in;
+ struct gpio_info out;
+};
+
+static struct gpio_inout gpio_info[SMP2P_NUM_PROCS];
+
+/**
+ * Init/reset the callback data.
+ *
+ * @info: Pointer to callback data
+ */
+static void cb_data_reset(struct gpio_info *info)
+{
+ int n;
+
+ if (!info)
+ return;
+
+ if (!info->initialized) {
+ init_completion(&info->cb_completion);
+ info->initialized = true;
+ }
+ info->cb_count = 0;
+
+ for (n = 0; n < SMP2P_BITS_PER_ENTRY; ++n)
+ clear_bit(n, info->triggered_irqs);
+
+ reinit_completion(&info->cb_completion);
+}
+
+static int smp2p_gpio_test_probe(struct platform_device *pdev)
+{
+ int id;
+ int cnt;
+ struct device_node *node = pdev->dev.of_node;
+ struct gpio_info *gpio_info_ptr = NULL;
+
+ /*
+ * NOTE: This does a string-lookup of the GPIO pin name and doesn't
+ * actually directly link to the SMP2P GPIO driver since all
+ * GPIO/Interrupt access must be through standard
+ * Linux GPIO / Interrupt APIs.
+ */
+ if (strcmp("qcom,smp2pgpio_test_smp2p_1_in", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_MODEM_PROC].in;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_1_out", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_MODEM_PROC].out;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_2_in", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_AUDIO_PROC].in;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_2_out", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_AUDIO_PROC].out;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_3_in", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_SENSOR_PROC].in;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_3_out", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_SENSOR_PROC].out;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_4_in", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_WIRELESS_PROC].in;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_4_out", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_WIRELESS_PROC].out;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_5_in", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_CDSP_PROC].in;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_5_out", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_CDSP_PROC].out;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_7_in", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_TZ_PROC].in;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_7_out", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_TZ_PROC].out;
+ } else if (strcmp("qcom,smp2pgpio_test_smp2p_15_in", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_REMOTE_MOCK_PROC].in;
+ } else if (
+ strcmp("qcom,smp2pgpio_test_smp2p_15_out", node->name) == 0) {
+ gpio_info_ptr = &gpio_info[SMP2P_REMOTE_MOCK_PROC].out;
+ } else {
+ pr_err("%s: unable to match device type '%s'\n",
+ __func__, node->name);
+ return -ENODEV;
+ }
+
+ /* retrieve the GPIO and interrupt ID's */
+ cnt = of_gpio_count(node);
+ if (cnt && gpio_info_ptr) {
+ /*
+ * Instead of looping through all 32-bits, we can just get the
+ * first pin to get the base IDs. This saves on the verbosity
+ * of the device tree nodes as well.
+ */
+ id = of_get_gpio(node, 0);
+ if (id == -EPROBE_DEFER)
+ return id;
+ gpio_info_ptr->gpio_base_id = id;
+ gpio_info_ptr->irq_base_id = gpio_to_irq(id);
+ }
+ return 0;
+}
+
+/*
+ * NOTE: Instead of match table and device driver, you may be able to just
+ * call of_find_compatible_node() in your init function.
+ */
+static struct of_device_id msm_smp2p_match_table[] = {
+ /* modem */
+ {.compatible = "qcom,smp2pgpio_test_smp2p_1_out", },
+ {.compatible = "qcom,smp2pgpio_test_smp2p_1_in", },
+
+ /* audio (adsp) */
+ {.compatible = "qcom,smp2pgpio_test_smp2p_2_out", },
+ {.compatible = "qcom,smp2pgpio_test_smp2p_2_in", },
+
+ /* sensor */
+ {.compatible = "qcom,smp2pgpio_test_smp2p_3_out", },
+ {.compatible = "qcom,smp2pgpio_test_smp2p_3_in", },
+
+ /* wcnss */
+ {.compatible = "qcom,smp2pgpio_test_smp2p_4_out", },
+ {.compatible = "qcom,smp2pgpio_test_smp2p_4_in", },
+
+ /* CDSP */
+ {.compatible = "qcom,smp2pgpio_test_smp2p_5_out", },
+ {.compatible = "qcom,smp2pgpio_test_smp2p_5_in", },
+
+ /* TZ */
+ {.compatible = "qcom,smp2pgpio_test_smp2p_7_out", },
+ {.compatible = "qcom,smp2pgpio_test_smp2p_7_in", },
+
+ /* mock loopback */
+ {.compatible = "qcom,smp2pgpio_test_smp2p_15_out", },
+ {.compatible = "qcom,smp2pgpio_test_smp2p_15_in", },
+ {},
+};
+
+static struct platform_driver smp2p_gpio_driver = {
+ .probe = smp2p_gpio_test_probe,
+ .driver = {
+ .name = "smp2pgpio_test",
+ .owner = THIS_MODULE,
+ .of_match_table = msm_smp2p_match_table,
+ },
+};
+
+/**
+ * smp2p_ut_local_gpio_out - Verify outbound functionality.
+ *
+ * @s: pointer to output file
+ */
+static void smp2p_ut_local_gpio_out(struct seq_file *s)
+{
+ int failed = 0;
+ struct gpio_info *cb_info = &gpio_info[SMP2P_REMOTE_MOCK_PROC].out;
+ int ret;
+ int id;
+ struct msm_smp2p_remote_mock *mock;
+
+ seq_printf(s, "Running %s\n", __func__);
+ do {
+ /* initialize mock edge */
+ ret = smp2p_reset_mock_edge();
+ UT_ASSERT_INT(ret, ==, 0);
+
+ mock = msm_smp2p_get_remote_mock();
+ UT_ASSERT_PTR(mock, !=, NULL);
+
+ mock->rx_interrupt_count = 0;
+ memset(&mock->remote_item, 0,
+ sizeof(struct smp2p_smem_item));
+ smp2p_init_header((struct smp2p_smem *)&mock->remote_item,
+ SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC,
+ 0, 1);
+ strlcpy(mock->remote_item.entries[0].name, "smp2p",
+ SMP2P_MAX_ENTRY_NAME);
+ SMP2P_SET_ENT_VALID(
+ mock->remote_item.header.valid_total_ent, 1);
+ msm_smp2p_set_remote_mock_exists(true);
+ mock->tx_interrupt();
+
+ /* open GPIO entry */
+ smp2p_gpio_open_test_entry("smp2p",
+ SMP2P_REMOTE_MOCK_PROC, true);
+
+ /* verify set/get functions */
+ UT_ASSERT_INT(0, <, cb_info->gpio_base_id);
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
+ int pin = cb_info->gpio_base_id + id;
+
+ mock->rx_interrupt_count = 0;
+ gpio_set_value(pin, 1);
+ UT_ASSERT_INT(1, ==, mock->rx_interrupt_count);
+ UT_ASSERT_INT(1, ==, gpio_get_value(pin));
+
+ gpio_set_value(pin, 0);
+ UT_ASSERT_INT(2, ==, mock->rx_interrupt_count);
+ UT_ASSERT_INT(0, ==, gpio_get_value(pin));
+ }
+ if (failed)
+ break;
+
+ seq_puts(s, "\tOK\n");
+ } while (0);
+
+ if (failed) {
+ pr_err("%s: Failed\n", __func__);
+ seq_puts(s, "\tFailed\n");
+ }
+
+ smp2p_gpio_open_test_entry("smp2p",
+ SMP2P_REMOTE_MOCK_PROC, false);
+}
+
+/**
+ * smp2p_gpio_irq - Interrupt handler for inbound entries.
+ *
+ * @irq: Virtual IRQ being triggered
+ * @data: Cookie data (struct gpio_info * in this case)
+ * @returns: Number of bytes written
+ */
+static irqreturn_t smp2p_gpio_irq(int irq, void *data)
+{
+ struct gpio_info *gpio_ptr = (struct gpio_info *)data;
+ int offset;
+
+ if (!gpio_ptr) {
+ pr_err("%s: gpio_ptr is NULL for irq %d\n", __func__, irq);
+ return IRQ_HANDLED;
+ }
+
+ offset = irq - gpio_ptr->irq_base_id;
+ if (offset >= 0 && offset < SMP2P_BITS_PER_ENTRY)
+ set_bit(offset, gpio_ptr->triggered_irqs);
+ else
+ pr_err("%s: invalid irq offset base %d; irq %d\n",
+ __func__, gpio_ptr->irq_base_id, irq);
+
+ ++gpio_ptr->cb_count;
+ complete(&gpio_ptr->cb_completion);
+ return IRQ_HANDLED;
+}
+
+/**
+ * smp2p_ut_local_gpio_in - Verify inbound functionality.
+ *
+ * @s: pointer to output file
+ */
+static void smp2p_ut_local_gpio_in(struct seq_file *s)
+{
+ int failed = 0;
+ struct gpio_info *cb_info = &gpio_info[SMP2P_REMOTE_MOCK_PROC].in;
+ int id;
+ int ret;
+ int virq;
+ struct msm_smp2p_remote_mock *mock;
+
+ seq_printf(s, "Running %s\n", __func__);
+
+ cb_data_reset(cb_info);
+ do {
+ /* initialize mock edge */
+ ret = smp2p_reset_mock_edge();
+ UT_ASSERT_INT(ret, ==, 0);
+
+ mock = msm_smp2p_get_remote_mock();
+ UT_ASSERT_PTR(mock, !=, NULL);
+
+ mock->rx_interrupt_count = 0;
+ memset(&mock->remote_item, 0,
+ sizeof(struct smp2p_smem_item));
+ smp2p_init_header((struct smp2p_smem *)&mock->remote_item,
+ SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC,
+ 0, 1);
+ strlcpy(mock->remote_item.entries[0].name, "smp2p",
+ SMP2P_MAX_ENTRY_NAME);
+ SMP2P_SET_ENT_VALID(
+ mock->remote_item.header.valid_total_ent, 1);
+ msm_smp2p_set_remote_mock_exists(true);
+ mock->tx_interrupt();
+
+ smp2p_gpio_open_test_entry("smp2p",
+ SMP2P_REMOTE_MOCK_PROC, true);
+
+ /* verify set/get functions locally */
+ UT_ASSERT_INT(0, <, cb_info->gpio_base_id);
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
+ int pin;
+ int current_value;
+
+ /* verify pin value cannot be set */
+ pin = cb_info->gpio_base_id + id;
+ current_value = gpio_get_value(pin);
+
+ gpio_set_value(pin, 0);
+ UT_ASSERT_INT(current_value, ==, gpio_get_value(pin));
+ gpio_set_value(pin, 1);
+ UT_ASSERT_INT(current_value, ==, gpio_get_value(pin));
+
+ /* verify no interrupts */
+ UT_ASSERT_INT(0, ==, cb_info->cb_count);
+ }
+ if (failed)
+ break;
+
+ /* register for interrupts */
+ UT_ASSERT_INT(0, <, cb_info->irq_base_id);
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
+ virq = cb_info->irq_base_id + id;
+ UT_ASSERT_PTR(NULL, !=, irq_to_desc(virq));
+ ret = request_irq(virq,
+ smp2p_gpio_irq, IRQF_TRIGGER_RISING,
+ "smp2p_test", cb_info);
+ UT_ASSERT_INT(0, ==, ret);
+ }
+ if (failed)
+ break;
+
+ /* verify both rising and falling edge interrupts */
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
+ virq = cb_info->irq_base_id + id;
+ irq_set_irq_type(virq, IRQ_TYPE_EDGE_BOTH);
+ cb_data_reset(cb_info);
+
+ /* verify rising-edge interrupt */
+ mock->remote_item.entries[0].entry = 1 << id;
+ mock->tx_interrupt();
+ UT_ASSERT_INT(cb_info->cb_count, ==, 1);
+ UT_ASSERT_INT(0, <,
+ test_bit(id, cb_info->triggered_irqs));
+ test_bit(id, cb_info->triggered_irqs);
+
+ /* verify falling-edge interrupt */
+ mock->remote_item.entries[0].entry = 0;
+ mock->tx_interrupt();
+ UT_ASSERT_INT(cb_info->cb_count, ==, 2);
+ UT_ASSERT_INT(0, <,
+ test_bit(id, cb_info->triggered_irqs));
+ }
+ if (failed)
+ break;
+
+ /* verify rising-edge interrupts */
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
+ virq = cb_info->irq_base_id + id;
+ irq_set_irq_type(virq, IRQ_TYPE_EDGE_RISING);
+ cb_data_reset(cb_info);
+
+ /* verify only rising-edge interrupt is triggered */
+ mock->remote_item.entries[0].entry = 1 << id;
+ mock->tx_interrupt();
+ UT_ASSERT_INT(cb_info->cb_count, ==, 1);
+ UT_ASSERT_INT(0, <,
+ test_bit(id, cb_info->triggered_irqs));
+ test_bit(id, cb_info->triggered_irqs);
+
+ mock->remote_item.entries[0].entry = 0;
+ mock->tx_interrupt();
+ UT_ASSERT_INT(cb_info->cb_count, ==, 1);
+ UT_ASSERT_INT(0, <,
+ test_bit(id, cb_info->triggered_irqs));
+ }
+ if (failed)
+ break;
+
+ /* verify falling-edge interrupts */
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
+ virq = cb_info->irq_base_id + id;
+ irq_set_irq_type(virq, IRQ_TYPE_EDGE_FALLING);
+ cb_data_reset(cb_info);
+
+ /* verify only rising-edge interrupt is triggered */
+ mock->remote_item.entries[0].entry = 1 << id;
+ mock->tx_interrupt();
+ UT_ASSERT_INT(cb_info->cb_count, ==, 0);
+ UT_ASSERT_INT(0, ==,
+ test_bit(id, cb_info->triggered_irqs));
+
+ mock->remote_item.entries[0].entry = 0;
+ mock->tx_interrupt();
+ UT_ASSERT_INT(cb_info->cb_count, ==, 1);
+ UT_ASSERT_INT(0, <,
+ test_bit(id, cb_info->triggered_irqs));
+ }
+ if (failed)
+ break;
+
+ seq_puts(s, "\tOK\n");
+ } while (0);
+
+ if (failed) {
+ pr_err("%s: Failed\n", __func__);
+ seq_puts(s, "\tFailed\n");
+ }
+
+ /* unregister for interrupts */
+ if (cb_info->irq_base_id) {
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY; ++id)
+ free_irq(cb_info->irq_base_id + id, cb_info);
+ }
+
+ smp2p_gpio_open_test_entry("smp2p",
+ SMP2P_REMOTE_MOCK_PROC, false);
+}
+
+/**
+ * smp2p_ut_local_gpio_in_update_open - Verify combined open/update.
+ *
+ * @s: pointer to output file
+ *
+ * If the remote side updates the SMP2P bits and sends before negotiation is
+ * complete, then the UPDATE event will have to be delayed until negotiation is
+ * complete. This should result in both the OPEN and UPDATE events coming in
+ * right after each other and the behavior should be transparent to the clients
+ * of SMP2P GPIO.
+ */
+static void smp2p_ut_local_gpio_in_update_open(struct seq_file *s)
+{
+ int failed = 0;
+ struct gpio_info *cb_info = &gpio_info[SMP2P_REMOTE_MOCK_PROC].in;
+ int id;
+ int ret;
+ int virq;
+ struct msm_smp2p_remote_mock *mock;
+
+ seq_printf(s, "Running %s\n", __func__);
+
+ cb_data_reset(cb_info);
+ do {
+ /* initialize mock edge */
+ ret = smp2p_reset_mock_edge();
+ UT_ASSERT_INT(ret, ==, 0);
+
+ mock = msm_smp2p_get_remote_mock();
+ UT_ASSERT_PTR(mock, !=, NULL);
+
+ mock->rx_interrupt_count = 0;
+ memset(&mock->remote_item, 0,
+ sizeof(struct smp2p_smem_item));
+ smp2p_init_header((struct smp2p_smem *)&mock->remote_item,
+ SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC,
+ 0, 1);
+ strlcpy(mock->remote_item.entries[0].name, "smp2p",
+ SMP2P_MAX_ENTRY_NAME);
+ SMP2P_SET_ENT_VALID(
+ mock->remote_item.header.valid_total_ent, 1);
+
+ /* register for interrupts */
+ smp2p_gpio_open_test_entry("smp2p",
+ SMP2P_REMOTE_MOCK_PROC, true);
+
+ UT_ASSERT_INT(0, <, cb_info->irq_base_id);
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
+ virq = cb_info->irq_base_id + id;
+ UT_ASSERT_PTR(NULL, !=, irq_to_desc(virq));
+ ret = request_irq(virq,
+ smp2p_gpio_irq, IRQ_TYPE_EDGE_BOTH,
+ "smp2p_test", cb_info);
+ UT_ASSERT_INT(0, ==, ret);
+ }
+ if (failed)
+ break;
+
+ /* update the state value and complete negotiation */
+ mock->remote_item.entries[0].entry = 0xDEADDEAD;
+ msm_smp2p_set_remote_mock_exists(true);
+ mock->tx_interrupt();
+
+ /* verify delayed state updates were processed */
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
+ virq = cb_info->irq_base_id + id;
+
+ UT_ASSERT_INT(cb_info->cb_count, >, 0);
+ if (0x1 & (0xDEADDEAD >> id)) {
+ /* rising edge should have been triggered */
+ if (!test_bit(id, cb_info->triggered_irqs)) {
+ seq_printf(s, "%s:%d bit %d clear, ",
+ __func__, __LINE__, id);
+ seq_puts(s, "expected set\n");
+ failed = 1;
+ break;
+ }
+ } else {
+ /* edge should not have been triggered */
+ if (test_bit(id, cb_info->triggered_irqs)) {
+ seq_printf(s, "%s:%d bit %d set, ",
+ __func__, __LINE__, id);
+ seq_puts(s, "expected clear\n");
+ failed = 1;
+ break;
+ }
+ }
+ }
+ if (failed)
+ break;
+
+ seq_puts(s, "\tOK\n");
+ } while (0);
+
+ if (failed) {
+ pr_err("%s: Failed\n", __func__);
+ seq_puts(s, "\tFailed\n");
+ }
+
+ /* unregister for interrupts */
+ if (cb_info->irq_base_id) {
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY; ++id)
+ free_irq(cb_info->irq_base_id + id, cb_info);
+ }
+
+ smp2p_gpio_open_test_entry("smp2p",
+ SMP2P_REMOTE_MOCK_PROC, false);
+}
+
+/**
+ * smp2p_gpio_write_bits - writes value to each GPIO pin specified in mask.
+ *
+ * @gpio: gpio test structure
+ * @mask: 1 = write gpio_value to this GPIO pin
+ * @gpio_value: value to write to GPIO pin
+ */
+static void smp2p_gpio_write_bits(struct gpio_info *gpio, uint32_t mask,
+ int gpio_value)
+{
+ int n;
+
+ for (n = 0; n < SMP2P_BITS_PER_ENTRY; ++n) {
+ if (mask & 0x1)
+ gpio_set_value(gpio->gpio_base_id + n, gpio_value);
+ mask >>= 1;
+ }
+}
+
+static void smp2p_gpio_set_bits(struct gpio_info *gpio, uint32_t mask)
+{
+ smp2p_gpio_write_bits(gpio, mask, 1);
+}
+
+static void smp2p_gpio_clr_bits(struct gpio_info *gpio, uint32_t mask)
+{
+ smp2p_gpio_write_bits(gpio, mask, 0);
+}
+
+/**
+ * smp2p_gpio_get_value - reads entire 32-bits of GPIO
+ *
+ * @gpio: gpio structure
+ * @returns: 32 bit value of GPIO pins
+ */
+static uint32_t smp2p_gpio_get_value(struct gpio_info *gpio)
+{
+ int n;
+ uint32_t value = 0;
+
+ for (n = 0; n < SMP2P_BITS_PER_ENTRY; ++n) {
+ if (gpio_get_value(gpio->gpio_base_id + n))
+ value |= 1 << n;
+ }
+ return value;
+}
+
+/**
+ * smp2p_ut_remote_inout_core - Verify inbound/outbound functionality.
+ *
+ * @s: pointer to output file
+ * @remote_pid: Remote processor to test
+ * @name: Name of the test for reporting
+ *
+ * This test verifies inbound/outbound functionality for the remote processor.
+ */
+static void smp2p_ut_remote_inout_core(struct seq_file *s, int remote_pid,
+ const char *name)
+{
+ int failed = 0;
+ uint32_t request;
+ uint32_t response;
+ struct gpio_info *cb_in;
+ struct gpio_info *cb_out;
+ int id;
+ int ret;
+
+ seq_printf(s, "Running %s for '%s' remote pid %d\n",
+ __func__, smp2p_pid_to_name(remote_pid), remote_pid);
+
+ cb_in = &gpio_info[remote_pid].in;
+ cb_out = &gpio_info[remote_pid].out;
+ cb_data_reset(cb_in);
+ cb_data_reset(cb_out);
+ do {
+ /* open test entries */
+ msm_smp2p_deinit_rmt_lpb_proc(remote_pid);
+ smp2p_gpio_open_test_entry("smp2p", remote_pid, true);
+
+ /* register for interrupts */
+ UT_ASSERT_INT(0, <, cb_in->gpio_base_id);
+ UT_ASSERT_INT(0, <, cb_in->irq_base_id);
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
+ int virq = cb_in->irq_base_id + id;
+ UT_ASSERT_PTR(NULL, !=, irq_to_desc(virq));
+ ret = request_irq(virq,
+ smp2p_gpio_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "smp2p_test", cb_in);
+ UT_ASSERT_INT(0, ==, ret);
+ }
+ if (failed)
+ break;
+
+ /* write echo of data value 0 */
+ UT_ASSERT_INT(0, <, cb_out->gpio_base_id);
+ request = 0x0;
+ SMP2P_SET_RMT_CMD_TYPE(request, 1);
+ SMP2P_SET_RMT_CMD(request, SMP2P_LB_CMD_ECHO);
+ SMP2P_SET_RMT_DATA(request, 0x0);
+
+ smp2p_gpio_set_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
+ smp2p_gpio_clr_bits(cb_out, ~SMP2P_RMT_IGNORE_MASK);
+ smp2p_gpio_set_bits(cb_out, request);
+
+ UT_ASSERT_INT(cb_in->cb_count, ==, 0);
+ smp2p_gpio_clr_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
+
+ /* verify response */
+ do {
+ /* wait for up to 32 changes */
+ if (wait_for_completion_timeout(
+ &cb_in->cb_completion, HZ / 2) == 0)
+ break;
+ reinit_completion(&cb_in->cb_completion);
+ } while (cb_in->cb_count < 32);
+ UT_ASSERT_INT(cb_in->cb_count, >, 0);
+ response = smp2p_gpio_get_value(cb_in);
+ SMP2P_SET_RMT_CMD_TYPE(request, 0);
+ UT_ASSERT_HEX(request, ==, response);
+
+ /* write echo of data value of all 1's */
+ request = 0x0;
+ SMP2P_SET_RMT_CMD_TYPE(request, 1);
+ SMP2P_SET_RMT_CMD(request, SMP2P_LB_CMD_ECHO);
+ SMP2P_SET_RMT_DATA(request, ~0);
+
+ smp2p_gpio_set_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
+ cb_data_reset(cb_in);
+ smp2p_gpio_clr_bits(cb_out, ~SMP2P_RMT_IGNORE_MASK);
+ smp2p_gpio_set_bits(cb_out, request);
+
+ UT_ASSERT_INT(cb_in->cb_count, ==, 0);
+ smp2p_gpio_clr_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
+
+ /* verify response including 24 interrupts */
+ do {
+ UT_ASSERT_INT(
+ (int)wait_for_completion_timeout(
+ &cb_in->cb_completion, HZ / 2),
+ >, 0);
+ reinit_completion(&cb_in->cb_completion);
+ } while (cb_in->cb_count < 24);
+ response = smp2p_gpio_get_value(cb_in);
+ SMP2P_SET_RMT_CMD_TYPE(request, 0);
+ UT_ASSERT_HEX(request, ==, response);
+ UT_ASSERT_INT(24, ==, cb_in->cb_count);
+
+ seq_puts(s, "\tOK\n");
+ } while (0);
+
+ if (failed) {
+ pr_err("%s: Failed\n", name);
+ seq_puts(s, "\tFailed\n");
+ }
+
+ /* unregister for interrupts */
+ if (cb_in->irq_base_id) {
+ for (id = 0; id < SMP2P_BITS_PER_ENTRY; ++id)
+ free_irq(cb_in->irq_base_id + id, cb_in);
+ }
+
+ smp2p_gpio_open_test_entry("smp2p", remote_pid, false);
+ msm_smp2p_init_rmt_lpb_proc(remote_pid);
+}
+
+/**
+ * smp2p_ut_remote_inout - Verify inbound/outbound functionality for all.
+ *
+ * @s: pointer to output file
+ *
+ * This test verifies inbound and outbound functionality for all
+ * configured remote processor.
+ */
+static void smp2p_ut_remote_inout(struct seq_file *s)
+{
+ struct smp2p_interrupt_config *int_cfg;
+ int pid;
+
+ int_cfg = smp2p_get_interrupt_config();
+ if (!int_cfg) {
+ seq_puts(s, "Remote processor config unavailable\n");
+ return;
+ }
+
+ for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
+ if (!int_cfg[pid].is_configured)
+ continue;
+
+ smp2p_ut_remote_inout_core(s, pid, __func__);
+ }
+}
+
+static int __init smp2p_debugfs_init(void)
+{
+ /* register GPIO pins */
+ (void)platform_driver_register(&smp2p_gpio_driver);
+
+ /*
+ * Add Unit Test entries.
+ *
+ * The idea with unit tests is that you can run all of them
+ * from ADB shell by doing:
+ * adb shell
+ * cat ut*
+ *
+ * And if particular tests fail, you can then repeatedly run the
+ * failing tests as you debug and resolve the failing test.
+ */
+ smp2p_debug_create("ut_local_gpio_out", smp2p_ut_local_gpio_out);
+ smp2p_debug_create("ut_local_gpio_in", smp2p_ut_local_gpio_in);
+ smp2p_debug_create("ut_local_gpio_in_update_open",
+ smp2p_ut_local_gpio_in_update_open);
+ smp2p_debug_create("ut_remote_gpio_inout", smp2p_ut_remote_inout);
+ return 0;
+}
+late_initcall(smp2p_debugfs_init);
diff --git a/drivers/gpio/gpio-msm-smp2p.c b/drivers/gpio/gpio-msm-smp2p.c
new file mode 100644
index 000000000000..36a375d65719
--- /dev/null
+++ b/drivers/gpio/gpio-msm-smp2p.c
@@ -0,0 +1,835 @@
+/* drivers/gpio/gpio-msm-smp2p.c
+ *
+ * Copyright (c) 2013-2014, 2016 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/bitmap.h>
+#include <linux/of.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/ipc_logging.h>
+#include "../soc/qcom/smp2p_private_api.h"
+#include "../soc/qcom/smp2p_private.h"
+
+/* GPIO device - one per SMP2P entry. */
+struct smp2p_chip_dev {
+ struct list_head entry_list;
+ char name[SMP2P_MAX_ENTRY_NAME];
+ int remote_pid;
+ bool is_inbound;
+ bool is_open;
+ bool in_shadow;
+ uint32_t shadow_value;
+ struct work_struct shadow_work;
+ spinlock_t shadow_lock;
+ struct notifier_block out_notifier;
+ struct notifier_block in_notifier;
+ struct msm_smp2p_out *out_handle;
+
+ struct gpio_chip gpio;
+ struct irq_domain *irq_domain;
+ int irq_base;
+
+ spinlock_t irq_lock;
+ DECLARE_BITMAP(irq_enabled, SMP2P_BITS_PER_ENTRY);
+ DECLARE_BITMAP(irq_rising_edge, SMP2P_BITS_PER_ENTRY);
+ DECLARE_BITMAP(irq_falling_edge, SMP2P_BITS_PER_ENTRY);
+};
+
+static struct platform_driver smp2p_gpio_driver;
+static struct lock_class_key smp2p_gpio_lock_class;
+static struct irq_chip smp2p_gpio_irq_chip;
+static DEFINE_SPINLOCK(smp2p_entry_lock_lha1);
+static LIST_HEAD(smp2p_entry_list);
+
+/* Used for mapping edge to name for logging. */
+static const char * const edge_names[] = {
+ "-",
+ "0->1",
+ "1->0",
+ "-",
+};
+
+/* Used for mapping edge to value for logging. */
+static const char * const edge_name_rising[] = {
+ "-",
+ "0->1",
+};
+
+/* Used for mapping edge to value for logging. */
+static const char * const edge_name_falling[] = {
+ "-",
+ "1->0",
+};
+
+static int smp2p_gpio_to_irq(struct gpio_chip *cp,
+ unsigned offset);
+
+/**
+ * smp2p_get_value - Retrieves GPIO value.
+ *
+ * @cp: GPIO chip pointer
+ * @offset: Pin offset
+ * @returns: >=0: value of GPIO Pin; < 0 for error
+ *
+ * Error codes:
+ * -ENODEV - chip/entry invalid
+ * -ENETDOWN - valid entry, but entry not yet created
+ */
+static int smp2p_get_value(struct gpio_chip *cp,
+ unsigned offset)
+{
+ struct smp2p_chip_dev *chip;
+ int ret = 0;
+ uint32_t data;
+
+ if (!cp)
+ return -ENODEV;
+
+ chip = container_of(cp, struct smp2p_chip_dev, gpio);
+ if (!chip->is_open)
+ return -ENETDOWN;
+
+ if (chip->is_inbound)
+ ret = msm_smp2p_in_read(chip->remote_pid, chip->name, &data);
+ else
+ ret = msm_smp2p_out_read(chip->out_handle, &data);
+
+ if (!ret)
+ ret = (data & (1 << offset)) ? 1 : 0;
+
+ return ret;
+}
+
+/**
+ * smp2p_set_value - Sets GPIO value.
+ *
+ * @cp: GPIO chip pointer
+ * @offset: Pin offset
+ * @value: New value
+ */
+static void smp2p_set_value(struct gpio_chip *cp, unsigned offset, int value)
+{
+ struct smp2p_chip_dev *chip;
+ uint32_t data_set;
+ uint32_t data_clear;
+ bool send_irq;
+ int ret;
+ unsigned long flags;
+
+ if (!cp)
+ return;
+
+ chip = container_of(cp, struct smp2p_chip_dev, gpio);
+
+ if (chip->is_inbound) {
+ SMP2P_INFO("%s: '%s':%d virq %d invalid operation\n",
+ __func__, chip->name, chip->remote_pid,
+ chip->irq_base + offset);
+ return;
+ }
+
+ if (value & SMP2P_GPIO_NO_INT) {
+ value &= ~SMP2P_GPIO_NO_INT;
+ send_irq = false;
+ } else {
+ send_irq = true;
+ }
+
+ if (value) {
+ data_set = 1 << offset;
+ data_clear = 0;
+ } else {
+ data_set = 0;
+ data_clear = 1 << offset;
+ }
+
+ spin_lock_irqsave(&chip->shadow_lock, flags);
+ if (!chip->is_open) {
+ chip->in_shadow = true;
+ chip->shadow_value &= ~data_clear;
+ chip->shadow_value |= data_set;
+ spin_unlock_irqrestore(&chip->shadow_lock, flags);
+ return;
+ }
+
+ if (chip->in_shadow) {
+ chip->in_shadow = false;
+ chip->shadow_value &= ~data_clear;
+ chip->shadow_value |= data_set;
+ ret = msm_smp2p_out_modify(chip->out_handle,
+ chip->shadow_value, 0x0, send_irq);
+ chip->shadow_value = 0x0;
+ } else {
+ ret = msm_smp2p_out_modify(chip->out_handle,
+ data_set, data_clear, send_irq);
+ }
+ spin_unlock_irqrestore(&chip->shadow_lock, flags);
+
+ if (ret)
+ SMP2P_GPIO("'%s':%d gpio %d set to %d failed (%d)\n",
+ chip->name, chip->remote_pid,
+ chip->gpio.base + offset, value, ret);
+ else
+ SMP2P_GPIO("'%s':%d gpio %d set to %d\n",
+ chip->name, chip->remote_pid,
+ chip->gpio.base + offset, value);
+}
+
+/**
+ * smp2p_direction_input - Sets GPIO direction to input.
+ *
+ * @cp: GPIO chip pointer
+ * @offset: Pin offset
+ * @returns: 0 for success; < 0 for failure
+ */
+static int smp2p_direction_input(struct gpio_chip *cp, unsigned offset)
+{
+ struct smp2p_chip_dev *chip;
+
+ if (!cp)
+ return -ENODEV;
+
+ chip = container_of(cp, struct smp2p_chip_dev, gpio);
+ if (!chip->is_inbound)
+ return -EPERM;
+
+ return 0;
+}
+
+/**
+ * smp2p_direction_output - Sets GPIO direction to output.
+ *
+ * @cp: GPIO chip pointer
+ * @offset: Pin offset
+ * @value: Direction
+ * @returns: 0 for success; < 0 for failure
+ */
+static int smp2p_direction_output(struct gpio_chip *cp,
+ unsigned offset, int value)
+{
+ struct smp2p_chip_dev *chip;
+
+ if (!cp)
+ return -ENODEV;
+
+ chip = container_of(cp, struct smp2p_chip_dev, gpio);
+ if (chip->is_inbound)
+ return -EPERM;
+
+ return 0;
+}
+
+/**
+ * smp2p_gpio_to_irq - Convert GPIO pin to virtual IRQ pin.
+ *
+ * @cp: GPIO chip pointer
+ * @offset: Pin offset
+ * @returns: >0 for virtual irq value; < 0 for failure
+ */
+static int smp2p_gpio_to_irq(struct gpio_chip *cp, unsigned offset)
+{
+ struct smp2p_chip_dev *chip;
+
+ chip = container_of(cp, struct smp2p_chip_dev, gpio);
+ if (!cp || chip->irq_base <= 0)
+ return -ENODEV;
+
+ return chip->irq_base + offset;
+}
+
+/**
+ * smp2p_gpio_irq_mask_helper - Mask/Unmask interrupt.
+ *
+ * @d: IRQ data
+ * @mask: true to mask (disable), false to unmask (enable)
+ */
+static void smp2p_gpio_irq_mask_helper(struct irq_data *d, bool mask)
+{
+ struct smp2p_chip_dev *chip;
+ int offset;
+ unsigned long flags;
+
+ chip = (struct smp2p_chip_dev *)irq_get_chip_data(d->irq);
+ if (!chip || chip->irq_base <= 0)
+ return;
+
+ offset = d->irq - chip->irq_base;
+ spin_lock_irqsave(&chip->irq_lock, flags);
+ if (mask)
+ clear_bit(offset, chip->irq_enabled);
+ else
+ set_bit(offset, chip->irq_enabled);
+ spin_unlock_irqrestore(&chip->irq_lock, flags);
+}
+
+/**
+ * smp2p_gpio_irq_mask - Mask interrupt.
+ *
+ * @d: IRQ data
+ */
+static void smp2p_gpio_irq_mask(struct irq_data *d)
+{
+ smp2p_gpio_irq_mask_helper(d, true);
+}
+
+/**
+ * smp2p_gpio_irq_unmask - Unmask interrupt.
+ *
+ * @d: IRQ data
+ */
+static void smp2p_gpio_irq_unmask(struct irq_data *d)
+{
+ smp2p_gpio_irq_mask_helper(d, false);
+}
+
+/**
+ * smp2p_gpio_irq_set_type - Set interrupt edge type.
+ *
+ * @d: IRQ data
+ * @type: Edge type for interrupt
+ * @returns 0 for success; < 0 for failure
+ */
+static int smp2p_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct smp2p_chip_dev *chip;
+ int offset;
+ unsigned long flags;
+ int ret = 0;
+
+ chip = (struct smp2p_chip_dev *)irq_get_chip_data(d->irq);
+ if (!chip)
+ return -ENODEV;
+
+ if (chip->irq_base <= 0) {
+ SMP2P_ERR("%s: '%s':%d virqbase %d invalid\n",
+ __func__, chip->name, chip->remote_pid,
+ chip->irq_base);
+ return -ENODEV;
+ }
+
+ offset = d->irq - chip->irq_base;
+
+ spin_lock_irqsave(&chip->irq_lock, flags);
+ clear_bit(offset, chip->irq_rising_edge);
+ clear_bit(offset, chip->irq_falling_edge);
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ set_bit(offset, chip->irq_rising_edge);
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ set_bit(offset, chip->irq_falling_edge);
+ break;
+
+ case IRQ_TYPE_NONE:
+ case IRQ_TYPE_DEFAULT:
+ case IRQ_TYPE_EDGE_BOTH:
+ set_bit(offset, chip->irq_rising_edge);
+ set_bit(offset, chip->irq_falling_edge);
+ break;
+
+ default:
+ SMP2P_ERR("%s: unsupported interrupt type 0x%x\n",
+ __func__, type);
+ ret = -EINVAL;
+ break;
+ }
+ spin_unlock_irqrestore(&chip->irq_lock, flags);
+ return ret;
+}
+
+/**
+ * smp2p_irq_map - Creates or updates binding of virtual IRQ
+ *
+ * @domain_ptr: Interrupt domain pointer
+ * @virq: Virtual IRQ
+ * @hw: Hardware IRQ (same as virq for nomap)
+ * @returns: 0 for success
+ */
+static int smp2p_irq_map(struct irq_domain *domain_ptr, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct smp2p_chip_dev *chip;
+
+ chip = domain_ptr->host_data;
+ if (!chip) {
+ SMP2P_ERR("%s: invalid domain ptr\n", __func__);
+ return -ENODEV;
+ }
+
+ /* map chip structures to device */
+ irq_set_lockdep_class(virq, &smp2p_gpio_lock_class);
+ irq_set_chip_and_handler(virq, &smp2p_gpio_irq_chip,
+ handle_level_irq);
+ irq_set_chip_data(virq, chip);
+
+ return 0;
+}
+
+static struct irq_chip smp2p_gpio_irq_chip = {
+ .name = "smp2p_gpio",
+ .irq_mask = smp2p_gpio_irq_mask,
+ .irq_unmask = smp2p_gpio_irq_unmask,
+ .irq_set_type = smp2p_gpio_irq_set_type,
+};
+
+/* No-map interrupt Domain */
+static const struct irq_domain_ops smp2p_irq_domain_ops = {
+ .map = smp2p_irq_map,
+};
+
+/**
+ * msm_summary_irq_handler - Handles inbound entry change notification.
+ *
+ * @chip: GPIO chip pointer
+ * @entry: Change notification data
+ *
+ * Whenever an entry changes, this callback is triggered to determine
+ * which bits changed and if the corresponding interrupts need to be
+ * triggered.
+ */
+static void msm_summary_irq_handler(struct smp2p_chip_dev *chip,
+ struct msm_smp2p_update_notif *entry)
+{
+ int i;
+ uint32_t cur_val;
+ uint32_t prev_val;
+ uint32_t edge;
+ unsigned long flags;
+ bool trigger_interrrupt;
+ bool irq_rising;
+ bool irq_falling;
+
+ cur_val = entry->current_value;
+ prev_val = entry->previous_value;
+
+ if (chip->irq_base <= 0)
+ return;
+
+ SMP2P_GPIO("'%s':%d GPIO Summary IRQ Change %08x->%08x\n",
+ chip->name, chip->remote_pid, prev_val, cur_val);
+
+ for (i = 0; i < SMP2P_BITS_PER_ENTRY; ++i) {
+ spin_lock_irqsave(&chip->irq_lock, flags);
+ trigger_interrrupt = false;
+ edge = (prev_val & 0x1) << 1 | (cur_val & 0x1);
+ irq_rising = test_bit(i, chip->irq_rising_edge);
+ irq_falling = test_bit(i, chip->irq_falling_edge);
+
+ if (test_bit(i, chip->irq_enabled)) {
+ if (edge == 0x1 && irq_rising)
+ /* 0->1 transition */
+ trigger_interrrupt = true;
+ else if (edge == 0x2 && irq_falling)
+ /* 1->0 transition */
+ trigger_interrrupt = true;
+ } else {
+ SMP2P_GPIO(
+ "'%s':%d GPIO bit %d virq %d (%s,%s) - edge %s disabled\n",
+ chip->name, chip->remote_pid, i,
+ chip->irq_base + i,
+ edge_name_rising[irq_rising],
+ edge_name_falling[irq_falling],
+ edge_names[edge]);
+ }
+ spin_unlock_irqrestore(&chip->irq_lock, flags);
+
+ if (trigger_interrrupt) {
+ SMP2P_INFO(
+ "'%s':%d GPIO bit %d virq %d (%s,%s) - edge %s triggering\n",
+ chip->name, chip->remote_pid, i,
+ chip->irq_base + i,
+ edge_name_rising[irq_rising],
+ edge_name_falling[irq_falling],
+ edge_names[edge]);
+ (void)generic_handle_irq(chip->irq_base + i);
+ }
+
+ cur_val >>= 1;
+ prev_val >>= 1;
+ }
+}
+
+/**
+ * Adds an interrupt domain based upon the DT node.
+ *
+ * @chip: pointer to GPIO chip
+ * @node: pointer to Device Tree node
+ */
+static void smp2p_add_irq_domain(struct smp2p_chip_dev *chip,
+ struct device_node *node)
+{
+ int irq_base;
+
+ /* map GPIO pins to interrupts */
+ chip->irq_domain = irq_domain_add_linear(node, SMP2P_BITS_PER_ENTRY,
+ &smp2p_irq_domain_ops, chip);
+ if (!chip->irq_domain) {
+ SMP2P_ERR("%s: unable to create interrupt domain '%s':%d\n",
+ __func__, chip->name, chip->remote_pid);
+ goto domain_fail;
+ }
+
+ /* alloc a contiguous set of virt irqs from anywhere in the irq space */
+ irq_base = irq_alloc_descs_from(0, SMP2P_BITS_PER_ENTRY, of_node_to_nid(
+ irq_domain_get_of_node(chip->irq_domain)));
+ if (irq_base < 0) {
+ SMP2P_ERR("alloc virt irqs failed:%d name:%s pid%d\n", irq_base,
+ chip->name, chip->remote_pid);
+ goto irq_alloc_fail;
+ }
+
+ /* map the allocated irqs to gpios */
+ irq_domain_associate_many(chip->irq_domain, irq_base, 0,
+ SMP2P_BITS_PER_ENTRY);
+
+ chip->irq_base = irq_base;
+ SMP2P_DBG("create mapping:%d naem:%s pid:%d\n", chip->irq_base,
+ chip->name, chip->remote_pid);
+ return;
+
+irq_alloc_fail:
+ irq_domain_remove(chip->irq_domain);
+domain_fail:
+ return;
+}
+
+/**
+ * Notifier function passed into smp2p API for out bound entries.
+ *
+ * @self: Pointer to calling notifier block
+ * @event: Event
+ * @data: Event-specific data
+ * @returns: 0
+ */
+static int smp2p_gpio_out_notify(struct notifier_block *self,
+ unsigned long event, void *data)
+{
+ struct smp2p_chip_dev *chip;
+
+ chip = container_of(self, struct smp2p_chip_dev, out_notifier);
+
+ switch (event) {
+ case SMP2P_OPEN:
+ chip->is_open = 1;
+ SMP2P_GPIO("%s: Opened out '%s':%d in_shadow[%d]\n", __func__,
+ chip->name, chip->remote_pid, chip->in_shadow);
+ if (chip->in_shadow)
+ schedule_work(&chip->shadow_work);
+ break;
+ case SMP2P_ENTRY_UPDATE:
+ break;
+ default:
+ SMP2P_ERR("%s: Unknown event\n", __func__);
+ break;
+ }
+ return 0;
+}
+
+/**
+ * Notifier function passed into smp2p API for in bound entries.
+ *
+ * @self: Pointer to calling notifier block
+ * @event: Event
+ * @data: Event-specific data
+ * @returns: 0
+ */
+static int smp2p_gpio_in_notify(struct notifier_block *self,
+ unsigned long event, void *data)
+{
+ struct smp2p_chip_dev *chip;
+
+ chip = container_of(self, struct smp2p_chip_dev, in_notifier);
+
+ switch (event) {
+ case SMP2P_OPEN:
+ chip->is_open = 1;
+ SMP2P_GPIO("%s: Opened in '%s':%d\n", __func__,
+ chip->name, chip->remote_pid);
+ break;
+ case SMP2P_ENTRY_UPDATE:
+ msm_summary_irq_handler(chip, data);
+ break;
+ default:
+ SMP2P_ERR("%s: Unknown event\n", __func__);
+ break;
+ }
+ return 0;
+}
+
+/**
+ * smp2p_gpio_shadow_worker - Handles shadow updates of an entry.
+ *
+ * @work: Work Item scheduled to handle the shadow updates.
+ */
+static void smp2p_gpio_shadow_worker(struct work_struct *work)
+{
+ struct smp2p_chip_dev *chip;
+ int ret;
+ unsigned long flags;
+
+ chip = container_of(work, struct smp2p_chip_dev, shadow_work);
+ spin_lock_irqsave(&chip->shadow_lock, flags);
+ if (chip->in_shadow) {
+ ret = msm_smp2p_out_modify(chip->out_handle,
+ chip->shadow_value, 0x0, true);
+
+ if (ret)
+ SMP2P_GPIO("'%s':%d shadow val[0x%x] failed(%d)\n",
+ chip->name, chip->remote_pid,
+ chip->shadow_value, ret);
+ else
+ SMP2P_GPIO("'%s':%d shadow val[0x%x]\n",
+ chip->name, chip->remote_pid,
+ chip->shadow_value);
+ chip->shadow_value = 0;
+ chip->in_shadow = false;
+ }
+ spin_unlock_irqrestore(&chip->shadow_lock, flags);
+}
+
+/**
+ * Device tree probe function.
+ *
+ * @pdev: Pointer to device tree data.
+ * @returns: 0 on success; -ENODEV otherwise
+ *
+ * Called for each smp2pgpio entry in the device tree.
+ */
+static int smp2p_gpio_probe(struct platform_device *pdev)
+{
+ struct device_node *node;
+ char *key;
+ struct smp2p_chip_dev *chip;
+ const char *name_tmp;
+ unsigned long flags;
+ bool is_test_entry = false;
+ int ret;
+
+ chip = kzalloc(sizeof(struct smp2p_chip_dev), GFP_KERNEL);
+ if (!chip) {
+ SMP2P_ERR("%s: out of memory\n", __func__);
+ ret = -ENOMEM;
+ goto fail;
+ }
+ spin_lock_init(&chip->irq_lock);
+ spin_lock_init(&chip->shadow_lock);
+ INIT_WORK(&chip->shadow_work, smp2p_gpio_shadow_worker);
+
+ /* parse device tree */
+ node = pdev->dev.of_node;
+ key = "qcom,entry-name";
+ ret = of_property_read_string(node, key, &name_tmp);
+ if (ret) {
+ SMP2P_ERR("%s: missing DT key '%s'\n", __func__, key);
+ goto fail;
+ }
+ strlcpy(chip->name, name_tmp, sizeof(chip->name));
+
+ key = "qcom,remote-pid";
+ ret = of_property_read_u32(node, key, &chip->remote_pid);
+ if (ret) {
+ SMP2P_ERR("%s: missing DT key '%s'\n", __func__, key);
+ goto fail;
+ }
+
+ key = "qcom,is-inbound";
+ chip->is_inbound = of_property_read_bool(node, key);
+
+ /* create virtual GPIO controller */
+ chip->gpio.label = chip->name;
+ chip->gpio.dev = &pdev->dev;
+ chip->gpio.owner = THIS_MODULE;
+ chip->gpio.direction_input = smp2p_direction_input,
+ chip->gpio.get = smp2p_get_value;
+ chip->gpio.direction_output = smp2p_direction_output,
+ chip->gpio.set = smp2p_set_value;
+ chip->gpio.to_irq = smp2p_gpio_to_irq,
+ chip->gpio.base = -1; /* use dynamic GPIO pin allocation */
+ chip->gpio.ngpio = SMP2P_BITS_PER_ENTRY;
+ ret = gpiochip_add(&chip->gpio);
+ if (ret) {
+ SMP2P_ERR("%s: unable to register GPIO '%s' ret %d\n",
+ __func__, chip->name, ret);
+ goto fail;
+ }
+
+ /*
+ * Test entries opened by GPIO Test conflict with loopback
+ * support, so the test entries must be explicitly opened
+ * in the unit test framework.
+ */
+ if (strncmp("smp2p", chip->name, SMP2P_MAX_ENTRY_NAME) == 0)
+ is_test_entry = true;
+
+ if (!chip->is_inbound) {
+ chip->out_notifier.notifier_call = smp2p_gpio_out_notify;
+ if (!is_test_entry) {
+ ret = msm_smp2p_out_open(chip->remote_pid, chip->name,
+ &chip->out_notifier,
+ &chip->out_handle);
+ if (ret < 0)
+ goto error;
+ }
+ } else {
+ chip->in_notifier.notifier_call = smp2p_gpio_in_notify;
+ if (!is_test_entry) {
+ ret = msm_smp2p_in_register(chip->remote_pid,
+ chip->name,
+ &chip->in_notifier);
+ if (ret < 0)
+ goto error;
+ }
+ }
+
+ spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
+ list_add(&chip->entry_list, &smp2p_entry_list);
+ spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
+
+ /*
+ * Create interrupt domain - note that chip can't be removed from the
+ * interrupt domain, so chip cannot be deleted after this point.
+ */
+ if (chip->is_inbound)
+ smp2p_add_irq_domain(chip, node);
+ else
+ chip->irq_base = -1;
+
+ SMP2P_GPIO("%s: added %s%s entry '%s':%d gpio %d irq %d",
+ __func__,
+ is_test_entry ? "test " : "",
+ chip->is_inbound ? "in" : "out",
+ chip->name, chip->remote_pid,
+ chip->gpio.base, chip->irq_base);
+
+ return 0;
+error:
+ gpiochip_remove(&chip->gpio);
+
+fail:
+ kfree(chip);
+ return ret;
+}
+
+/**
+ * smp2p_gpio_open_close - Opens or closes entry.
+ *
+ * @entry: Entry to open or close
+ * @do_open: true = open port; false = close
+ */
+static void smp2p_gpio_open_close(struct smp2p_chip_dev *entry,
+ bool do_open)
+{
+ int ret;
+
+ if (do_open) {
+ /* open entry */
+ if (entry->is_inbound)
+ ret = msm_smp2p_in_register(entry->remote_pid,
+ entry->name, &entry->in_notifier);
+ else
+ ret = msm_smp2p_out_open(entry->remote_pid,
+ entry->name, &entry->out_notifier,
+ &entry->out_handle);
+ SMP2P_GPIO("%s: opened %s '%s':%d ret %d\n",
+ __func__,
+ entry->is_inbound ? "in" : "out",
+ entry->name, entry->remote_pid,
+ ret);
+ } else {
+ /* close entry */
+ if (entry->is_inbound)
+ ret = msm_smp2p_in_unregister(entry->remote_pid,
+ entry->name, &entry->in_notifier);
+ else
+ ret = msm_smp2p_out_close(&entry->out_handle);
+ entry->is_open = false;
+ SMP2P_GPIO("%s: closed %s '%s':%d ret %d\n",
+ __func__,
+ entry->is_inbound ? "in" : "out",
+ entry->name, entry->remote_pid, ret);
+ }
+}
+
+/**
+ * smp2p_gpio_open_test_entry - Opens or closes test entries for unit testing.
+ *
+ * @name: Name of the entry
+ * @remote_pid: Remote processor ID
+ * @do_open: true = open port; false = close
+ */
+void smp2p_gpio_open_test_entry(const char *name, int remote_pid, bool do_open)
+{
+ struct smp2p_chip_dev *entry;
+ struct smp2p_chip_dev *start_entry;
+ unsigned long flags;
+
+ spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
+ if (list_empty(&smp2p_entry_list)) {
+ spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
+ return;
+ }
+ start_entry = list_first_entry(&smp2p_entry_list,
+ struct smp2p_chip_dev,
+ entry_list);
+ entry = start_entry;
+ do {
+ if (!strncmp(entry->name, name, SMP2P_MAX_ENTRY_NAME)
+ && entry->remote_pid == remote_pid) {
+ /* found entry to change */
+ spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
+ smp2p_gpio_open_close(entry, do_open);
+ spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
+ }
+ list_rotate_left(&smp2p_entry_list);
+ entry = list_first_entry(&smp2p_entry_list,
+ struct smp2p_chip_dev,
+ entry_list);
+ } while (entry != start_entry);
+ spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
+}
+
+static struct of_device_id msm_smp2p_match_table[] = {
+ {.compatible = "qcom,smp2pgpio", },
+ {},
+};
+
+static struct platform_driver smp2p_gpio_driver = {
+ .probe = smp2p_gpio_probe,
+ .driver = {
+ .name = "smp2pgpio",
+ .owner = THIS_MODULE,
+ .of_match_table = msm_smp2p_match_table,
+ },
+};
+
+static int smp2p_init(void)
+{
+ INIT_LIST_HEAD(&smp2p_entry_list);
+ return platform_driver_register(&smp2p_gpio_driver);
+}
+module_init(smp2p_init);
+
+static void __exit smp2p_exit(void)
+{
+ platform_driver_unregister(&smp2p_gpio_driver);
+}
+module_exit(smp2p_exit);
+
+MODULE_DESCRIPTION("SMP2P GPIO");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
index 16a7b6816744..8decab2a9cce 100644
--- a/drivers/gpio/gpiolib-acpi.c
+++ b/drivers/gpio/gpiolib-acpi.c
@@ -597,7 +597,7 @@ int acpi_dev_gpio_irq_get(struct acpi_device *adev, int index)
int idx, i;
for (i = 0, idx = 0; idx <= index; i++) {
- struct acpi_gpio_info info;
+ struct acpi_gpio_info info = {0, 0};
struct gpio_desc *desc;
desc = acpi_get_gpiod_by_index(adev, NULL, i, &info);
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 759a39906a52..fe89fd56eabf 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -2117,6 +2117,8 @@ struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
struct gpio_desc *desc = NULL;
int status;
enum gpio_lookup_flags lookupflags = 0;
+ /* Maybe we have a device name, maybe not */
+ const char *devname = dev ? dev_name(dev) : "?";
dev_dbg(dev, "GPIO lookup for consumer %s\n", con_id);
@@ -2145,8 +2147,11 @@ struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
return desc;
}
- /* If a connection label was passed use that, else use the device name as label */
- status = gpiod_request(desc, con_id ? con_id : dev_name(dev));
+ /*
+ * If a connection label was passed use that, else attempt to use
+ * the device name as label
+ */
+ status = gpiod_request(desc, con_id ? con_id : devname);
if (status < 0)
return ERR_PTR(status);
diff --git a/drivers/gpio/qpnp-pin.c b/drivers/gpio/qpnp-pin.c
new file mode 100644
index 000000000000..62cd78a95303
--- /dev/null
+++ b/drivers/gpio/qpnp-pin.c
@@ -0,0 +1,1714 @@
+/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/qpnp/pin.h>
+
+#define Q_REG_ADDR(q_spec, reg_index) \
+ ((q_spec)->offset + reg_index)
+
+#define Q_REG_STATUS1 0x8
+#define Q_REG_STATUS1_VAL_MASK 0x1
+#define Q_REG_STATUS1_GPIO_EN_REV0_MASK 0x2
+#define Q_REG_STATUS1_GPIO_EN_MASK 0x80
+#define Q_REG_STATUS1_MPP_EN_MASK 0x80
+
+#define Q_NUM_CTL_REGS 0xD
+
+/* revision registers base address offsets */
+#define Q_REG_DIG_MINOR_REV 0x0
+#define Q_REG_DIG_MAJOR_REV 0x1
+#define Q_REG_ANA_MINOR_REV 0x2
+
+/* type registers base address offsets */
+#define Q_REG_TYPE 0x4
+#define Q_REG_SUBTYPE 0x5
+
+/* gpio peripheral type and subtype values */
+#define Q_GPIO_TYPE 0x10
+#define Q_GPIO_SUBTYPE_GPIO_4CH 0x1
+#define Q_GPIO_SUBTYPE_GPIOC_4CH 0x5
+#define Q_GPIO_SUBTYPE_GPIO_8CH 0x9
+#define Q_GPIO_SUBTYPE_GPIOC_8CH 0xD
+#define Q_GPIO_SUBTYPE_GPIO_LV 0x10
+#define Q_GPIO_SUBTYPE_GPIO_MV 0x11
+
+/* mpp peripheral type and subtype values */
+#define Q_MPP_TYPE 0x11
+#define Q_MPP_SUBTYPE_4CH_NO_ANA_OUT 0x3
+#define Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT 0x4
+#define Q_MPP_SUBTYPE_4CH_NO_SINK 0x5
+#define Q_MPP_SUBTYPE_ULT_4CH_NO_SINK 0x6
+#define Q_MPP_SUBTYPE_4CH_FULL_FUNC 0x7
+#define Q_MPP_SUBTYPE_8CH_FULL_FUNC 0xF
+
+/* control register base address offsets */
+#define Q_REG_MODE_CTL 0x40
+#define Q_REG_DIG_VIN_CTL 0x41
+#define Q_REG_DIG_PULL_CTL 0x42
+#define Q_REG_DIG_IN_CTL 0x43
+#define Q_REG_DIG_OUT_SRC_CTL 0x44
+#define Q_REG_DIG_OUT_CTL 0x45
+#define Q_REG_EN_CTL 0x46
+#define Q_REG_AOUT_CTL 0x48
+#define Q_REG_AIN_CTL 0x4A
+#define Q_REG_APASS_SEL_CTL 0x4A
+#define Q_REG_SINK_CTL 0x4C
+
+/* control register regs array indices */
+#define Q_REG_I_MODE_CTL 0
+#define Q_REG_I_DIG_VIN_CTL 1
+#define Q_REG_I_DIG_PULL_CTL 2
+#define Q_REG_I_DIG_IN_CTL 3
+#define Q_REG_I_DIG_OUT_SRC_CTL 4
+#define Q_REG_I_DIG_OUT_CTL 5
+#define Q_REG_I_EN_CTL 6
+#define Q_REG_I_AOUT_CTL 8
+#define Q_REG_I_APASS_SEL_CTL 10
+#define Q_REG_I_AIN_CTL 10
+#define Q_REG_I_SINK_CTL 12
+
+/* control reg: mode */
+#define Q_REG_OUT_INVERT_SHIFT 0
+#define Q_REG_OUT_INVERT_MASK 0x1
+#define Q_REG_SRC_SEL_SHIFT 1
+#define Q_REG_SRC_SEL_MASK 0xE
+#define Q_REG_MODE_SEL_SHIFT 4
+#define Q_REG_MODE_SEL_MASK 0x70
+#define Q_REG_LV_MV_MODE_SEL_SHIFT 0
+#define Q_REG_LV_MV_MODE_SEL_MASK 0x3
+
+/* control reg: dig_out_src (GPIO LV/MV only) */
+#define Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT 0
+#define Q_REG_DIG_OUT_SRC_SRC_SEL_MASK 0xF
+#define Q_REG_DIG_OUT_SRC_INVERT_SHIFT 7
+#define Q_REG_DIG_OUT_SRC_INVERT_MASK 0x80
+
+/* control reg: dig_vin */
+#define Q_REG_VIN_SHIFT 0
+#define Q_REG_VIN_MASK 0x7
+
+/* control reg: dig_pull */
+#define Q_REG_PULL_SHIFT 0
+#define Q_REG_PULL_MASK 0x7
+
+/* control reg: dig_out */
+#define Q_REG_OUT_STRENGTH_SHIFT 0
+#define Q_REG_OUT_STRENGTH_MASK 0x3
+#define Q_REG_OUT_TYPE_SHIFT 4
+#define Q_REG_OUT_TYPE_MASK 0x30
+
+/* control reg: dig_in_ctl */
+#define Q_REG_DTEST_SEL_SHIFT 0
+#define Q_REG_DTEST_SEL_MASK 0xF
+#define Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT 0
+#define Q_REG_LV_MV_DTEST_SEL_CFG_MASK 0x7
+#define Q_REG_LV_MV_DTEST_SEL_EN_SHIFT 7
+#define Q_REG_LV_MV_DTEST_SEL_EN_MASK 0x80
+
+/* control reg: en */
+#define Q_REG_MASTER_EN_SHIFT 7
+#define Q_REG_MASTER_EN_MASK 0x80
+
+/* control reg: ana_out */
+#define Q_REG_AOUT_REF_SHIFT 0
+#define Q_REG_AOUT_REF_MASK 0x7
+
+/* control reg: ana_in */
+#define Q_REG_AIN_ROUTE_SHIFT 0
+#define Q_REG_AIN_ROUTE_MASK 0x7
+
+/* control reg: sink */
+#define Q_REG_CS_OUT_SHIFT 0
+#define Q_REG_CS_OUT_MASK 0x7
+
+/* control ref: apass_sel */
+#define Q_REG_APASS_SEL_SHIFT 0
+#define Q_REG_APASS_SEL_MASK 0x3
+
+enum qpnp_pin_param_type {
+ Q_PIN_CFG_MODE,
+ Q_PIN_CFG_OUTPUT_TYPE,
+ Q_PIN_CFG_INVERT,
+ Q_PIN_CFG_PULL,
+ Q_PIN_CFG_VIN_SEL,
+ Q_PIN_CFG_OUT_STRENGTH,
+ Q_PIN_CFG_SRC_SEL,
+ Q_PIN_CFG_MASTER_EN,
+ Q_PIN_CFG_AOUT_REF,
+ Q_PIN_CFG_AIN_ROUTE,
+ Q_PIN_CFG_CS_OUT,
+ Q_PIN_CFG_APASS_SEL,
+ Q_PIN_CFG_DTEST_SEL,
+ Q_PIN_CFG_INVALID,
+};
+
+#define Q_NUM_PARAMS Q_PIN_CFG_INVALID
+
+/* param error checking */
+#define QPNP_PIN_GPIO_MODE_INVALID 3
+#define QPNP_PIN_GPIO_LV_MV_MODE_INVALID 4
+#define QPNP_PIN_MPP_MODE_INVALID 7
+#define QPNP_PIN_INVERT_INVALID 2
+#define QPNP_PIN_OUT_BUF_INVALID 3
+#define QPNP_PIN_GPIO_LV_MV_OUT_BUF_INVALID 4
+#define QPNP_PIN_VIN_4CH_INVALID 5
+#define QPNP_PIN_VIN_8CH_INVALID 8
+#define QPNP_PIN_GPIO_LV_VIN_INVALID 1
+#define QPNP_PIN_GPIO_MV_VIN_INVALID 2
+#define QPNP_PIN_GPIO_PULL_INVALID 6
+#define QPNP_PIN_MPP_PULL_INVALID 4
+#define QPNP_PIN_OUT_STRENGTH_INVALID 4
+#define QPNP_PIN_SRC_INVALID 8
+#define QPNP_PIN_GPIO_LV_MV_SRC_INVALID 16
+#define QPNP_PIN_MASTER_INVALID 2
+#define QPNP_PIN_AOUT_REF_INVALID 8
+#define QPNP_PIN_AIN_ROUTE_INVALID 8
+#define QPNP_PIN_CS_OUT_INVALID 8
+#define QPNP_PIN_APASS_SEL_INVALID 4
+#define QPNP_PIN_DTEST_SEL_INVALID 4
+
+struct qpnp_pin_spec {
+ uint8_t slave; /* 0-15 */
+ uint16_t offset; /* 0-255 */
+ uint32_t gpio_chip_idx; /* offset from gpio_chip base */
+ uint32_t pmic_pin; /* PMIC pin number */
+ int irq; /* logical IRQ number */
+ u8 regs[Q_NUM_CTL_REGS]; /* Control regs */
+ u8 num_ctl_regs; /* usable number on this pin */
+ u8 type; /* peripheral type */
+ u8 subtype; /* peripheral subtype */
+ u8 dig_major_rev;
+ struct device_node *node;
+ enum qpnp_pin_param_type params[Q_NUM_PARAMS];
+ struct qpnp_pin_chip *q_chip;
+};
+
+struct qpnp_pin_chip {
+ struct gpio_chip gpio_chip;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct qpnp_pin_spec **pmic_pins;
+ struct qpnp_pin_spec **chip_gpios;
+ uint32_t pmic_pin_lowest;
+ uint32_t pmic_pin_highest;
+ struct device_node *int_ctrl;
+ struct list_head chip_list;
+ struct dentry *dfs_dir;
+ bool chip_registered;
+};
+
+static LIST_HEAD(qpnp_pin_chips);
+static DEFINE_MUTEX(qpnp_pin_chips_lock);
+
+static inline void qpnp_pmic_pin_set_spec(struct qpnp_pin_chip *q_chip,
+ uint32_t pmic_pin,
+ struct qpnp_pin_spec *spec)
+{
+ q_chip->pmic_pins[pmic_pin - q_chip->pmic_pin_lowest] = spec;
+}
+
+static inline struct qpnp_pin_spec *qpnp_pmic_pin_get_spec(
+ struct qpnp_pin_chip *q_chip,
+ uint32_t pmic_pin)
+{
+ if (pmic_pin < q_chip->pmic_pin_lowest ||
+ pmic_pin > q_chip->pmic_pin_highest)
+ return NULL;
+
+ return q_chip->pmic_pins[pmic_pin - q_chip->pmic_pin_lowest];
+}
+
+static inline struct qpnp_pin_spec *qpnp_chip_gpio_get_spec(
+ struct qpnp_pin_chip *q_chip,
+ uint32_t chip_gpio)
+{
+ if (chip_gpio >= q_chip->gpio_chip.ngpio)
+ return NULL;
+
+ return q_chip->chip_gpios[chip_gpio];
+}
+
+static inline void qpnp_chip_gpio_set_spec(struct qpnp_pin_chip *q_chip,
+ uint32_t chip_gpio,
+ struct qpnp_pin_spec *spec)
+{
+ q_chip->chip_gpios[chip_gpio] = spec;
+}
+
+static bool is_gpio_lv_mv(struct qpnp_pin_spec *q_spec)
+{
+ if ((q_spec->type == Q_GPIO_TYPE) &&
+ (q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_LV ||
+ q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_MV))
+ return true;
+
+ return false;
+}
+
+/*
+ * Determines whether a specified param's configuration is correct.
+ * This check is two tier. First a check is done whether the hardware
+ * supports this param and value requested. The second check validates
+ * that the configuration is correct, given the fact that the hardware
+ * supports it.
+ *
+ * Returns
+ * -ENXIO is the hardware does not support this param.
+ * -EINVAL if the the hardware does support this param, but the
+ * requested value is outside the supported range.
+ */
+static int qpnp_pin_check_config(enum qpnp_pin_param_type idx,
+ struct qpnp_pin_spec *q_spec, uint32_t val)
+{
+ u8 subtype = q_spec->subtype;
+
+ switch (idx) {
+ case Q_PIN_CFG_MODE:
+ if (q_spec->type == Q_GPIO_TYPE) {
+ if (is_gpio_lv_mv(q_spec)) {
+ if (val >= QPNP_PIN_GPIO_LV_MV_MODE_INVALID)
+ return -EINVAL;
+ } else if (val >= QPNP_PIN_GPIO_MODE_INVALID) {
+ return -EINVAL;
+ }
+ } else if (q_spec->type == Q_MPP_TYPE) {
+ if (val >= QPNP_PIN_MPP_MODE_INVALID)
+ return -EINVAL;
+ if ((subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK) &&
+ (val == QPNP_PIN_MODE_BIDIR))
+ return -ENXIO;
+ }
+ break;
+ case Q_PIN_CFG_OUTPUT_TYPE:
+ if (q_spec->type != Q_GPIO_TYPE)
+ return -ENXIO;
+ if ((val == QPNP_PIN_OUT_BUF_OPEN_DRAIN_NMOS ||
+ val == QPNP_PIN_OUT_BUF_OPEN_DRAIN_PMOS) &&
+ (subtype == Q_GPIO_SUBTYPE_GPIOC_4CH ||
+ (subtype == Q_GPIO_SUBTYPE_GPIOC_8CH)))
+ return -EINVAL;
+ else if (is_gpio_lv_mv(q_spec) &&
+ val >= QPNP_PIN_GPIO_LV_MV_OUT_BUF_INVALID)
+ return -EINVAL;
+ else if (val >= QPNP_PIN_OUT_BUF_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_INVERT:
+ if (val >= QPNP_PIN_INVERT_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_PULL:
+ if (q_spec->type == Q_GPIO_TYPE &&
+ val >= QPNP_PIN_GPIO_PULL_INVALID)
+ return -EINVAL;
+ if (q_spec->type == Q_MPP_TYPE) {
+ if (val >= QPNP_PIN_MPP_PULL_INVALID)
+ return -EINVAL;
+ if (subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK)
+ return -ENXIO;
+ }
+ break;
+ case Q_PIN_CFG_VIN_SEL:
+ if (is_gpio_lv_mv(q_spec)) {
+ if (subtype == Q_GPIO_SUBTYPE_GPIO_LV) {
+ if (val >= QPNP_PIN_GPIO_LV_VIN_INVALID)
+ return -EINVAL;
+ } else {
+ if (val >= QPNP_PIN_GPIO_MV_VIN_INVALID)
+ return -EINVAL;
+ }
+ } else if (val >= QPNP_PIN_VIN_8CH_INVALID) {
+ return -EINVAL;
+ } else if (val >= QPNP_PIN_VIN_4CH_INVALID) {
+ if (q_spec->type == Q_GPIO_TYPE &&
+ (subtype == Q_GPIO_SUBTYPE_GPIO_4CH ||
+ subtype == Q_GPIO_SUBTYPE_GPIOC_4CH))
+ return -EINVAL;
+ if (q_spec->type == Q_MPP_TYPE &&
+ (subtype == Q_MPP_SUBTYPE_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_4CH_NO_SINK ||
+ subtype == Q_MPP_SUBTYPE_4CH_FULL_FUNC ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK))
+ return -EINVAL;
+ }
+ break;
+ case Q_PIN_CFG_OUT_STRENGTH:
+ if (q_spec->type != Q_GPIO_TYPE)
+ return -ENXIO;
+ if (val >= QPNP_PIN_OUT_STRENGTH_INVALID ||
+ val == 0)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_SRC_SEL:
+ if (q_spec->type == Q_MPP_TYPE &&
+ (val == QPNP_PIN_SEL_FUNC_1 ||
+ val == QPNP_PIN_SEL_FUNC_2))
+ return -EINVAL;
+ if (is_gpio_lv_mv(q_spec)) {
+ if (val >= QPNP_PIN_GPIO_LV_MV_SRC_INVALID)
+ return -EINVAL;
+ } else if (val >= QPNP_PIN_SRC_INVALID) {
+ return -EINVAL;
+ }
+ break;
+ case Q_PIN_CFG_MASTER_EN:
+ if (val >= QPNP_PIN_MASTER_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_AOUT_REF:
+ if (q_spec->type != Q_MPP_TYPE)
+ return -ENXIO;
+ if (subtype == Q_MPP_SUBTYPE_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT)
+ return -ENXIO;
+ if (val >= QPNP_PIN_AOUT_REF_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_AIN_ROUTE:
+ if (q_spec->type != Q_MPP_TYPE)
+ return -ENXIO;
+ if (val >= QPNP_PIN_AIN_ROUTE_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_CS_OUT:
+ if (q_spec->type != Q_MPP_TYPE)
+ return -ENXIO;
+ if (subtype == Q_MPP_SUBTYPE_4CH_NO_SINK ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK)
+ return -ENXIO;
+ if (val >= QPNP_PIN_CS_OUT_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_APASS_SEL:
+ if (!is_gpio_lv_mv(q_spec))
+ return -ENXIO;
+ if (val >= QPNP_PIN_APASS_SEL_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_DTEST_SEL:
+ if (val > QPNP_PIN_DTEST_SEL_INVALID)
+ return -EINVAL;
+ break;
+ default:
+ pr_err("invalid param type %u specified\n", idx);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#define Q_CHK_INVALID(idx, q_spec, val) \
+ (qpnp_pin_check_config(idx, q_spec, val) == -EINVAL)
+
+static int qpnp_pin_check_constraints(struct qpnp_pin_spec *q_spec,
+ struct qpnp_pin_cfg *param)
+{
+ int pin = q_spec->pmic_pin;
+ const char *name;
+
+ name = (q_spec->type == Q_GPIO_TYPE) ? "gpio" : "mpp";
+
+ if (Q_CHK_INVALID(Q_PIN_CFG_MODE, q_spec, param->mode))
+ pr_err("invalid direction value %d for %s %d\n",
+ param->mode, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_INVERT, q_spec, param->invert))
+ pr_err("invalid invert polarity value %d for %s %d\n",
+ param->invert, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_SRC_SEL, q_spec, param->src_sel))
+ pr_err("invalid source select value %d for %s %d\n",
+ param->src_sel, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_OUT_STRENGTH,
+ q_spec, param->out_strength))
+ pr_err("invalid out strength value %d for %s %d\n",
+ param->out_strength, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_OUTPUT_TYPE,
+ q_spec, param->output_type))
+ pr_err("invalid out type value %d for %s %d\n",
+ param->output_type, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_VIN_SEL, q_spec, param->vin_sel))
+ pr_err("invalid vin select %d value for %s %d\n",
+ param->vin_sel, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_PULL, q_spec, param->pull))
+ pr_err("invalid pull value %d for pin %s %d\n",
+ param->pull, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_MASTER_EN, q_spec, param->master_en))
+ pr_err("invalid master_en value %d for %s %d\n",
+ param->master_en, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_AOUT_REF, q_spec, param->aout_ref))
+ pr_err("invalid aout_reg value %d for %s %d\n",
+ param->aout_ref, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_AIN_ROUTE, q_spec, param->ain_route))
+ pr_err("invalid ain_route value %d for %s %d\n",
+ param->ain_route, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_CS_OUT, q_spec, param->cs_out))
+ pr_err("invalid cs_out value %d for %s %d\n",
+ param->cs_out, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_APASS_SEL, q_spec, param->apass_sel))
+ pr_err("invalid apass_sel value %d for %s %d\n",
+ param->apass_sel, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_DTEST_SEL, q_spec, param->dtest_sel))
+ pr_err("invalid dtest_sel value %d for %s %d\n",
+ param->dtest_sel, name, pin);
+ else
+ return 0;
+
+ return -EINVAL;
+}
+
+static inline u8 q_reg_get(u8 *reg, int shift, int mask)
+{
+ return (*reg & mask) >> shift;
+}
+
+static inline void q_reg_set(u8 *reg, int shift, int mask, int value)
+{
+ *reg |= (value << shift) & mask;
+}
+
+static inline void q_reg_clr_set(u8 *reg, int shift, int mask, int value)
+{
+ *reg &= ~mask;
+ *reg |= (value << shift) & mask;
+}
+
+/*
+ * Calculate the minimum number of registers that must be read / written
+ * in order to satisfy the full feature set of the given pin.
+ */
+static int qpnp_pin_ctl_regs_init(struct qpnp_pin_spec *q_spec)
+{
+ if (q_spec->type == Q_GPIO_TYPE) {
+ if (is_gpio_lv_mv(q_spec))
+ q_spec->num_ctl_regs = 11;
+ else
+ q_spec->num_ctl_regs = 7;
+ } else if (q_spec->type == Q_MPP_TYPE) {
+ switch (q_spec->subtype) {
+ case Q_MPP_SUBTYPE_4CH_NO_SINK:
+ case Q_MPP_SUBTYPE_ULT_4CH_NO_SINK:
+ q_spec->num_ctl_regs = 12;
+ break;
+ case Q_MPP_SUBTYPE_4CH_NO_ANA_OUT:
+ case Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT:
+ case Q_MPP_SUBTYPE_4CH_FULL_FUNC:
+ case Q_MPP_SUBTYPE_8CH_FULL_FUNC:
+ q_spec->num_ctl_regs = 13;
+ break;
+ default:
+ pr_err("Invalid MPP subtype 0x%x\n", q_spec->subtype);
+ return -EINVAL;
+ }
+ } else {
+ pr_err("Invalid type 0x%x\n", q_spec->type);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int qpnp_pin_read_regs(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec)
+{
+ int bytes_left = q_spec->num_ctl_regs;
+ int rc;
+ char *buf_p = &q_spec->regs[0];
+ u16 reg_addr = Q_REG_ADDR(q_spec, Q_REG_MODE_CTL);
+
+ while (bytes_left > 0) {
+ rc = regmap_bulk_read(q_chip->regmap, reg_addr, buf_p,
+ bytes_left < 8 ? bytes_left : 8);
+ if (rc)
+ return rc;
+ bytes_left -= 8;
+ buf_p += 8;
+ reg_addr += 8;
+ }
+ return 0;
+}
+
+static int qpnp_pin_write_regs(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec)
+{
+ int bytes_left = q_spec->num_ctl_regs;
+ int rc;
+ char *buf_p = &q_spec->regs[0];
+ u16 reg_addr = Q_REG_ADDR(q_spec, Q_REG_MODE_CTL);
+
+ while (bytes_left > 0) {
+ rc = regmap_bulk_write(q_chip->regmap, reg_addr, buf_p,
+ bytes_left < 8 ? bytes_left : 8);
+ if (rc)
+ return rc;
+ bytes_left -= 8;
+ buf_p += 8;
+ reg_addr += 8;
+ }
+ return 0;
+}
+
+static int qpnp_pin_cache_regs(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec)
+{
+ int rc;
+ struct device *dev = &q_chip->pdev->dev;
+
+ rc = qpnp_pin_read_regs(q_chip, q_spec);
+ if (rc)
+ dev_err(dev, "%s: unable to read control regs\n", __func__);
+
+ return rc;
+}
+
+#define Q_HAVE_HW_SP(idx, q_spec, val) \
+ (qpnp_pin_check_config(idx, q_spec, val) == 0)
+
+static int _qpnp_pin_config(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec,
+ struct qpnp_pin_cfg *param)
+{
+ struct device *dev = &q_chip->pdev->dev;
+ int rc;
+ u8 shift, mask, *reg;
+
+ rc = qpnp_pin_check_constraints(q_spec, param);
+ if (rc)
+ goto gpio_cfg;
+
+ /* set mode */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_MODE, q_spec, param->mode)) {
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ } else {
+ shift = Q_REG_MODE_SEL_SHIFT;
+ mask = Q_REG_MODE_SEL_MASK;
+ }
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL],
+ shift, mask, param->mode);
+ }
+
+ /* output specific configuration */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_INVERT, q_spec, param->invert)) {
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_OUT_INVERT_SHIFT;
+ mask = Q_REG_OUT_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+ q_reg_clr_set(reg, shift, mask, param->invert);
+ }
+
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_SRC_SEL, q_spec, param->src_sel)) {
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_SRC_SEL_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_SRC_SEL_SHIFT;
+ mask = Q_REG_SRC_SEL_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+ q_reg_clr_set(reg, shift, mask, param->src_sel);
+ }
+
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_OUT_STRENGTH, q_spec, param->out_strength))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+ Q_REG_OUT_STRENGTH_SHIFT, Q_REG_OUT_STRENGTH_MASK,
+ param->out_strength);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_OUTPUT_TYPE, q_spec, param->output_type))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+ Q_REG_OUT_TYPE_SHIFT, Q_REG_OUT_TYPE_MASK,
+ param->output_type);
+
+ /* input config */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_DTEST_SEL, q_spec, param->dtest_sel)
+ && param->dtest_sel) {
+ if (is_gpio_lv_mv(q_spec)) {
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT,
+ Q_REG_LV_MV_DTEST_SEL_CFG_MASK,
+ param->dtest_sel - 1);
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_LV_MV_DTEST_SEL_EN_SHIFT,
+ Q_REG_LV_MV_DTEST_SEL_EN_MASK, 0x1);
+ } else {
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_DTEST_SEL_SHIFT,
+ Q_REG_DTEST_SEL_MASK,
+ BIT(param->dtest_sel - 1));
+ }
+ }
+
+ /* config applicable for both input / output */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_VIN_SEL, q_spec, param->vin_sel))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_VIN_CTL],
+ Q_REG_VIN_SHIFT, Q_REG_VIN_MASK,
+ param->vin_sel);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_PULL, q_spec, param->pull))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_PULL_CTL],
+ Q_REG_PULL_SHIFT, Q_REG_PULL_MASK,
+ param->pull);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_MASTER_EN, q_spec, param->master_en))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_EN_CTL],
+ Q_REG_MASTER_EN_SHIFT, Q_REG_MASTER_EN_MASK,
+ param->master_en);
+
+ /* mpp specific config */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_AOUT_REF, q_spec, param->aout_ref))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_AOUT_CTL],
+ Q_REG_AOUT_REF_SHIFT, Q_REG_AOUT_REF_MASK,
+ param->aout_ref);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_AIN_ROUTE, q_spec, param->ain_route))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_AIN_CTL],
+ Q_REG_AIN_ROUTE_SHIFT, Q_REG_AIN_ROUTE_MASK,
+ param->ain_route);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_CS_OUT, q_spec, param->cs_out))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_SINK_CTL],
+ Q_REG_CS_OUT_SHIFT, Q_REG_CS_OUT_MASK,
+ param->cs_out);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_APASS_SEL, q_spec, param->apass_sel))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_APASS_SEL_CTL],
+ Q_REG_APASS_SEL_SHIFT, Q_REG_APASS_SEL_MASK,
+ param->apass_sel);
+
+ rc = qpnp_pin_write_regs(q_chip, q_spec);
+ if (rc) {
+ dev_err(&q_chip->pdev->dev,
+ "%s: unable to write master enable\n",
+ __func__);
+ goto gpio_cfg;
+ }
+
+ return 0;
+
+gpio_cfg:
+ dev_err(dev, "%s: unable to set default config for pmic pin %d\n",
+ __func__, q_spec->pmic_pin);
+
+ return rc;
+}
+
+int qpnp_pin_config(int gpio, struct qpnp_pin_cfg *param)
+{
+ int rc, chip_offset;
+ struct qpnp_pin_chip *q_chip;
+ struct qpnp_pin_spec *q_spec = NULL;
+ struct gpio_chip *gpio_chip;
+
+ if (param == NULL)
+ return -EINVAL;
+
+ mutex_lock(&qpnp_pin_chips_lock);
+ list_for_each_entry(q_chip, &qpnp_pin_chips, chip_list) {
+ gpio_chip = &q_chip->gpio_chip;
+ if (gpio >= gpio_chip->base
+ && gpio < gpio_chip->base + gpio_chip->ngpio) {
+ chip_offset = gpio - gpio_chip->base;
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, chip_offset);
+ if (WARN_ON(!q_spec)) {
+ mutex_unlock(&qpnp_pin_chips_lock);
+ return -ENODEV;
+ }
+ break;
+ }
+ }
+ mutex_unlock(&qpnp_pin_chips_lock);
+
+ if (!q_spec)
+ return -ENODEV;
+
+ rc = _qpnp_pin_config(q_chip, q_spec, param);
+
+ return rc;
+}
+EXPORT_SYMBOL(qpnp_pin_config);
+
+int qpnp_pin_map(const char *name, uint32_t pmic_pin)
+{
+ struct qpnp_pin_chip *q_chip;
+ struct qpnp_pin_spec *q_spec = NULL;
+
+ if (!name)
+ return -EINVAL;
+
+ mutex_lock(&qpnp_pin_chips_lock);
+ list_for_each_entry(q_chip, &qpnp_pin_chips, chip_list) {
+ if (strcmp(q_chip->gpio_chip.label, name) != 0)
+ continue;
+ if (q_chip->pmic_pin_lowest <= pmic_pin &&
+ q_chip->pmic_pin_highest >= pmic_pin) {
+ q_spec = qpnp_pmic_pin_get_spec(q_chip, pmic_pin);
+ mutex_unlock(&qpnp_pin_chips_lock);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+ return q_chip->gpio_chip.base + q_spec->gpio_chip_idx;
+ }
+ }
+ mutex_unlock(&qpnp_pin_chips_lock);
+ return -EINVAL;
+}
+EXPORT_SYMBOL(qpnp_pin_map);
+
+static int qpnp_pin_to_irq(struct gpio_chip *gpio_chip, unsigned int offset)
+{
+ struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_pin_spec *q_spec;
+ struct of_phandle_args oirq;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (!q_spec)
+ return -EINVAL;
+
+ /* if we have mapped this pin previously return the virq */
+ if (q_spec->irq)
+ return q_spec->irq;
+
+ /* call into irq_domain to get irq mapping */
+ oirq.np = q_chip->int_ctrl;
+ oirq.args[0] = to_spmi_device(q_chip->pdev->dev.parent)->usid;
+ oirq.args[1] = (q_spec->offset >> 8) & 0xFF;
+ oirq.args[2] = 0;
+ oirq.args[3] = IRQ_TYPE_NONE;
+ oirq.args_count = 4;
+
+ q_spec->irq = irq_create_of_mapping(&oirq);
+ if (!q_spec->irq) {
+ dev_err(&q_chip->pdev->dev, "%s: invalid irq for gpio %u\n",
+ __func__, q_spec->pmic_pin);
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ return q_spec->irq;
+}
+
+static int qpnp_pin_get(struct gpio_chip *gpio_chip, unsigned int offset)
+{
+ struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_pin_spec *q_spec = NULL;
+ u8 buf, en_mask, shift, mask, reg;
+ unsigned int val;
+ int rc;
+
+ if (WARN_ON(!q_chip))
+ return -ENODEV;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ if (is_gpio_lv_mv(q_spec)) {
+ mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ } else {
+ mask = Q_REG_MODE_SEL_MASK;
+ shift = Q_REG_MODE_SEL_SHIFT;
+ }
+
+ /* gpio val is from RT status iff input is enabled */
+ if (q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], shift, mask)
+ == QPNP_PIN_MODE_DIG_IN) {
+ rc = regmap_read(q_chip->regmap,
+ Q_REG_ADDR(q_spec, Q_REG_STATUS1), &val);
+ if (rc)
+ return rc;
+ buf = val;
+
+ if (q_spec->type == Q_GPIO_TYPE && q_spec->dig_major_rev == 0)
+ en_mask = Q_REG_STATUS1_GPIO_EN_REV0_MASK;
+ else if (q_spec->type == Q_GPIO_TYPE &&
+ q_spec->dig_major_rev > 0)
+ en_mask = Q_REG_STATUS1_GPIO_EN_MASK;
+ else /* MPP */
+ en_mask = Q_REG_STATUS1_MPP_EN_MASK;
+
+ if (!(buf & en_mask))
+ return -EPERM;
+
+ return buf & Q_REG_STATUS1_VAL_MASK;
+ }
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ reg = q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_OUT_INVERT_SHIFT;
+ mask = Q_REG_OUT_INVERT_MASK;
+ reg = q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+
+ return (reg & mask) >> shift;
+}
+
+static int __qpnp_pin_set(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec, int value)
+{
+ int rc;
+ u8 shift, mask, *reg;
+ u16 address;
+
+ if (!q_chip || !q_spec)
+ return -EINVAL;
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ address = Q_REG_ADDR(q_spec, Q_REG_DIG_OUT_SRC_CTL);
+ } else {
+ shift = Q_REG_OUT_INVERT_SHIFT;
+ mask = Q_REG_OUT_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ address = Q_REG_ADDR(q_spec, Q_REG_MODE_CTL);
+ }
+
+ q_reg_clr_set(reg, shift, mask, !!value);
+
+ rc = regmap_write(q_chip->regmap, address, *reg);
+ if (rc)
+ dev_err(&q_chip->pdev->dev, "%s: spmi write failed\n",
+ __func__);
+ return rc;
+}
+
+
+static void qpnp_pin_set(struct gpio_chip *gpio_chip,
+ unsigned int offset, int value)
+{
+ struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_pin_spec *q_spec;
+
+ if (WARN_ON(!q_chip))
+ return;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return;
+
+ __qpnp_pin_set(q_chip, q_spec, value);
+}
+
+static int qpnp_pin_set_mode(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec, int mode)
+{
+ int rc;
+ u8 shift, mask;
+
+ if (!q_chip || !q_spec)
+ return -EINVAL;
+
+ if (qpnp_pin_check_config(Q_PIN_CFG_MODE, q_spec, mode)) {
+ pr_err("invalid mode specification %d\n", mode);
+ return -EINVAL;
+ }
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ } else {
+ shift = Q_REG_MODE_SEL_SHIFT;
+ mask = Q_REG_MODE_SEL_MASK;
+ }
+
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], shift, mask, mode);
+
+ rc = regmap_write(q_chip->regmap, Q_REG_ADDR(q_spec, Q_REG_MODE_CTL),
+ *&q_spec->regs[Q_REG_I_MODE_CTL]);
+ return rc;
+}
+
+static int qpnp_pin_direction_input(struct gpio_chip *gpio_chip,
+ unsigned int offset)
+{
+ struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_pin_spec *q_spec;
+
+ if (WARN_ON(!q_chip))
+ return -ENODEV;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ return qpnp_pin_set_mode(q_chip, q_spec, QPNP_PIN_MODE_DIG_IN);
+}
+
+static int qpnp_pin_direction_output(struct gpio_chip *gpio_chip,
+ unsigned int offset, int val)
+{
+ int rc;
+ struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_pin_spec *q_spec;
+
+ if (WARN_ON(!q_chip))
+ return -ENODEV;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ rc = __qpnp_pin_set(q_chip, q_spec, val);
+ if (rc)
+ return rc;
+
+ rc = qpnp_pin_set_mode(q_chip, q_spec, QPNP_PIN_MODE_DIG_OUT);
+
+ return rc;
+}
+
+static int qpnp_pin_of_gpio_xlate(struct gpio_chip *gpio_chip,
+ const struct of_phandle_args *gpio_spec,
+ u32 *flags)
+{
+ struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_pin_spec *q_spec;
+
+ if (WARN_ON(gpio_chip->of_gpio_n_cells < 2)) {
+ pr_err("of_gpio_n_cells < 2\n");
+ return -EINVAL;
+ }
+
+ q_spec = qpnp_pmic_pin_get_spec(q_chip, gpio_spec->args[0]);
+ if (!q_spec) {
+ pr_err("no such PMIC gpio %u in device topology\n",
+ gpio_spec->args[0]);
+ return -EINVAL;
+ }
+
+ if (flags)
+ *flags = gpio_spec->args[1];
+
+ return q_spec->gpio_chip_idx;
+}
+
+static int qpnp_pin_apply_config(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec)
+{
+ struct qpnp_pin_cfg param;
+ struct device_node *node = q_spec->node;
+ int rc;
+ u8 shift, mask, *reg;
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ } else {
+ shift = Q_REG_MODE_SEL_SHIFT;
+ mask = Q_REG_MODE_SEL_MASK;
+ }
+ param.mode = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL],
+ shift, mask);
+
+ param.output_type = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+ Q_REG_OUT_TYPE_SHIFT,
+ Q_REG_OUT_TYPE_MASK);
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_OUT_INVERT_SHIFT;
+ mask = Q_REG_OUT_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+ param.invert = q_reg_get(reg, shift, mask);
+
+ param.pull = q_reg_get(&q_spec->regs[Q_REG_I_DIG_PULL_CTL],
+ Q_REG_PULL_SHIFT, Q_REG_PULL_MASK);
+ param.vin_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_VIN_CTL],
+ Q_REG_VIN_SHIFT, Q_REG_VIN_MASK);
+ param.out_strength = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+ Q_REG_OUT_STRENGTH_SHIFT,
+ Q_REG_OUT_STRENGTH_MASK);
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_SRC_SEL_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_SRC_SEL_SHIFT;
+ mask = Q_REG_SRC_SEL_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+ param.src_sel = q_reg_get(reg, shift, mask);
+
+ param.master_en = q_reg_get(&q_spec->regs[Q_REG_I_EN_CTL],
+ Q_REG_MASTER_EN_SHIFT,
+ Q_REG_MASTER_EN_MASK);
+ param.aout_ref = q_reg_get(&q_spec->regs[Q_REG_I_AOUT_CTL],
+ Q_REG_AOUT_REF_SHIFT,
+ Q_REG_AOUT_REF_MASK);
+ param.ain_route = q_reg_get(&q_spec->regs[Q_REG_I_AIN_CTL],
+ Q_REG_AIN_ROUTE_SHIFT,
+ Q_REG_AIN_ROUTE_MASK);
+ param.cs_out = q_reg_get(&q_spec->regs[Q_REG_I_SINK_CTL],
+ Q_REG_CS_OUT_SHIFT,
+ Q_REG_CS_OUT_MASK);
+ param.apass_sel = q_reg_get(&q_spec->regs[Q_REG_I_APASS_SEL_CTL],
+ Q_REG_APASS_SEL_SHIFT,
+ Q_REG_APASS_SEL_MASK);
+ if (is_gpio_lv_mv(q_spec)) {
+ param.dtest_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT,
+ Q_REG_LV_MV_DTEST_SEL_CFG_MASK);
+ } else {
+ param.dtest_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_DTEST_SEL_SHIFT,
+ Q_REG_DTEST_SEL_MASK);
+ }
+
+ of_property_read_u32(node, "qcom,mode",
+ &param.mode);
+ of_property_read_u32(node, "qcom,output-type",
+ &param.output_type);
+ of_property_read_u32(node, "qcom,invert",
+ &param.invert);
+ of_property_read_u32(node, "qcom,pull",
+ &param.pull);
+ of_property_read_u32(node, "qcom,vin-sel",
+ &param.vin_sel);
+ of_property_read_u32(node, "qcom,out-strength",
+ &param.out_strength);
+ of_property_read_u32(node, "qcom,src-sel",
+ &param.src_sel);
+ of_property_read_u32(node, "qcom,master-en",
+ &param.master_en);
+ of_property_read_u32(node, "qcom,aout-ref",
+ &param.aout_ref);
+ of_property_read_u32(node, "qcom,ain-route",
+ &param.ain_route);
+ of_property_read_u32(node, "qcom,cs-out",
+ &param.cs_out);
+ of_property_read_u32(node, "qcom,apass-sel",
+ &param.apass_sel);
+ of_property_read_u32(node, "qcom,dtest-sel",
+ &param.dtest_sel);
+
+ rc = _qpnp_pin_config(q_chip, q_spec, &param);
+
+ return rc;
+}
+
+static int qpnp_pin_free_chip(struct qpnp_pin_chip *q_chip)
+{
+ int i, rc = 0;
+
+ if (q_chip->chip_gpios)
+ for (i = 0; i < q_chip->gpio_chip.ngpio; i++)
+ kfree(q_chip->chip_gpios[i]);
+
+ mutex_lock(&qpnp_pin_chips_lock);
+ list_del(&q_chip->chip_list);
+ mutex_unlock(&qpnp_pin_chips_lock);
+ if (q_chip->chip_registered)
+ gpiochip_remove(&q_chip->gpio_chip);
+
+ kfree(q_chip->chip_gpios);
+ kfree(q_chip->pmic_pins);
+ kfree(q_chip);
+ return rc;
+}
+
+#ifdef CONFIG_GPIO_QPNP_PIN_DEBUG
+struct qpnp_pin_reg {
+ uint32_t addr;
+ uint32_t idx;
+ uint32_t shift;
+ uint32_t mask;
+};
+
+static struct dentry *driver_dfs_dir;
+
+static int qpnp_pin_reg_attr(enum qpnp_pin_param_type type,
+ struct qpnp_pin_reg *cfg,
+ struct qpnp_pin_spec *q_spec)
+{
+ switch (type) {
+ case Q_PIN_CFG_MODE:
+ if (is_gpio_lv_mv(q_spec)) {
+ cfg->shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ cfg->mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ } else {
+ cfg->shift = Q_REG_MODE_SEL_SHIFT;
+ cfg->mask = Q_REG_MODE_SEL_MASK;
+ }
+ cfg->addr = Q_REG_MODE_CTL;
+ cfg->idx = Q_REG_I_MODE_CTL;
+ break;
+ case Q_PIN_CFG_OUTPUT_TYPE:
+ cfg->addr = Q_REG_DIG_OUT_CTL;
+ cfg->idx = Q_REG_I_DIG_OUT_CTL;
+ cfg->shift = Q_REG_OUT_TYPE_SHIFT;
+ cfg->mask = Q_REG_OUT_TYPE_MASK;
+ break;
+ case Q_PIN_CFG_INVERT:
+ if (is_gpio_lv_mv(q_spec)) {
+ cfg->addr = Q_REG_DIG_OUT_SRC_CTL;
+ cfg->idx = Q_REG_I_DIG_OUT_SRC_CTL;
+ cfg->shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ cfg->mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ } else {
+ cfg->addr = Q_REG_MODE_CTL;
+ cfg->idx = Q_REG_I_MODE_CTL;
+ cfg->shift = Q_REG_OUT_INVERT_SHIFT;
+ cfg->mask = Q_REG_OUT_INVERT_MASK;
+ }
+ break;
+ case Q_PIN_CFG_PULL:
+ cfg->addr = Q_REG_DIG_PULL_CTL;
+ cfg->idx = Q_REG_I_DIG_PULL_CTL;
+ cfg->shift = Q_REG_PULL_SHIFT;
+ cfg->mask = Q_REG_PULL_MASK;
+ break;
+ case Q_PIN_CFG_VIN_SEL:
+ cfg->addr = Q_REG_DIG_VIN_CTL;
+ cfg->idx = Q_REG_I_DIG_VIN_CTL;
+ cfg->shift = Q_REG_VIN_SHIFT;
+ cfg->mask = Q_REG_VIN_MASK;
+ break;
+ case Q_PIN_CFG_OUT_STRENGTH:
+ cfg->addr = Q_REG_DIG_OUT_CTL;
+ cfg->idx = Q_REG_I_DIG_OUT_CTL;
+ cfg->shift = Q_REG_OUT_STRENGTH_SHIFT;
+ cfg->mask = Q_REG_OUT_STRENGTH_MASK;
+ break;
+ case Q_PIN_CFG_SRC_SEL:
+ if (is_gpio_lv_mv(q_spec)) {
+ cfg->addr = Q_REG_DIG_OUT_SRC_CTL;
+ cfg->idx = Q_REG_I_DIG_OUT_SRC_CTL;
+ cfg->shift = Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT;
+ cfg->mask = Q_REG_DIG_OUT_SRC_SRC_SEL_MASK;
+ } else {
+ cfg->addr = Q_REG_MODE_CTL;
+ cfg->idx = Q_REG_I_MODE_CTL;
+ cfg->shift = Q_REG_SRC_SEL_SHIFT;
+ cfg->mask = Q_REG_SRC_SEL_MASK;
+ }
+ break;
+ case Q_PIN_CFG_MASTER_EN:
+ cfg->addr = Q_REG_EN_CTL;
+ cfg->idx = Q_REG_I_EN_CTL;
+ cfg->shift = Q_REG_MASTER_EN_SHIFT;
+ cfg->mask = Q_REG_MASTER_EN_MASK;
+ break;
+ case Q_PIN_CFG_AOUT_REF:
+ cfg->addr = Q_REG_AOUT_CTL;
+ cfg->idx = Q_REG_I_AOUT_CTL;
+ cfg->shift = Q_REG_AOUT_REF_SHIFT;
+ cfg->mask = Q_REG_AOUT_REF_MASK;
+ break;
+ case Q_PIN_CFG_AIN_ROUTE:
+ cfg->addr = Q_REG_AIN_CTL;
+ cfg->idx = Q_REG_I_AIN_CTL;
+ cfg->shift = Q_REG_AIN_ROUTE_SHIFT;
+ cfg->mask = Q_REG_AIN_ROUTE_MASK;
+ break;
+ case Q_PIN_CFG_CS_OUT:
+ cfg->addr = Q_REG_SINK_CTL;
+ cfg->idx = Q_REG_I_SINK_CTL;
+ cfg->shift = Q_REG_CS_OUT_SHIFT;
+ cfg->mask = Q_REG_CS_OUT_MASK;
+ break;
+ case Q_PIN_CFG_APASS_SEL:
+ cfg->addr = Q_REG_APASS_SEL_CTL;
+ cfg->idx = Q_REG_I_APASS_SEL_CTL;
+ cfg->shift = Q_REG_APASS_SEL_SHIFT;
+ cfg->mask = Q_REG_APASS_SEL_MASK;
+ break;
+ case Q_PIN_CFG_DTEST_SEL:
+ if (is_gpio_lv_mv(q_spec)) {
+ cfg->shift = Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT;
+ cfg->mask = Q_REG_LV_MV_DTEST_SEL_CFG_MASK;
+ } else {
+ cfg->shift = Q_REG_DTEST_SEL_SHIFT;
+ cfg->mask = Q_REG_DTEST_SEL_MASK;
+ }
+ cfg->addr = Q_REG_DIG_IN_CTL;
+ cfg->idx = Q_REG_I_DIG_IN_CTL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_pin_debugfs_get(void *data, u64 *val)
+{
+ enum qpnp_pin_param_type *idx = data;
+ struct qpnp_pin_spec *q_spec;
+ struct qpnp_pin_reg cfg = {};
+ int rc;
+
+ q_spec = container_of(idx, struct qpnp_pin_spec, params[*idx]);
+
+ rc = qpnp_pin_reg_attr(*idx, &cfg, q_spec);
+ if (rc)
+ return rc;
+
+ *val = q_reg_get(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask);
+ return 0;
+}
+
+static int qpnp_pin_debugfs_set(void *data, u64 val)
+{
+ enum qpnp_pin_param_type *idx = data;
+ struct qpnp_pin_spec *q_spec;
+ struct qpnp_pin_chip *q_chip;
+ struct qpnp_pin_reg cfg = {};
+ int rc;
+
+ q_spec = container_of(idx, struct qpnp_pin_spec, params[*idx]);
+ q_chip = q_spec->q_chip;
+
+ /*
+ * special handling for GPIO_LV/MV 'dtest-sel'
+ * if (dtest_sel == 0) then disable dtest-sel
+ * else enable and set dtest.
+ */
+ if ((q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_LV ||
+ q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_MV) &&
+ *idx == Q_PIN_CFG_DTEST_SEL) {
+ /* enable/disable DTEST */
+ cfg.shift = Q_REG_LV_MV_DTEST_SEL_EN_SHIFT;
+ cfg.mask = Q_REG_LV_MV_DTEST_SEL_EN_MASK;
+ cfg.addr = Q_REG_DIG_IN_CTL;
+ cfg.idx = Q_REG_I_DIG_IN_CTL;
+ q_reg_clr_set(&q_spec->regs[cfg.idx],
+ cfg.shift, cfg.mask, !!val);
+ }
+
+ rc = qpnp_pin_check_config(*idx, q_spec, val);
+ if (rc)
+ return rc;
+
+ rc = qpnp_pin_reg_attr(*idx, &cfg, q_spec);
+ if (rc)
+ return rc;
+
+ if (*idx == Q_PIN_CFG_DTEST_SEL && val) {
+ if (is_gpio_lv_mv(q_spec))
+ val -= 1;
+ else
+ val = BIT(val - 1);
+ }
+
+ q_reg_clr_set(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask, val);
+ rc = regmap_write(q_chip->regmap, Q_REG_ADDR(q_spec, cfg.addr),
+ *&q_spec->regs[cfg.idx]);
+
+ return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(qpnp_pin_fops, qpnp_pin_debugfs_get,
+ qpnp_pin_debugfs_set, "%llu\n");
+
+#define DEBUGFS_BUF_SIZE 11 /* supports 2^32 in decimal */
+
+struct qpnp_pin_debugfs_args {
+ enum qpnp_pin_param_type type;
+ const char *filename;
+};
+
+static struct qpnp_pin_debugfs_args dfs_args[Q_NUM_PARAMS] = {
+ { Q_PIN_CFG_MODE, "mode" },
+ { Q_PIN_CFG_OUTPUT_TYPE, "output_type" },
+ { Q_PIN_CFG_INVERT, "invert" },
+ { Q_PIN_CFG_PULL, "pull" },
+ { Q_PIN_CFG_VIN_SEL, "vin_sel" },
+ { Q_PIN_CFG_OUT_STRENGTH, "out_strength" },
+ { Q_PIN_CFG_SRC_SEL, "src_sel" },
+ { Q_PIN_CFG_MASTER_EN, "master_en" },
+ { Q_PIN_CFG_AOUT_REF, "aout_ref" },
+ { Q_PIN_CFG_AIN_ROUTE, "ain_route" },
+ { Q_PIN_CFG_CS_OUT, "cs_out" },
+ { Q_PIN_CFG_APASS_SEL, "apass_sel" },
+ { Q_PIN_CFG_DTEST_SEL, "dtest-sel" },
+};
+
+static int qpnp_pin_debugfs_create(struct qpnp_pin_chip *q_chip)
+{
+ struct platform_device *pdev = q_chip->pdev;
+ struct device *dev = &pdev->dev;
+ struct qpnp_pin_spec *q_spec;
+ enum qpnp_pin_param_type *params;
+ enum qpnp_pin_param_type type;
+ char pmic_pin[DEBUGFS_BUF_SIZE];
+ const char *filename;
+ struct dentry *dfs, *dfs_io_dir;
+ int i, j, rc;
+
+ q_chip->dfs_dir = debugfs_create_dir(q_chip->gpio_chip.label,
+ driver_dfs_dir);
+ if (q_chip->dfs_dir == NULL) {
+ dev_err(dev, "%s: cannot register chip debugfs directory %s\n",
+ __func__, dev->of_node->name);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < q_chip->gpio_chip.ngpio; i++) {
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, i);
+ params = q_spec->params;
+ snprintf(pmic_pin, DEBUGFS_BUF_SIZE, "%u", q_spec->pmic_pin);
+ dfs_io_dir = debugfs_create_dir(pmic_pin, q_chip->dfs_dir);
+ if (dfs_io_dir == NULL)
+ goto dfs_err;
+
+ for (j = 0; j < Q_NUM_PARAMS; j++) {
+ type = dfs_args[j].type;
+ filename = dfs_args[j].filename;
+
+ /*
+ * Use a value of '0' to see if the pin has even basic
+ * support for a function. Do not create a file if
+ * it doesn't.
+ */
+ rc = qpnp_pin_check_config(type, q_spec, 0);
+ if (rc == -ENXIO)
+ continue;
+
+ params[type] = type;
+ dfs = debugfs_create_file(filename, 0644, dfs_io_dir,
+ &q_spec->params[type], &qpnp_pin_fops);
+ if (dfs == NULL)
+ goto dfs_err;
+ }
+ }
+ return 0;
+dfs_err:
+ dev_err(dev, "%s: cannot register debugfs for pmic gpio %u on chip %s\n",
+ __func__, q_spec->pmic_pin, dev->of_node->name);
+ debugfs_remove_recursive(q_chip->dfs_dir);
+ return -ENFILE;
+}
+#else
+static int qpnp_pin_debugfs_create(struct qpnp_pin_chip *q_chip)
+{
+ return 0;
+}
+#endif
+
+static int qpnp_pin_is_valid_pin(struct qpnp_pin_spec *q_spec)
+{
+ if (q_spec->type == Q_GPIO_TYPE)
+ switch (q_spec->subtype) {
+ case Q_GPIO_SUBTYPE_GPIO_4CH:
+ case Q_GPIO_SUBTYPE_GPIOC_4CH:
+ case Q_GPIO_SUBTYPE_GPIO_8CH:
+ case Q_GPIO_SUBTYPE_GPIOC_8CH:
+ case Q_GPIO_SUBTYPE_GPIO_LV:
+ case Q_GPIO_SUBTYPE_GPIO_MV:
+ return 1;
+ }
+ else if (q_spec->type == Q_MPP_TYPE)
+ switch (q_spec->subtype) {
+ case Q_MPP_SUBTYPE_4CH_NO_ANA_OUT:
+ case Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT:
+ case Q_MPP_SUBTYPE_4CH_NO_SINK:
+ case Q_MPP_SUBTYPE_ULT_4CH_NO_SINK:
+ case Q_MPP_SUBTYPE_4CH_FULL_FUNC:
+ case Q_MPP_SUBTYPE_8CH_FULL_FUNC:
+ return 1;
+ }
+
+ return 0;
+}
+
+static int qpnp_pin_probe(struct platform_device *pdev)
+{
+ struct qpnp_pin_chip *q_chip;
+ struct qpnp_pin_spec *q_spec;
+ unsigned int base;
+ struct device_node *child;
+ int i, rc;
+ u32 lowest_gpio = UINT_MAX, highest_gpio = 0;
+ u32 gpio;
+ char version[Q_REG_SUBTYPE - Q_REG_DIG_MAJOR_REV + 1];
+ const char *pin_dev_name;
+
+ pin_dev_name = dev_name(&pdev->dev);
+ if (!pin_dev_name) {
+ dev_err(&pdev->dev,
+ "%s: label binding undefined for node %s\n",
+ __func__,
+ pdev->dev.of_node->full_name);
+ return -EINVAL;
+ }
+
+ q_chip = kzalloc(sizeof(*q_chip), GFP_KERNEL);
+ if (!q_chip)
+ return -ENOMEM;
+
+ q_chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!q_chip->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+ q_chip->pdev = pdev;
+ dev_set_drvdata(&pdev->dev, q_chip);
+
+ mutex_lock(&qpnp_pin_chips_lock);
+ list_add(&q_chip->chip_list, &qpnp_pin_chips);
+ mutex_unlock(&qpnp_pin_chips_lock);
+
+ /* first scan through nodes to find the range required for allocation */
+ i = 0;
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "qcom,pin-num", &gpio);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "%s: unable to get qcom,pin-num property\n",
+ __func__);
+ goto err_probe;
+ }
+
+ if (gpio < lowest_gpio)
+ lowest_gpio = gpio;
+ if (gpio > highest_gpio)
+ highest_gpio = gpio;
+ i++;
+ }
+ q_chip->gpio_chip.ngpio = i;
+
+ if (highest_gpio < lowest_gpio) {
+ dev_err(&pdev->dev,
+ "%s: no device nodes specified in topology\n",
+ __func__);
+ rc = -EINVAL;
+ goto err_probe;
+ } else if (lowest_gpio == 0) {
+ dev_err(&pdev->dev, "%s: 0 is not a valid PMIC GPIO\n",
+ __func__);
+ rc = -EINVAL;
+ goto err_probe;
+ }
+
+ q_chip->pmic_pin_lowest = lowest_gpio;
+ q_chip->pmic_pin_highest = highest_gpio;
+
+ /* allocate gpio lookup tables */
+ q_chip->pmic_pins = kzalloc(sizeof(struct qpnp_pin_spec *) *
+ (highest_gpio - lowest_gpio + 1),
+ GFP_KERNEL);
+ q_chip->chip_gpios = kzalloc(sizeof(struct qpnp_pin_spec *) *
+ q_chip->gpio_chip.ngpio,
+ GFP_KERNEL);
+ if (!q_chip->pmic_pins || !q_chip->chip_gpios) {
+ dev_err(&pdev->dev, "%s: unable to allocate memory\n",
+ __func__);
+ rc = -ENOMEM;
+ goto err_probe;
+ }
+
+ /* get interrupt controller device_node */
+ q_chip->int_ctrl = of_irq_find_parent(pdev->dev.of_node);
+ if (!q_chip->int_ctrl) {
+ dev_err(&pdev->dev, "%s: Can't find interrupt parent\n",
+ __func__);
+ rc = -EINVAL;
+ goto err_probe;
+ }
+ i = 0;
+ /* now scan through again and populate the lookup table */
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ child->full_name, rc);
+ goto err_probe;
+ }
+
+ rc = of_property_read_u32(child, "qcom,pin-num", &gpio);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "%s: unable to get qcom,pin-num property\n",
+ __func__);
+ goto err_probe;
+ }
+
+ q_spec = kzalloc(sizeof(struct qpnp_pin_spec), GFP_KERNEL);
+ if (!q_spec) {
+ rc = -ENOMEM;
+ goto err_probe;
+ }
+
+ q_spec->slave = to_spmi_device(pdev->dev.parent)->usid;
+ q_spec->offset = base;
+ q_spec->gpio_chip_idx = i;
+ q_spec->pmic_pin = gpio;
+ q_spec->node = child;
+ q_spec->q_chip = q_chip;
+
+ rc = regmap_bulk_read(q_chip->regmap,
+ Q_REG_ADDR(q_spec, Q_REG_DIG_MAJOR_REV),
+ &version[0],
+ ARRAY_SIZE(version));
+ if (rc) {
+ dev_err(&pdev->dev, "%s: unable to read type regs\n",
+ __func__);
+ goto err_probe;
+ }
+ q_spec->dig_major_rev = version[Q_REG_DIG_MAJOR_REV -
+ Q_REG_DIG_MAJOR_REV];
+ q_spec->type = version[Q_REG_TYPE - Q_REG_DIG_MAJOR_REV];
+ q_spec->subtype = version[Q_REG_SUBTYPE - Q_REG_DIG_MAJOR_REV];
+
+ if (!qpnp_pin_is_valid_pin(q_spec)) {
+ dev_err(&pdev->dev,
+ "%s: invalid pin type (type=0x%x subtype=0x%x)\n",
+ __func__, q_spec->type, q_spec->subtype);
+ goto err_probe;
+ }
+
+ rc = qpnp_pin_ctl_regs_init(q_spec);
+ if (rc)
+ goto err_probe;
+
+ /* initialize lookup table params */
+ qpnp_pmic_pin_set_spec(q_chip, gpio, q_spec);
+ qpnp_chip_gpio_set_spec(q_chip, i, q_spec);
+ i++;
+ }
+
+ q_chip->gpio_chip.base = -1;
+ q_chip->gpio_chip.label = pin_dev_name;
+ q_chip->gpio_chip.direction_input = qpnp_pin_direction_input;
+ q_chip->gpio_chip.direction_output = qpnp_pin_direction_output;
+ q_chip->gpio_chip.to_irq = qpnp_pin_to_irq;
+ q_chip->gpio_chip.get = qpnp_pin_get;
+ q_chip->gpio_chip.set = qpnp_pin_set;
+ q_chip->gpio_chip.dev = &pdev->dev;
+ q_chip->gpio_chip.of_xlate = qpnp_pin_of_gpio_xlate;
+ q_chip->gpio_chip.of_gpio_n_cells = 2;
+ q_chip->gpio_chip.can_sleep = 0;
+
+ rc = gpiochip_add(&q_chip->gpio_chip);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: Can't add gpio chip, rc = %d\n",
+ __func__, rc);
+ goto err_probe;
+ }
+
+ q_chip->chip_registered = true;
+ /* now configure gpio config defaults if they exist */
+ for (i = 0; i < q_chip->gpio_chip.ngpio; i++) {
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, i);
+ if (WARN_ON(!q_spec)) {
+ rc = -ENODEV;
+ goto err_probe;
+ }
+
+ rc = qpnp_pin_cache_regs(q_chip, q_spec);
+ if (rc)
+ goto err_probe;
+
+ rc = qpnp_pin_apply_config(q_chip, q_spec);
+ if (rc)
+ goto err_probe;
+ }
+
+ dev_dbg(&pdev->dev, "%s: gpio_chip registered between %d-%u\n",
+ __func__, q_chip->gpio_chip.base,
+ (q_chip->gpio_chip.base + q_chip->gpio_chip.ngpio) - 1);
+
+ rc = qpnp_pin_debugfs_create(q_chip);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: debugfs creation failed\n",
+ __func__);
+ goto err_probe;
+ }
+
+ return 0;
+
+err_probe:
+ qpnp_pin_free_chip(q_chip);
+ return rc;
+}
+
+static int qpnp_pin_remove(struct platform_device *pdev)
+{
+ struct qpnp_pin_chip *q_chip = dev_get_drvdata(&pdev->dev);
+
+ debugfs_remove_recursive(q_chip->dfs_dir);
+
+ return qpnp_pin_free_chip(q_chip);
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = "qcom,qpnp-pin",
+ },
+ {}
+};
+
+static const struct platform_device_id qpnp_pin_id[] = {
+ { "qcom,qpnp-pin", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_pin_id);
+
+static struct platform_driver qpnp_pin_driver = {
+ .driver = {
+ .name = "qcom,qpnp-pin",
+ .of_match_table = spmi_match_table,
+ },
+ .probe = qpnp_pin_probe,
+ .remove = qpnp_pin_remove,
+ .id_table = qpnp_pin_id,
+};
+
+static int __init qpnp_pin_init(void)
+{
+#ifdef CONFIG_GPIO_QPNP_PIN_DEBUG
+ driver_dfs_dir = debugfs_create_dir("qpnp_pin", NULL);
+ if (driver_dfs_dir == NULL)
+ pr_err("Cannot register top level debugfs directory\n");
+#endif
+
+ return platform_driver_register(&qpnp_pin_driver);
+}
+
+static void __exit qpnp_pin_exit(void)
+{
+#ifdef CONFIG_GPIO_QPNP_PIN_DEBUG
+ debugfs_remove_recursive(driver_dfs_dir);
+#endif
+ platform_driver_unregister(&qpnp_pin_driver);
+}
+
+MODULE_DESCRIPTION("QPNP PMIC gpio driver");
+MODULE_LICENSE("GPL v2");
+
+subsys_initcall(qpnp_pin_init);
+module_exit(qpnp_pin_exit);