You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

648 lines
20 KiB

/*
* Copyright (c) Hisilicon Technologies Co., Ltd.. 2010-2020. All rights reserved.
* Description:drv of gpio
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <asm/irq.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/printk.h>
#include <linux/gpio/driver.h>
#include <linux/list.h>
#include <linux/semaphore.h>
#include <linux/version.h>
#include "soc_errno.h"
#include "soc_module.h"
#include "drv_sys_ext.h"
#include "drv_gpio_ext.h"
#include "drv_gpio.h"
#define EXT_GPIO_GROUP_MAX 31 /* reserved9_c group is 31 */
#define GPIO_MAX_BUF 256
#define GPIO_BUF_HEAD g_gpio_attr.gpio_irq_list[g_gpio_attr.head]
#define GPIO_BUF_TAIL g_gpio_attr.gpio_irq_list[g_gpio_attr.tail]
typedef struct {
td_bool gpio_interrupt_enable;
ext_gpio_interrupt_type gpio_interrupt_type;
td_void (*gpio_server)(td_u32 gpio_num);
} gpio_interrupt_attr;
typedef struct {
td_u32 head; /* gpio interrupt list */
td_u32 tail;
td_u32 gpio_irq_list[GPIO_MAX_BUF];
td_u32 gpio_irq_list_size;
td_u32 gpio_wait_timeout;
osal_wait gpio_interrupt_wait_queue; /* gpio wait queue */
td_size_t gpio_request[GPIO_MAX_BUF];
gpio_interrupt_attr gpio_irq_attr[GPIO_MAX_BUF];
osal_semaphore mutex;
} gpio_attr;
static gpio_get_gpio_num g_gpio_num;
static osal_atomic g_gpio_init_counter = { TD_NULL };
static gpio_attr g_gpio_attr;
td_s32 ext_drv_gpio_pm_suspend(td_void *private_data)
{
soc_print("GPIO suspend OK\n");
return 0;
}
td_s32 ext_drv_gpio_pm_resume(td_void *private_data)
{
soc_print("GPIO resume OK\n");
return 0;
}
static gpio_ext_func g_gpio_export_funcs = {
.pfn_gpio_direction_get_bit = ext_drv_gpio_get_direction_bit,
.pfn_gpio_direction_set_bit = ext_drv_gpio_set_direction_bit,
.pfn_gpio_read_bit = ext_drv_gpio_read_bit,
.pfn_gpio_write_bit = ext_drv_gpio_write_bit,
.pfn_gpio_get_num = ext_drv_gpio_get_gpio_num,
.pfn_gpio_register_server_func = ext_drv_gpio_register_server_func,
.pfn_gpio_unregister_server_func = ext_drv_gpio_unregister_server_func,
.pfn_gpio_set_interrupt_type = ext_drv_gpio_set_interrupt_type,
.pfn_gpio_set_interrupt_enable = ext_drv_gpio_set_bit_interrupt_enable,
.pfn_gpio_clear_group_interrupt = ext_drv_gpio_clear_group_interrupt,
.pfn_gpio_clear_bit_interrupt = ext_drv_gpio_clear_bit_interrupt,
.pfn_gpio_suspend = ext_drv_gpio_pm_suspend,
.pfn_gpio_resume = ext_drv_gpio_pm_resume
};
td_s32 ext_drv_gpio_set_direction_bit(td_u32 gpio_num, td_u32 dir_bit)
{
td_s32 ret;
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return TD_FAILURE;
}
ret = osal_gpio_set_direction(gpio_num, dir_bit);
if (ret != TD_SUCCESS) {
soc_log_err("osal_gpio_set_direction failed! \n");
return TD_FAILURE;
}
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_get_direction_bit(td_u32 gpio_num, td_u32 *pdir_bit)
{
td_s32 status;
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return TD_FAILURE;
}
if (pdir_bit == NULL) {
soc_log_err("pdir_bit is NULL \n");
return TD_FAILURE;
}
status = osal_gpio_get_direction(gpio_num);
if (status == TD_FAILURE) {
soc_log_err("osal_gpio_get_direction failed! \n");
return TD_FAILURE;
}
if (status == GPIOF_DIR_IN) {
*pdir_bit = TD_TRUE;
} else if (status == GPIOF_DIR_OUT) {
*pdir_bit = TD_FALSE;
} else {
soc_log_err("Get gpio%u direction failed:%d\n", gpio_num, status);
return SOC_ERR_GPIO_FAILED_GETDIRECT;
}
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_write_bit(td_u32 gpio_num, td_u32 bit_value)
{
td_s32 status;
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return TD_FAILURE;
}
if ((bit_value != TD_TRUE) && (bit_value != TD_FALSE)) {
soc_log_err("Invalid bit_value:%u\n", bit_value);
return SOC_ERR_GPIO_INVALID_PARA;
}
status = osal_gpio_get_direction(gpio_num);
if (status != GPIOF_DIR_OUT) {
soc_log_err("Input direction, write denied\n");
return SOC_ERR_GPIO_INVALID_OPT;
}
osal_gpio_set_value(gpio_num, bit_value);
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_read_bit(td_u32 gpio_num, td_u32 *pbit_value)
{
td_s32 ret;
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return TD_FAILURE;
}
if (pbit_value == NULL) {
soc_log_err("pbit_value is null\n");
return TD_FAILURE;
}
ret = osal_gpio_get_value(gpio_num);
if (ret == TD_FAILURE) {
*pbit_value = TD_FALSE;
soc_log_err("osal_gpio_get_value failed\n");
return TD_FAILURE;
}
*pbit_value = (td_u32)ret;
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_set_interrupt_type(td_u32 gpio_num, ext_gpio_interrupt_type interrupt_trigger_mode)
{
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return SOC_ERR_GPIO_INVALID_PARA;
}
if (interrupt_trigger_mode >= EXT_GPIO_INTTYPE_MAX) {
soc_log_err("Invalid parameter, interrupt_trigger_mode:%d\n", interrupt_trigger_mode);
return SOC_ERR_GPIO_INVALID_PARA;
}
g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_type = interrupt_trigger_mode;
return TD_SUCCESS;
}
static osal_irqreturn_t gpio_isr(td_s32 irq, td_void *gpio_number)
{
td_size_t gpio_num;
if (gpio_number == NULL) {
soc_log_err("gpio number is null\n");
return (osal_irqreturn_t)OSAL_IRQ_HANDLED;
}
gpio_num = (td_size_t)(uintptr_t)gpio_number;
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return (osal_irqreturn_t)OSAL_IRQ_HANDLED;
}
if (g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_type == EXT_GPIO_INTTYPE_HIGH ||
g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_type == EXT_GPIO_INTTYPE_LOW) {
disable_irq_nosync(gpio_to_irq(gpio_num));
if (g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_enable == TD_FALSE) {
soc_log_err("error irq! \n");
}
}
if (g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_enable == TD_TRUE) {
if (g_gpio_attr.gpio_irq_attr[gpio_num].gpio_server != TD_NULL) {
g_gpio_attr.gpio_irq_attr[gpio_num].gpio_server(gpio_num);
soc_log_info("gpio[%u].gpio_server executed\n", gpio_num);
}
soc_log_info("Recive gpio interrupt: GPIO%u. \n", gpio_num);
GPIO_BUF_HEAD = gpio_num;
g_gpio_attr.head = (g_gpio_attr.head + 1) % (g_gpio_attr.gpio_irq_list_size);
osal_wait_wakeup(&(g_gpio_attr.gpio_interrupt_wait_queue));
}
return (osal_irqreturn_t)OSAL_IRQ_HANDLED;
}
static td_s32 ext_drv_gpio_set_interrupt_config(td_u32 gpio_num)
{
td_u32 irq_type;
td_s32 irq;
td_u32 flags = 0;
td_s32 ret;
ret = osal_gpio_set_direction(gpio_num, 1); /* set direction input */
if (ret != TD_SUCCESS) {
soc_log_err("set gpio %u input direction failed ! \n", gpio_num);
return TD_FAILURE;
}
irq_type = g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_type;
switch (irq_type) {
case EXT_GPIO_INTTYPE_UP:
flags |= IRQF_TRIGGER_RISING;
break;
case EXT_GPIO_INTTYPE_DOWN:
flags |= IRQF_TRIGGER_FALLING;
break;
case EXT_GPIO_INTTYPE_UPDOWN:
flags |= IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
break;
/* IRQF_TRIGGER_HIGH or IRQF_TRIGGER_LOW will trigger
* continuously interrupts, must avoid it
*/
case EXT_GPIO_INTTYPE_HIGH:
flags |= (IRQF_TRIGGER_HIGH | IRQF_ONESHOT);
break;
case EXT_GPIO_INTTYPE_LOW:
flags |= (IRQF_TRIGGER_LOW | IRQF_ONESHOT);
break;
default:
flags = IRQF_TRIGGER_RISING;
break;
}
if ((flags == IRQF_TRIGGER_HIGH || flags == IRQF_TRIGGER_LOW) &&
(g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_enable == TD_TRUE)) {
osal_gpio_irq_free(gpio_num, (td_void *)((uintptr_t)gpio_num));
g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_enable = TD_FALSE;
}
irq = osal_gpio_irq_request(gpio_num, (osal_irq_handler)gpio_isr, flags, "gpio", (td_void *)((uintptr_t)gpio_num));
if (irq == TD_FAILURE) {
soc_log_err("Request_irq irq%d on gpio%u failed: %d\n", irq, gpio_num, ret);
g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_enable = TD_FALSE;
return SOC_ERR_GPIO_FAILED_SETINT;
}
g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_enable = TD_TRUE;
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_set_bit_interrupt_enable(td_u32 gpio_num, td_bool enable)
{
td_s32 ret;
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return TD_FAILURE;
}
if (enable) { /* not mask */
ret = ext_drv_gpio_set_interrupt_config(gpio_num);
if (ret != TD_SUCCESS) {
soc_log_err("set gpio interrupt config failed!\n");
return ret;
}
} else { /* mask */
if (g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_enable == TD_TRUE) {
osal_gpio_irq_free(gpio_num, (td_void *)((uintptr_t)gpio_num));
g_gpio_attr.gpio_irq_attr[gpio_num].gpio_interrupt_enable = TD_FALSE;
}
}
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_register_server_func(td_u32 gpio_num, td_void (*func)(td_u32))
{
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return TD_FAILURE;
}
if (func == TD_NULL) {
soc_log_err("Register func para is null, gpio_num:%u \n", gpio_num);
return TD_FAILURE;
}
if (g_gpio_attr.gpio_irq_attr[gpio_num].gpio_server != TD_NULL) {
soc_log_err("GPIO %u had registered gpio server pragram \n", gpio_num);
return TD_FAILURE;
}
g_gpio_attr.gpio_irq_attr[gpio_num].gpio_server = func;
soc_log_info("Gpio %d finished register gpio server function \n", gpio_num);
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_unregister_server_func(td_u32 gpio_num)
{
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return TD_FAILURE;
}
g_gpio_attr.gpio_irq_attr[gpio_num].gpio_server = TD_NULL;
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_clear_bit_interrupt(td_u32 gpio_num)
{
if (gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", gpio_num);
return SOC_ERR_GPIO_INVALID_PARA;
}
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_clear_group_interrupt(td_u32 gpio_group)
{
if (gpio_group >= g_gpio_num.gpio_group_num) {
soc_log_err("Invalid parameter, gpio_group:%u\n", gpio_group);
return SOC_ERR_GPIO_INVALID_PARA;
}
return TD_SUCCESS;
}
td_s32 ext_drv_gpio_get_gpio_num(gpio_get_gpio_num *gpio_num)
{
if (gpio_num == NULL) {
soc_log_err("gpio_num is null\n");
return TD_FAILURE;
}
gpio_num->gpio_group_num = g_gpio_num.gpio_group_num;
gpio_num->gpio_max_num = g_gpio_num.gpio_max_num;
return TD_SUCCESS;
}
td_s32 drv_gpio_open(td_void *private_data)
{
return TD_SUCCESS;
}
td_s32 drv_gpio_close(td_void *private_data)
{
td_s32 ret, i;
if (osal_atomic_read(&g_gpio_init_counter) <= 0) {
soc_log_err("gpio not initialized\n");
return TD_FAILURE;
}
ret = osal_sem_down_interruptible(&g_gpio_attr.mutex);
if (ret) {
soc_log_err("osal_sem_down_interruptible:%d\n", ret);
return TD_FAILURE;
}
for (i = 0; i < GPIO_MAX_BUF; i++) {
if (g_gpio_attr.gpio_irq_attr[i].gpio_interrupt_enable == TD_TRUE) {
osal_gpio_irq_free(i, (td_void *)((uintptr_t)i));
g_gpio_attr.gpio_irq_attr[i].gpio_interrupt_enable = TD_FALSE;
}
}
osal_sem_up(&g_gpio_attr.mutex);
return TD_SUCCESS;
}
td_s32 drv_gpio_query_interrupt_wait_condition(const td_void *para)
{
return (g_gpio_attr.head != g_gpio_attr.tail);
}
td_s32 drv_gpio_query_interrupt(gpio_interrupt *pgpio_interrupt_value)
{
td_s32 ret;
td_u32 gpio_interrupt_num;
if (pgpio_interrupt_value == NULL) {
soc_log_err("pgpio_interrupt_value is null\n");
return TD_FAILURE;
}
if (pgpio_interrupt_value->timeout_ms > 5000U) {
soc_log_err("pgpio_interrupt_value wait time[%d] too long, the range is 1~5000ms\n",
pgpio_interrupt_value->timeout_ms);
return SOC_ERR_GPIO_INVALID_PARA;
}
if (pgpio_interrupt_value->gpio_num >= g_gpio_num.gpio_max_num) {
soc_log_err("Invalid parameter, gpio_num:%u\n", pgpio_interrupt_value->gpio_num);
return SOC_ERR_GPIO_INVALID_PARA;
}
g_gpio_attr.gpio_wait_timeout = pgpio_interrupt_value->timeout_ms;
while (g_gpio_attr.head == g_gpio_attr.tail) {
if (g_gpio_attr.gpio_wait_timeout == 0xffffffff) {
ret = osal_wait_interruptible(&g_gpio_attr.gpio_interrupt_wait_queue,
drv_gpio_query_interrupt_wait_condition, NULL);
if (ret < 0) {
soc_log_err("osal_wait_interruptible failed:%d\n", ret);
return -ERESTARTSYS;
}
} else {
ret = osal_wait_timeout_interruptible(&g_gpio_attr.gpio_interrupt_wait_queue,
drv_gpio_query_interrupt_wait_condition,
/* 1000 "ms" converted to "s" */
NULL, (td_slong)(g_gpio_attr.gpio_wait_timeout));
if (ret < 0) {
soc_log_err("osal_wait_timeout_interruptible failed:%d\n", ret);
return -ERESTARTSYS;
} else if (ret == 0) {
soc_log_err("osal_wait_timeout_interruptible timrout\n");
return SOC_ERR_GPIO_GETINT_TIMEOUT;
}
}
}
if (g_gpio_attr.head != g_gpio_attr.tail) {
gpio_interrupt_num = GPIO_BUF_TAIL;
g_gpio_attr.tail = (g_gpio_attr.tail + 1) % (g_gpio_attr.gpio_irq_list_size);
pgpio_interrupt_value->gpio_num = gpio_interrupt_num;
}
return TD_SUCCESS;
}
static ext_gpio_outputtype g_gpio_outputtype = EXT_GPIO_OUTPUTTYPE_CMOS;
td_s32 drv_gpio_set_output_type(td_u32 gpio_num, ext_gpio_outputtype output_type)
{
if (output_type >= EXT_GPIO_OUTPUTTYPE_MAX) {
soc_log_err("Invalid parameter, gpio_num:%u, output_type:%u\n", gpio_num, output_type);
return SOC_ERR_GPIO_INVALID_PARA;
}
g_gpio_outputtype = output_type;
return TD_SUCCESS;
}
td_s32 drv_gpio_get_output_type(td_u32 gpio_num, ext_gpio_outputtype *poutput_type)
{
if (poutput_type == TD_NULL) {
soc_log_err("gpio_num:%u, poutput_type is null\n", gpio_num);
return SOC_ERR_GPIO_NULL_PTR;
}
*poutput_type = g_gpio_outputtype;
return TD_SUCCESS;
}
td_s32 drv_gpio_osal_resource_init(td_void)
{
td_s32 ret;
ret = osal_sem_init(&g_gpio_attr.mutex, 1);
if (ret != TD_SUCCESS) {
soc_log_err("osal_sem_init failed! \n");
return TD_FAILURE;
}
ret = osal_atomic_init(&g_gpio_init_counter);
if (ret != TD_SUCCESS) {
soc_log_err("osal_atomic_init failed! \n");
goto err0;
}
ret = osal_wait_init(&g_gpio_attr.gpio_interrupt_wait_queue);
if (ret != TD_SUCCESS) {
soc_log_err("osal_wait_init failed! \n");
goto err1;
}
return TD_SUCCESS;
err1:
osal_atomic_destroy(&g_gpio_init_counter);
err0:
osal_sem_destroy(&g_gpio_attr.mutex);
return TD_FAILURE;
}
td_s32 ext_drv_gpio_init(td_void)
{
td_s32 ret;
td_u32 i, tmp_gpio_group_num;
ret = drv_gpio_osal_resource_init();
if (ret != TD_SUCCESS) {
soc_log_err("drv_gpio_osal_resource_init failed! \n");
return TD_FAILURE;
}
if (osal_atomic_inc_return(&g_gpio_init_counter) != 1) {
soc_log_warn(" EXT_DRV_GPIO already registered:%d\n", osal_atomic_read(&g_gpio_init_counter));
return TD_SUCCESS;
}
ret = osal_exportfunc_register(SOC_ID_GPIO, "SOC_GPIO", (td_void *)&g_gpio_export_funcs);
if (ret != TD_SUCCESS) {
soc_log_err(" GPIO Module register failed 0x%x.\n", ret);
goto err0;
}
g_gpio_attr.head = 0;
g_gpio_attr.tail = 0;
g_gpio_attr.gpio_irq_list_size = GPIO_MAX_BUF;
g_gpio_attr.gpio_wait_timeout = 0xffffffff;
ret = osal_dts_get_u32_byname("huanglong,gpioinfo", "groupnum", &tmp_gpio_group_num);
if (ret != 0) {
soc_log_err("get groupnum from dts failed.\n");
goto err1;
}
g_gpio_num.gpio_group_num = (td_u8)tmp_gpio_group_num;
if (g_gpio_num.gpio_group_num > EXT_GPIO_GROUP_MAX) {
soc_log_err("para gpio_num is invalid.\n");
goto err1;
}
g_gpio_num.gpio_max_num = g_gpio_num.gpio_group_num * EXT_GPIO_BIT_NUM;
for (i = 0; i < GPIO_MAX_BUF; i++) {
g_gpio_attr.gpio_irq_attr[i].gpio_interrupt_enable = TD_FALSE;
g_gpio_attr.gpio_irq_attr[i].gpio_interrupt_type = EXT_GPIO_INTTYPE_DOWN;
g_gpio_attr.gpio_irq_attr[i].gpio_server = TD_NULL;
}
return TD_SUCCESS;
err1:
ret = osal_exportfunc_unregister(SOC_ID_GPIO);
if (ret != TD_SUCCESS) {
soc_log_err(" GPIO Module unregister failed 0x%x.\n", ret);
}
err0:
osal_atomic_destroy(&g_gpio_init_counter);
osal_sem_destroy(&g_gpio_attr.mutex);
osal_wait_destroy(&g_gpio_attr.gpio_interrupt_wait_queue);
return TD_FAILURE;
}
td_void ext_drv_gpio_deinit(td_void)
{
td_u32 i;
td_s32 ret;
if (osal_atomic_dec_return(&g_gpio_init_counter) != 0) {
soc_log_warn("ext_drv_gpio_deinit counter:%d\n", osal_atomic_read(&g_gpio_init_counter));
return;
}
for (i = 0; i < GPIO_MAX_BUF; i++) {
if (g_gpio_attr.gpio_irq_attr[i].gpio_interrupt_enable == TD_TRUE) {
osal_gpio_irq_free(i, (td_void *)((uintptr_t)i));
g_gpio_attr.gpio_irq_attr[i].gpio_interrupt_enable = TD_FALSE;
}
g_gpio_attr.gpio_irq_attr[i].gpio_interrupt_type = EXT_GPIO_INTTYPE_DOWN;
g_gpio_attr.gpio_irq_attr[i].gpio_server = TD_NULL;
}
ret = osal_exportfunc_unregister(SOC_ID_GPIO);
if (ret != TD_SUCCESS) {
soc_log_err(" GPIO Module unregister failed 0x%x.\n", ret);
}
osal_atomic_destroy(&g_gpio_init_counter);
osal_sem_destroy(&g_gpio_attr.mutex);
osal_wait_destroy(&g_gpio_attr.gpio_interrupt_wait_queue);
return;
}
#ifdef MODULE
EXPORT_SYMBOL(ext_drv_gpio_init);
EXPORT_SYMBOL(ext_drv_gpio_deinit);
#endif
EXPORT_SYMBOL(ext_drv_gpio_get_direction_bit);
EXPORT_SYMBOL(ext_drv_gpio_set_direction_bit);
EXPORT_SYMBOL(ext_drv_gpio_write_bit);
EXPORT_SYMBOL(ext_drv_gpio_read_bit);
EXPORT_SYMBOL(ext_drv_gpio_get_gpio_num);
EXPORT_SYMBOL(ext_drv_gpio_register_server_func);
EXPORT_SYMBOL(ext_drv_gpio_unregister_server_func);
EXPORT_SYMBOL(ext_drv_gpio_set_interrupt_type);
EXPORT_SYMBOL(ext_drv_gpio_set_bit_interrupt_enable);
EXPORT_SYMBOL(ext_drv_gpio_clear_group_interrupt);
EXPORT_SYMBOL(ext_drv_gpio_clear_bit_interrupt);