Linux通过字符设备文件控制GPIO

通过字符设备文件控制GPIO电平通知单片机信号。

驱动代码

获取GPIO号

	notify_gpio = of_get_named_gpio(pdev->dev.of_node, "notify_gpio", 0);
	if((ret = gpio_init()) < 0)
	{
    		printk("[gale] %s: notify pin init failed\n", __func__);
                goto err_gpio_init;
	}

 注册字符设备

#define GPIO_MAJOR 199         	// major device NO.
#define GPIO_MINOR 0         	// minor device NO.
#define DEVICE_NAME "gpio_notify"  //driver device node.

static const struct file_operations gpio_fops =
{
  .owner = THIS_MODULE,
  .open = gpio_open,
  .release = gpio_release,
  .unlocked_ioctl = gpio_ioctl,
  .fasync = gpio_fasync,
};

static int gpio_init(void)
{
    int ret;
    
    dev_t my_dev_no;
    struct cdev *gpio_cdev;
    gpio_cdev = cdev_alloc();
    if(gpio_cdev == NULL)
    {
        printk(KERN_EMERG "Cannot alloc cdev\n");
        goto request_failed;
    }
    cdev_init(gpio_cdev,&gpio_fops);
    gpio_cdev->owner=THIS_MODULE;
    ret = alloc_chrdev_region(&my_dev_no,0,1,DEVICE_NAME);
    if(ret < 0)
    {
        printk(KERN_EMERG "alloc_chrdev_region failed\n");
        goto request_failed;
    }

    ret=cdev_add(gpio_cdev,my_dev_no,1);
    
    ret = register_chrdev(GPIO_MAJOR, DEVICE_NAME, &gpio_fops);
    if(ret < 0)
    {
    	printk(KERN_EMERG "GPIO register failed\n");
    	goto request_failed;
    }
    
    gpio_class = class_create(THIS_MODULE, DEVICE_NAME);
    device_create(gpio_class, NULL, MKDEV(GPIO_MAJOR, GPIO_MINOR), NULL, DEVICE_NAME);

    printk("[gale] %s: OK ###############################\n", __func__);

    return ret;
    
request_failed:
    return ret;
}

ioctl函数实现如下:收到信号设置对应的高低电平

static int gpio_ioctl(struct file *filp,unsigned int cmd)
{
    if(0 == notify_gpio)
    	return -1;
        
    switch(cmd)
    {
        case SET_OUTPUT_LOW://0
        {
			printk("\n\n%s: set mcu wakeup(pin2) to low level\n",current->comm);
            gpio_direction_output(notify_gpio, 0);
            break;
        }
        case SET_OUTPUT_HIGH://1
        {
			printk("\n\n%s: set mcu wakeup(pin2) to high level\n",current->comm);
            gpio_direction_output(notify_gpio, 1);
            break;
        }
        case GET_VALUE://2
        {
            return gpio_get_value(notify_gpio);    
        }
        case SET_INPUT://3
        {
            gpio_direction_input(notify_gpio);
            break;
        }
        default:
        {
            printk(KERN_EMERG "GPIO command mistake!!!\n");
            break;
        }
    }
    return 0;
}

异步通知函数初始化,用于指定kill_fasync函数的目标进程ID

static int gpio_fasync (int fd, struct file *filp, int mode)  
{  
        printk("quectel low consume: gpio_fasync\n");  
          
        return fasync_helper(fd, filp, mode, &gpio_async);  
}

 指定数据内容存储文件位置,位置在:/sys/devices/soc:quec,quectel-low-consume/irq_edge

static volatile char edge[8] = "rising";

static ssize_t irq_edge_show(struct device* dev, struct device_attribute *attr, char* buf)
{  
	ssize_t ret = 0;  
  
	sprintf(buf, "%s", edge);  
  
	ret = strlen(buf) + 1;  
	return ret;  
} 

static DEVICE_ATTR(irq_edge, S_IRUGO, irq_edge_show, NULL);

控制代码

分2个部分:GPIO中断函数里面使用kill_fasync发信号给字符设备;其他地方使用ioctl给字符设备发命令;

1、GPIO中断函数里面使用kill_fasync发信号给字符设备;

static irqreturn_t quectel_wakeup_irq_func(void)
{
	int value = gpio_get_value(wakeup_gpio);
	if(value == LOW_VALUE)
	{
		strcpy(edge, "falling");
		kill_fasync(&gpio_async, SIGIO, POLL_IN);
	}
	else if(value == HIGH_VALUE)
	{
		strcpy(edge, "rising");
		kill_fasync(&gpio_async, SIGIO, POLL_IN );
	}

	return IRQ_HANDLED;
}

2、其他地方收到信号并获取数据内容,然后使用ioctl给字符设备发命令;

#define WAKEUP_IO          "/dev/gpio_notify"

pm_register(WAKEUP_IO, pm_handle);

static int pm_register(char *path, void (*handle)(int signal))
{
	int oflags;

	signal(SIGIO, handle); /* 应用程序注册信号处理函数, SIGIO 表示 Io 有数据可供读取; */ 
	io_fd = open(path, O_RDWR);
	if (io_fd < 0)
		return -1;

	fcntl(io_fd, F_SETOWN, getpid());/* 信号发给谁,是通过这段程序告诉内核的 */ 
    
	oflags = fcntl(io_fd, F_GETFL);//读取 Oflags;
	fcntl(io_fd, F_SETFL, oflags | FASYNC);/* 改变 Oflags 为异步通知 FASYNC, 改变 FASYNC标志, 最终调用到驱动的 fasync -> fasync_helper */  

	handle(SIGIO);

	return 0;
}

 信号处理函数

#define IRQ_EDGE           "/sys/devices/soc:quec,quectel-low-consume/irq_edge"
#define ACTIVE             0

static void pm_handle(int signal)
{
	int fd;
	int count = 0;
	char edge[8] = {0};

	fd = open(IRQ_EDGE, O_RDONLY | O_NONBLOCK);
	if (fd < 0)
		return "null";

	count = read(fd, edge, sizeof(edge)); //读取数据内容
	printf("%s:%s(%d)\n", IRQ_EDGE, edge, count);
	close(fd);

	if (strncmp(edge, "falling", 7) == 0) {
		ioctl(io_fd, !ACTIVE);
	} else if (strncmp(edge, "rising", 6) == 0) {
		ioctl(io_fd, ACTIVE);
	}

这样的GPIO方式需防止GPIO冲突导致信号发送不成功的偶现低概率问题。

你可能感兴趣的:(Linux)