diff options
Diffstat (limited to 'drivers/gpio')
-rw-r--r-- | drivers/gpio/Kconfig | 19 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpio/gpio-msm-smp2p-test.c | 762 | ||||
-rw-r--r-- | drivers/gpio/gpio-msm-smp2p.c | 835 | ||||
-rw-r--r-- | drivers/gpio/gpiolib-acpi.c | 2 | ||||
-rw-r--r-- | drivers/gpio/gpiolib.c | 9 | ||||
-rw-r--r-- | drivers/gpio/qpnp-pin.c | 1714 |
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", + ¶m.mode); + of_property_read_u32(node, "qcom,output-type", + ¶m.output_type); + of_property_read_u32(node, "qcom,invert", + ¶m.invert); + of_property_read_u32(node, "qcom,pull", + ¶m.pull); + of_property_read_u32(node, "qcom,vin-sel", + ¶m.vin_sel); + of_property_read_u32(node, "qcom,out-strength", + ¶m.out_strength); + of_property_read_u32(node, "qcom,src-sel", + ¶m.src_sel); + of_property_read_u32(node, "qcom,master-en", + ¶m.master_en); + of_property_read_u32(node, "qcom,aout-ref", + ¶m.aout_ref); + of_property_read_u32(node, "qcom,ain-route", + ¶m.ain_route); + of_property_read_u32(node, "qcom,cs-out", + ¶m.cs_out); + of_property_read_u32(node, "qcom,apass-sel", + ¶m.apass_sel); + of_property_read_u32(node, "qcom,dtest-sel", + ¶m.dtest_sel); + + rc = _qpnp_pin_config(q_chip, q_spec, ¶m); + + 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); |