| /* arch/arm/mach-msm/smp2p_gpio.c |
| * |
| * Copyright (c) 2013, 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/irq.h> |
| #include <linux/irqdomain.h> |
| #include <linux/slab.h> |
| #include <linux/list.h> |
| #include <mach/msm_ipc_logging.h> |
| #include "smp2p_private_api.h" |
| #include "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; |
| 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; |
| int ret; |
| |
| if (!cp) |
| return; |
| |
| chip = container_of(cp, struct smp2p_chip_dev, gpio); |
| if (!chip->is_open) |
| return; |
| |
| if (chip->is_inbound) { |
| SMP2P_ERR("%s: '%s':%d virq %d invalid operation\n", |
| __func__, chip->name, chip->remote_pid, |
| chip->irq_base + offset); |
| return; |
| } |
| |
| if (value) { |
| data_set = 1 << offset; |
| data_clear = 0; |
| } else { |
| data_set = 0; |
| data_clear = 1 << offset; |
| } |
| |
| ret = msm_smp2p_out_modify(chip->out_handle, |
| data_set, data_clear); |
| |
| 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 %p\n", __func__, domain_ptr); |
| 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); |
| set_irq_flags(virq, IRQF_VALID); |
| |
| 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_GPIO( |
| "'%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 i; |
| |
| /* map GPIO pins to interrupts */ |
| chip->irq_domain = irq_domain_add_nomap(node, 0, |
| &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); |
| return; |
| } |
| |
| for (i = 0; i < SMP2P_BITS_PER_ENTRY; ++i) { |
| unsigned int virt_irq; |
| |
| virt_irq = irq_create_direct_mapping(chip->irq_domain); |
| if (virt_irq == NO_IRQ) { |
| SMP2P_ERR("%s: gpio->virt IRQ mapping failed '%s':%d\n", |
| __func__, chip->name, chip->remote_pid); |
| } else if (!chip->irq_base) { |
| chip->irq_base = virt_irq; |
| } |
| } |
| } |
| |
| /** |
| * 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\n", __func__, |
| chip->name, chip->remote_pid); |
| 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; |
| } |
| |
| /** |
| * 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 __devinit 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); |
| |
| /* 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 fail; |
| } |
| } 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 fail; |
| } |
| } |
| |
| 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; |
| |
| 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[] __devinitdata = { |
| {.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 __devinit 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"); |