701 lines
18 KiB
C
701 lines
18 KiB
C
/*
|
|
* drivers/gpio/gpio-tmpm32xi2c.c
|
|
*
|
|
* Copyright (c) 2015-2017, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#ifdef CONFIG_OF_GPIO
|
|
#include <linux/of_platform.h>
|
|
#endif
|
|
#include <linux/mfd/tmpm32xi2c.h>
|
|
|
|
#define TMPM_MAX_PORT 112
|
|
#define TMPM_MAX_INTR_PORT 8
|
|
#define TMPM_BANK_SZ 8
|
|
|
|
#define TMPM_MAX_BANK (TMPM_MAX_PORT / TMPM_BANK_SZ)
|
|
#define TMPM_MAX_INTR_BANK (TMPM_MAX_INTR_PORT / TMPM_BANK_SZ)
|
|
|
|
#if ((TMPM_MAX_PORT % TMPM_BANK_SZ) || (TMPM_MAX_INTR_PORT % TMPM_BANK_SZ))
|
|
#error "Please set TMPM_MAX_PORT with BANK size as a unit!"
|
|
#endif
|
|
|
|
#define TMPM_GET_BANK(_A, _pos) (_A[(_pos) / TMPM_BANK_SZ])
|
|
#define TMPM_GET_BANKOFFSET(_pos) ((_pos) % TMPM_BANK_SZ)
|
|
|
|
#define TMPM_SET_BIT(_A, _pos) \
|
|
(_A[(_pos) / TMPM_BANK_SZ] |= (1 << TMPM_GET_BANKOFFSET(_pos)))
|
|
#define TMPM_CLR_BIT(_A, _pos) \
|
|
(_A[(_pos) / TMPM_BANK_SZ] &= ~(1 << TMPM_GET_BANKOFFSET(_pos)))
|
|
#define TMPM_TEST_BIT(_A, _pos) \
|
|
(_A[(_pos) / TMPM_BANK_SZ] & (1 << TMPM_GET_BANKOFFSET(_pos)))
|
|
|
|
struct tmpm32xi2c_intr_map {
|
|
/* index: intr-num, data: gpio-num */
|
|
const uint32_t iidg[TMPM_MAX_INTR_PORT];
|
|
/* index: gpio-num, data: intr-num */
|
|
uint32_t igdi[TMPM_MAX_PORT];
|
|
};
|
|
|
|
/*
|
|
* Currently MCU firmware supports only 8 interrupt sources including
|
|
* one reserved.
|
|
*
|
|
* *---------------------------------------*
|
|
* | intr source | GPIO offset |
|
|
* *---------------------------------------*
|
|
* | 0 | 24 - TMPM32X_GPIO(D, 0) |
|
|
* | 1 | 25 - TMPM32X_GPIO(D, 1) |
|
|
* | 2 | 35 - TMPM32X_GPIO(H, 6) |
|
|
* | 3 | 27 - TMPM32X_GPIO(D, 3) |
|
|
* | 4 | 28 - TMPM32X_GPIO(D, 4) |
|
|
* | 5 | 29 - TMPM32X_GPIO(D, 5) |
|
|
* | 6 | 81 - TMPM32X_GPIO(K, 1) |
|
|
* | 7 | N/A |
|
|
* *---------------------------------------*
|
|
*/
|
|
static struct tmpm32xi2c_intr_map intr_map = {
|
|
{ 24, 25, 35, 27, 28, 29, 81, ~0 }, { ~0, }
|
|
};
|
|
|
|
#define TMPM_GET_GPIO_NUM(index) (intr_map.iidg[(index)])
|
|
#define TMPM_GET_INTR_NUM(index) (intr_map.igdi[(index)])
|
|
|
|
struct tmpm32xi2c_gpio_data {
|
|
struct device *dev;
|
|
struct gpio_chip gc;
|
|
|
|
struct mutex lock;
|
|
struct mutex irq_lock;
|
|
int irq;
|
|
unsigned long irq_flags;
|
|
|
|
/* for interrupt */
|
|
uint8_t irq_available[TMPM_MAX_BANK];
|
|
uint8_t reg_direction_intr[TMPM_MAX_INTR_BANK];
|
|
uint8_t irq_mask_cache[TMPM_MAX_INTR_BANK];
|
|
uint8_t irq_mask[TMPM_MAX_INTR_BANK];
|
|
uint8_t irq_trig_raise[TMPM_MAX_INTR_BANK];
|
|
uint8_t irq_trig_fall[TMPM_MAX_INTR_BANK];
|
|
|
|
uint8_t irq_pending[TMPM_MAX_BANK];
|
|
uint8_t irq_is_pending;
|
|
|
|
/* for direction output */
|
|
uint8_t dir_output[TMPM_MAX_BANK];
|
|
uint8_t dir_output_init[TMPM_MAX_BANK];
|
|
uint8_t output_val[TMPM_MAX_BANK];
|
|
uint8_t output_val_init[TMPM_MAX_BANK];
|
|
};
|
|
|
|
static void tmpm32xi2c_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
|
|
int val);
|
|
|
|
static inline
|
|
struct tmpm32xi2c_gpio_data *gc_to_tmpm32xi2c_gpio(struct gpio_chip *gpio_chip)
|
|
{
|
|
return container_of(gpio_chip, struct tmpm32xi2c_gpio_data, gc);
|
|
}
|
|
|
|
static int tmpm32xi2c_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
|
|
{
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
|
|
if (TMPM_TEST_BIT(data->irq_available, offset))
|
|
return irq_find_mapping(gc->irqdomain, offset);
|
|
|
|
dev_dbg(data->dev, "%s: offset[%u] is not available for irq\n",
|
|
__func__, offset);
|
|
return 0;
|
|
}
|
|
|
|
static int __tmpm32xi2c_gpio_direction_input(struct tmpm32xi2c_gpio_data *data,
|
|
unsigned int offset)
|
|
{
|
|
struct tmpm32xi2c_chip *chip = dev_get_drvdata(data->dev->parent);
|
|
uint32_t intr_num;
|
|
u8 tx_buf[] = { CMD_PIN_IN, 0 /*pin*/ };
|
|
int update = 0;
|
|
int ret;
|
|
|
|
dev_dbg(data->dev, "%s: offset[%u]\n", __func__, offset);
|
|
|
|
if (!TMPM_TEST_BIT(data->dir_output_init, offset))
|
|
update = 1;
|
|
else if (TMPM_TEST_BIT(data->dir_output, offset))
|
|
update = 1;
|
|
|
|
if (!update)
|
|
return 0;
|
|
|
|
tx_buf[1] = (u8)offset;
|
|
ret = chip->write_read(chip, tx_buf, sizeof(tx_buf), NULL, 0);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
TMPM_CLR_BIT(data->dir_output, offset);
|
|
intr_num = TMPM_GET_INTR_NUM(offset);
|
|
if (intr_num != ~0)
|
|
TMPM_SET_BIT(data->reg_direction_intr, intr_num);
|
|
TMPM_SET_BIT(data->dir_output_init, offset);
|
|
|
|
ret = 0;
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static int tmpm32xi2c_gpio_direction_input(struct gpio_chip *gc,
|
|
unsigned int offset)
|
|
{
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
int ret;
|
|
|
|
mutex_lock(&data->lock);
|
|
ret = __tmpm32xi2c_gpio_direction_input(data, offset);
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int __tmpm32xi2c_gpio_direction_output(struct tmpm32xi2c_gpio_data *data,
|
|
unsigned int offset, int val)
|
|
{
|
|
struct tmpm32xi2c_chip *chip = dev_get_drvdata(data->dev->parent);
|
|
uint32_t intr_num;
|
|
u8 tx_buf[] = { CMD_PIN_OUT, 0 /*pin*/, 0 /*val*/ };
|
|
int update = 0;
|
|
int ret;
|
|
|
|
dev_dbg(data->dev, "%s: offset[%u], val[%d]\n", __func__, offset, val);
|
|
|
|
if (!TMPM_TEST_BIT(data->dir_output_init, offset))
|
|
update = 1;
|
|
else if (!TMPM_TEST_BIT(data->dir_output, offset))
|
|
update = 1;
|
|
|
|
if (!update)
|
|
return 0;
|
|
|
|
tx_buf[1] = (u8)offset;
|
|
tx_buf[2] = val ? 1 : 0;
|
|
|
|
/* set output level */
|
|
ret = chip->write_read(chip, tx_buf, sizeof(tx_buf), NULL, 0);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
TMPM_SET_BIT(data->dir_output, offset);
|
|
intr_num = TMPM_GET_INTR_NUM(offset);
|
|
if (intr_num != ~0)
|
|
TMPM_CLR_BIT(data->reg_direction_intr, intr_num);
|
|
TMPM_SET_BIT(data->dir_output_init, offset);
|
|
ret = 0;
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static int tmpm32xi2c_gpio_direction_output(struct gpio_chip *gc,
|
|
unsigned int offset, int val)
|
|
{
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
int ret;
|
|
|
|
mutex_lock(&data->lock);
|
|
ret = __tmpm32xi2c_gpio_direction_output(data, offset, val);
|
|
mutex_unlock(&data->lock);
|
|
|
|
/* WAR: CMD_PIN_OUT doesn't set the output value with latest F/W */
|
|
tmpm32xi2c_gpio_set_value(gc, offset, val);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __tmpm32xi2c_gpio_get_value(struct tmpm32xi2c_gpio_data *data,
|
|
unsigned int offset)
|
|
{
|
|
struct tmpm32xi2c_chip *chip = dev_get_drvdata(data->dev->parent);
|
|
int ret;
|
|
|
|
dev_dbg(data->dev, "%s: offset[%u]\n", __func__, offset);
|
|
|
|
if (TMPM_TEST_BIT(data->dir_output, offset)) {
|
|
ret = TMPM_TEST_BIT(data->output_val, offset) ? 1 : 0;
|
|
} else {
|
|
u8 tx_buf[] = { CMD_PIN_RD, 0 /*pin*/ };
|
|
u8 rx_buf[] = { 0 /*val*/ };
|
|
|
|
tx_buf[1] = (u8)offset;
|
|
ret = chip->write_read(chip, tx_buf, sizeof(tx_buf),
|
|
rx_buf, sizeof(rx_buf));
|
|
if (ret < 0)
|
|
return 0;
|
|
|
|
ret = rx_buf[0] ? 1 : 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tmpm32xi2c_gpio_get_value(struct gpio_chip *gc, unsigned int offset)
|
|
{
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
int ret;
|
|
|
|
mutex_lock(&data->lock);
|
|
ret = __tmpm32xi2c_gpio_get_value(data, offset);
|
|
mutex_unlock(&data->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __tmpm32xi2c_gpio_set_value(struct tmpm32xi2c_gpio_data *data,
|
|
unsigned int offset, int val)
|
|
{
|
|
struct tmpm32xi2c_chip *chip = dev_get_drvdata(data->dev->parent);
|
|
u8 tx_buf[] = { CMD_PIN_WR, 0 /*pin*/, 0 /*val*/};
|
|
int update = 0;
|
|
|
|
dev_dbg(data->dev, "%s: offset[%u], val[%d]\n", __func__, offset, val);
|
|
|
|
if (!TMPM_TEST_BIT(data->output_val_init, offset))
|
|
update = 1;
|
|
else if (TMPM_TEST_BIT(data->output_val, offset) && !val)
|
|
update = 1;
|
|
else if (!TMPM_TEST_BIT(data->output_val, offset) && val)
|
|
update = 1;
|
|
|
|
if (!update)
|
|
return;
|
|
|
|
tx_buf[1] = (u8)offset;
|
|
tx_buf[2] = val ? 1 : 0;
|
|
|
|
if (chip->write_read(chip, tx_buf, sizeof(tx_buf), NULL, 0) < 0)
|
|
return;
|
|
|
|
if (val)
|
|
TMPM_SET_BIT(data->output_val, offset);
|
|
else
|
|
TMPM_CLR_BIT(data->output_val, offset);
|
|
TMPM_SET_BIT(data->output_val_init, offset);
|
|
}
|
|
|
|
static void tmpm32xi2c_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
|
|
int val)
|
|
{
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
|
|
mutex_lock(&data->lock);
|
|
__tmpm32xi2c_gpio_set_value(data, offset, val);
|
|
mutex_unlock(&data->lock);
|
|
}
|
|
|
|
static int tmpm32xi2c_gpio_irq_pending(struct tmpm32xi2c_gpio_data *data)
|
|
{
|
|
struct tmpm32xi2c_chip *chip = dev_get_drvdata(data->dev->parent);
|
|
uint8_t *pending = &data->irq_pending[0];
|
|
uint8_t tx_buf[] = { CMD_INT_REG, 0 /* dummy */ };
|
|
uint8_t rx_buf[TMPM_MAX_INTR_BANK * 2] = { 0, };
|
|
int pendings = 0;
|
|
int i;
|
|
int ret = -1;
|
|
|
|
ret = chip->write_read(chip, tx_buf, sizeof(tx_buf),
|
|
rx_buf, sizeof(rx_buf));
|
|
if (ret < 0)
|
|
return 0;
|
|
|
|
mutex_lock(&data->irq_lock);
|
|
for (i = 0; i < TMPM_MAX_INTR_BANK; i++) {
|
|
uint8_t cur_stat;
|
|
uint8_t trig_raise, trig_fall;
|
|
uint8_t irq_trig_raise, irq_trig_fall;
|
|
|
|
cur_stat = (rx_buf[i * 2] & data->reg_direction_intr[i]) &
|
|
data->irq_mask_cache[i];
|
|
trig_raise = cur_stat & rx_buf[i * 2 + 1];
|
|
trig_fall = cur_stat & ~(rx_buf[i * 2 + 1]);
|
|
irq_trig_raise = cur_stat & data->irq_trig_raise[i];
|
|
irq_trig_fall = cur_stat & data->irq_trig_fall[i];
|
|
|
|
if (!trig_raise && !trig_fall)
|
|
continue;
|
|
|
|
if (irq_trig_raise || irq_trig_fall) {
|
|
/* Check pending irq based on configured irq type. */
|
|
pending[i] = (trig_raise & irq_trig_raise) |
|
|
(trig_fall & irq_trig_fall);
|
|
} else {
|
|
/*
|
|
* If there was no configured irq type, consider any
|
|
* triggered irq types as pending irq.
|
|
*/
|
|
pending[i] = (trig_raise | trig_fall);
|
|
}
|
|
pendings += (pending[i] ? 1 : 0);
|
|
}
|
|
mutex_unlock(&data->irq_lock);
|
|
|
|
return pendings;
|
|
}
|
|
|
|
static irqreturn_t tmpm32xi2c_gpio_irq_handler(int irq, void *devid)
|
|
{
|
|
struct tmpm32xi2c_gpio_data *data =
|
|
(struct tmpm32xi2c_gpio_data *)devid;
|
|
unsigned int nhandled = 0;
|
|
unsigned int gpio_irq = 0;
|
|
uint8_t level;
|
|
int pendings;
|
|
int i;
|
|
|
|
pendings = tmpm32xi2c_gpio_irq_pending(data);
|
|
if (!data->irq_is_pending && !pendings)
|
|
return IRQ_NONE;
|
|
|
|
for (i = 0; i < TMPM_MAX_INTR_BANK; i++) {
|
|
while (data->irq_pending[i]) {
|
|
level = __ffs(data->irq_pending[i]);
|
|
gpio_irq = irq_find_mapping(data->gc.irqdomain,
|
|
TMPM_GET_GPIO_NUM(level));
|
|
handle_nested_irq(gpio_irq);
|
|
data->irq_pending[i] &= ~(1 << level);
|
|
nhandled++;
|
|
}
|
|
}
|
|
data->irq_is_pending = 0;
|
|
|
|
return (nhandled > 0) ? IRQ_HANDLED : IRQ_NONE;
|
|
}
|
|
|
|
static void
|
|
tmpm32xi2c_gpio_update_irq_mask_reg(struct tmpm32xi2c_gpio_data *data)
|
|
{
|
|
struct tmpm32xi2c_chip *chip = dev_get_drvdata(data->dev->parent);
|
|
uint8_t tx_mask[] = { CMD_INT_MASK, 0x0 /*value*/ };
|
|
uint8_t irq_mask_cache;
|
|
uint8_t level;
|
|
uint8_t new_irqs;
|
|
int i;
|
|
|
|
for (i = 0; i < TMPM_MAX_INTR_BANK; i++) {
|
|
irq_mask_cache = data->irq_mask_cache[i];
|
|
new_irqs = ~irq_mask_cache & data->reg_direction_intr[i];
|
|
|
|
while (new_irqs) {
|
|
level = __ffs(new_irqs);
|
|
mutex_lock(&data->lock);
|
|
__tmpm32xi2c_gpio_direction_input(
|
|
data, TMPM_GET_GPIO_NUM(level));
|
|
mutex_unlock(&data->lock);
|
|
new_irqs &= ~(1 << level);
|
|
}
|
|
|
|
if (irq_mask_cache != data->irq_mask[i]) {
|
|
tx_mask[1] = data->irq_mask[i] = irq_mask_cache;
|
|
chip->write_read(chip, tx_mask, sizeof(tx_mask),
|
|
NULL, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void tmpm32xi2c_gpio_irq_mask(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
uint32_t intr_num = TMPM_GET_INTR_NUM(d->hwirq);
|
|
|
|
TMPM_CLR_BIT(data->irq_mask_cache, intr_num);
|
|
}
|
|
|
|
static void tmpm32xi2c_gpio_irq_unmask(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
uint32_t intr_num = TMPM_GET_INTR_NUM(d->hwirq);
|
|
|
|
TMPM_SET_BIT(data->irq_mask_cache, intr_num);
|
|
}
|
|
|
|
static void tmpm32xi2c_gpio_irq_bus_lock(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
|
|
mutex_lock(&data->irq_lock);
|
|
}
|
|
|
|
static void tmpm32xi2c_gpio_irq_bus_sync_unlock(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
|
|
tmpm32xi2c_gpio_update_irq_mask_reg(data);
|
|
mutex_unlock(&data->irq_lock);
|
|
}
|
|
|
|
static int tmpm32xi2c_gpio_irq_set_type(struct irq_data *d, unsigned int type)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct tmpm32xi2c_gpio_data *data = gc_to_tmpm32xi2c_gpio(gc);
|
|
uint32_t intr_num;
|
|
|
|
dev_dbg(data->dev, "%s: irq=%u, hwirq=%lu, type=%u\n",
|
|
__func__, d->irq, d->hwirq, type);
|
|
|
|
if (!TMPM_TEST_BIT(data->irq_available, d->hwirq))
|
|
return 0;
|
|
|
|
intr_num = TMPM_GET_INTR_NUM(d->hwirq);
|
|
if (type & ~IRQ_TYPE_SENSE_MASK)
|
|
return -EINVAL;
|
|
|
|
/* FIXME: LEVEL type is not supported yet in current MCU firmware. */
|
|
if (!(type & IRQ_TYPE_EDGE_BOTH) && (type & IRQ_TYPE_LEVEL_MASK))
|
|
return 0;
|
|
|
|
if (type & IRQ_TYPE_EDGE_FALLING)
|
|
TMPM_SET_BIT(data->irq_trig_fall, intr_num);
|
|
else
|
|
TMPM_CLR_BIT(data->irq_trig_fall, intr_num);
|
|
|
|
if (type & IRQ_TYPE_EDGE_RISING)
|
|
TMPM_SET_BIT(data->irq_trig_raise, intr_num);
|
|
else
|
|
TMPM_CLR_BIT(data->irq_trig_raise, intr_num);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct irq_chip tmpm32xi2c_gpio_irq_chip = {
|
|
.name = "tmpm32xi2c",
|
|
.irq_mask = tmpm32xi2c_gpio_irq_mask,
|
|
.irq_unmask = tmpm32xi2c_gpio_irq_unmask,
|
|
.irq_bus_lock = tmpm32xi2c_gpio_irq_bus_lock,
|
|
.irq_bus_sync_unlock = tmpm32xi2c_gpio_irq_bus_sync_unlock,
|
|
.irq_set_type = tmpm32xi2c_gpio_irq_set_type,
|
|
.flags = IRQCHIP_SKIP_SET_WAKE,
|
|
};
|
|
|
|
static int tmpm32xi2c_gpio_irq_setup(struct tmpm32xi2c_gpio_data *data,
|
|
const struct platform_device_id *id)
|
|
{
|
|
struct tmpm32xi2c_chip *chip = dev_get_drvdata(data->dev->parent);
|
|
uint32_t gpio_num = 0;
|
|
unsigned long irq_flags;
|
|
int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(intr_map.iidg); i++) {
|
|
gpio_num = TMPM_GET_GPIO_NUM(i);
|
|
if (gpio_num != ~0) {
|
|
TMPM_SET_BIT(data->irq_available, gpio_num);
|
|
intr_map.igdi[gpio_num] = i;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < TMPM_MAX_INTR_BANK; i++) {
|
|
uint8_t tx_mask[] = { CMD_INT_MASK, 0x0 /*value*/ };
|
|
|
|
data->irq_mask[i] = 0;
|
|
ret = chip->write_read(chip, tx_mask, sizeof(tx_mask), NULL, 0);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"Failed to init interrupt mask, %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
irq_flags = IRQF_ONESHOT | IRQF_SHARED | IRQF_EARLY_RESUME;
|
|
ret = devm_request_threaded_irq(data->dev, data->irq, NULL,
|
|
tmpm32xi2c_gpio_irq_handler,
|
|
data->irq_flags | irq_flags,
|
|
dev_name(data->dev), data);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"Failed to request irq%d, %d\n", data->irq, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = gpiochip_irqchip_add(&data->gc, &tmpm32xi2c_gpio_irq_chip,
|
|
0, handle_simple_irq, IRQ_TYPE_NONE);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"Failed to add irqchip to gpiochip, %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tmpm32xi2c_gpio_probe(struct platform_device *pdev)
|
|
{
|
|
const struct platform_device_id *id = platform_get_device_id(pdev);
|
|
struct tmpm32xi2c_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
|
struct tmpm32xi2c_gpio_data *data;
|
|
int ret;
|
|
|
|
data = devm_kzalloc(&pdev->dev, sizeof(struct tmpm32xi2c_gpio_data),
|
|
GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->dev = &pdev->dev;
|
|
platform_set_drvdata(pdev, data);
|
|
|
|
data->irq = chip->irq;
|
|
data->irq_flags = chip->irq_flags;
|
|
if (data->irq <= 0) {
|
|
dev_err(&pdev->dev, "No IRQ\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_init(&data->lock);
|
|
mutex_init(&data->irq_lock);
|
|
|
|
data->gc.direction_input = tmpm32xi2c_gpio_direction_input;
|
|
data->gc.direction_output = tmpm32xi2c_gpio_direction_output;
|
|
data->gc.get = tmpm32xi2c_gpio_get_value;
|
|
data->gc.set = tmpm32xi2c_gpio_set_value;
|
|
data->gc.can_sleep = true;
|
|
data->gc.base = -1;
|
|
data->gc.ngpio = id->driver_data;
|
|
data->gc.label = pdev->name;
|
|
data->gc.parent = &pdev->dev;
|
|
data->gc.owner = THIS_MODULE;
|
|
data->gc.can_sleep = true;
|
|
#ifdef CONFIG_OF_GPIO
|
|
data->gc.of_node = data->dev->parent->of_node;
|
|
#endif
|
|
|
|
ret = devm_gpiochip_add_data(&pdev->dev, &data->gc, data);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to add gpiochip data, %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = tmpm32xi2c_gpio_irq_setup(data, id);
|
|
if (ret < 0) {
|
|
gpiochip_remove(&data->gc);
|
|
return ret;
|
|
}
|
|
|
|
data->gc.to_irq = tmpm32xi2c_gpio_to_irq;
|
|
|
|
device_init_wakeup(data->dev, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tmpm32xi2c_gpio_remove(struct platform_device *pdev)
|
|
{
|
|
struct tmpm32xi2c_gpio_data *data = platform_get_drvdata(pdev);
|
|
|
|
device_init_wakeup(data->dev, false);
|
|
gpiochip_remove(&data->gc);
|
|
mutex_destroy(&data->lock);
|
|
mutex_destroy(&data->irq_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int tmpm32xi2c_gpio_suspend(struct device *dev)
|
|
{
|
|
struct tmpm32xi2c_gpio_data *data = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
disable_irq(data->irq);
|
|
|
|
if (device_may_wakeup(dev)) {
|
|
ret = enable_irq_wake(data->irq);
|
|
if (ret < 0)
|
|
dev_err(dev,
|
|
"Failed to enable irq wake for irq%d, %d\n",
|
|
data->irq, ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tmpm32xi2c_gpio_resume(struct device *dev)
|
|
{
|
|
struct tmpm32xi2c_gpio_data *data = dev_get_drvdata(dev);
|
|
|
|
if (device_may_wakeup(dev))
|
|
disable_irq_wake(data->irq);
|
|
|
|
enable_irq(data->irq);
|
|
|
|
/* If there is any pending irq, invoke the irq handler. */
|
|
if (tmpm32xi2c_gpio_irq_pending(data)) {
|
|
local_irq_disable();
|
|
data->irq_is_pending = 1;
|
|
generic_handle_irq(data->irq);
|
|
local_irq_enable();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops tmpm32xi2c_gpio_pm = {
|
|
.suspend_late = tmpm32xi2c_gpio_suspend,
|
|
.resume_early = tmpm32xi2c_gpio_resume,
|
|
};
|
|
#endif
|
|
|
|
static const struct platform_device_id tmpm32xi2c_gpio_id[] = {
|
|
{ "tmpm32xi2c-gpio", TMPM_MAX_PORT, },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, tmpm32xi2c_gpio_id);
|
|
|
|
static struct platform_driver tmpm32xi2c_gpio_driver = {
|
|
.driver = {
|
|
.name = "tmpm32xi2c-gpio",
|
|
#ifdef CONFIG_PM
|
|
.pm = &tmpm32xi2c_gpio_pm,
|
|
#endif
|
|
},
|
|
.probe = tmpm32xi2c_gpio_probe,
|
|
.remove = tmpm32xi2c_gpio_remove,
|
|
.id_table = tmpm32xi2c_gpio_id,
|
|
};
|
|
|
|
static int __init tmpm32xi2c_gpio_init(void)
|
|
{
|
|
return platform_driver_register(&tmpm32xi2c_gpio_driver);
|
|
}
|
|
/*
|
|
* register after postcore initcall and subsys initcall that may rely on
|
|
* these GPIOs and I2C.
|
|
*/
|
|
subsys_initcall_sync(tmpm32xi2c_gpio_init);
|
|
|
|
static void __exit tmpm32xi2c_gpio_exit(void)
|
|
{
|
|
platform_driver_unregister(&tmpm32xi2c_gpio_driver);
|
|
}
|
|
module_exit(tmpm32xi2c_gpio_exit);
|
|
|
|
MODULE_AUTHOR("NVIDIA Corporation");
|
|
MODULE_DESCRIPTION("GPIO expander driver for TMPM32x I2C");
|
|
MODULE_LICENSE("GPL");
|