836 lines
20 KiB
C
836 lines
20 KiB
C
/* drivers/gpio/gpio-msm-smp2p.c
|
|
*
|
|
* Copyright (c) 2013-2014, 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 %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_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(chip->irq_domain->of_node));
|
|
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");
|