diff options
Diffstat (limited to 'drivers/gpio/gpio-msm-smp2p.c')
| -rw-r--r-- | drivers/gpio/gpio-msm-smp2p.c | 835 |
1 files changed, 835 insertions, 0 deletions
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"); |
