MIPS下GPIO驱动与中断

MIPS下GPIO驱动与中断

驱动的开发离不开GPIO和中断,这篇文章将集中在介绍MIPS体系结构下驱动的开发; 当然,Linux下的驱动开发也并不区分体系结构,只不过如果没有写过的话,心中总是会有所顾虑和担心,这里便会破除我们心中的担心和顾虑。

单纯的说gpio驱动固然是比较简单的,所以我希望在我的文章里融入一些深层次的知识,或是对读者的提点,或是画龙点睛的一笔,只希望不要是画蛇填足便好

GPIO控制器

这里我对中断控制器的解析主要从设备树这个角度讲解,因为其他的函数都是读名便可以知意的。首先我们看一下gpio_chip结构体:

struct gpio_chip {
	const char		*label;
	struct gpio_device	*gpiodev;
	struct device		*parent;
	struct module		*owner;

	int			(*request)(struct gpio_chip *chip,
						unsigned offset);
	void			(*free)(struct gpio_chip *chip,
						unsigned offset);
	int			(*get_direction)(struct gpio_chip *chip,
						unsigned offset);
	int			(*direction_input)(struct gpio_chip *chip,
						unsigned offset);
	int			(*direction_output)(struct gpio_chip *chip,
						unsigned offset, int value);
	int			(*get)(struct gpio_chip *chip,
						unsigned offset);
	int			(*get_multiple)(struct gpio_chip *chip,
						unsigned long *mask,
						unsigned long *bits);
	void			(*set)(struct gpio_chip *chip,
						unsigned offset, int value);
	void			(*set_multiple)(struct gpio_chip *chip,
						unsigned long *mask,
						unsigned long *bits);
	int			(*set_config)(struct gpio_chip *chip,
					      unsigned offset,
					      unsigned long config);
	int			(*to_irq)(struct gpio_chip *chip,
						unsigned offset);

	void			(*dbg_show)(struct seq_file *s,
						struct gpio_chip *chip);
	int			base;
	u16			ngpio;
	const char		*const *names;
	bool			can_sleep;

	/**
	 * @of_node:
	 *
	 * Pointer to a device tree node representing this GPIO controller.
	 */
	struct device_node *of_node;

	/**
	 * @of_gpio_n_cells:
	 *
	 * Number of cells used to form the GPIO specifier.
	 */
	unsigned int of_gpio_n_cells;

	/**
	 * @of_xlate:
	 *
	 * Callback to translate a device tree GPIO specifier into a chip-
	 * relative GPIO number and flags.
	 */
	int (*of_xlate)(struct gpio_chip *gc,
			const struct of_phandle_args *gpiospec, u32 *flags);
};

如果希望详细的研究GPIO,希望读者仔细的研读一下源码,因为其中还有一些非常有趣的东西,这会和gpio相关的寄存器联系,会合中断子系统联系,也会和pinctrl子系统联系。结构体的上半部分我们可以读名知意,同时在许多驱动书籍中都会解释,我也不再解释。我要解释的是和of相关的三个参数:of_node of_gpio_n_cells of_xlate函数。

of_node: 这是dts设备树中的设备节点,对于我们解析和获取其中的属性还是非常重要的
of_gpio_n_cells: 这是设备树中指定GPIO参数是更在控制器后面参数的个数
of_xlate: 这是解析设备树gpio属性为pin的函数,

首先我们解释of_gpio_n_cells的意思:

gpio@0x1fe10450 {
          compatible = "ls,ls1a500-gpio";
          reg = <0x1fe10450 0x00000020>;
          ngpios = <0x00000040>;
          conf_offset = <0x00000000>;
          in_offset = <0x00000008>;
          out_offset = <0x00000010>;
          inten_offset = <0x00000098>;
          gpio_base = <0x00000040>;
          irq_base = <0x00000044>;
          gpio-controller;
          #gpio-cells = <0x00000002>;
          linux,phandle = <0x00000004>;
          phandle = <0x00000004>;
};
gpio@0x1fe10470 {
          compatible = "ls,ls1a500-gpio";
          reg = <0x1fe10470 0x00000020>;
          ngpios = <0x00000020>;
          conf_offset = <0x00000000>;
          in_offset = <0x00000008>;
          out_offset = <0x00000010>;
          inten_offset = <0x00000098>;
          gpio_base = <0x00000080>;
          gpio-controller;
          #gpio-cells = <0x00000002>;
};
pci_port {
        compatible = "loongson,ls1a500-pci";
        bus-range = <0x00000010 0x00000018>;
        pci-gpios = <0x00000004 0x00000016 0x00000000>;
};

这是一段DTB反编译后获得的设备树信息,希望读者注意第一个gpio具有phandle这个唯一的标识符,而第二个gpio没有phandle标识符,这主要是因为第一个gpio控制器被引用了,所以需要唯一的标识,第二个没有被引用所以没有相应的唯一标识。同时你也可以参一下下面的dts源码对gpio控制器的引用。我们来看一下pci_port中对gpio的引用,也就是pci-gpios属性,这里一共有三个参数,第一个是gpio控制器的phandle值,linux会根据该值去获取形如gpio-cells,gpio-map这样的属性,具体代码如果读者有兴趣可以自己研究研究,这里我们要看的重点是跟在phandle值后面的两个参数,注意这里是两个参数,这也是我们设置of_gpio_n_cells参数的值。

pioB: gpio@0x1fe10450{
           compatible = "ls,ls1a500-gpio";
           reg = <0x1fe10450 0x20>;
           ngpios = <64>;
           conf_offset = <0>;
           in_offset = <8>;
           out_offset = <0x10>;
           inten_offset = <0x98>;
           gpio_base = <64>;
           irq_base = <68>;
           gpio-controller;
           #gpio-cells = <2>;
};

pioC: gpio@0x1fe10470{
           compatible = "ls,ls1a500-gpio";
           reg = <0x1fe10470 0x20>;
           ngpios = <32>;
           conf_offset = <0>;
           in_offset = <8>;
           out_offset = <0x10>;
           inten_offset = <0x98>;
           gpio_base = <128>;
           gpio-controller;
           #gpio-cells = <2>;
};

pci_port {
          compatible = "loongson,ls1a500-pci";
          bus-range = <0x10 0x18>;
          pci-gpios = <&pioB 22 0>;
};

下面我在给出一段我自己实现的of_xlate函数,该函数可以解析设备树对gpio控制器的引用为pin号:

static int ls2_gpio_xlate(struct gpio_chip *chip,
                        const struct of_phandle_args *gpiospec,
                        u32 *flags)
{
        int pin, base;
        int max;

        base = chip->base;
        pin = base + gpiospec->args[0];
        max = base + chip->ngpio;

        if ((pin > max) || (pin < base))
                return -EINVAL;

        if (flags)
                *flags = gpiospec->args[1];

        return pin;
}

从上面设备树的分析,我们知道phandle中具有的参数是两个。这也就是我们可以从gpiospc->args中可以获取的参数个数,同时其参数便是我们在设备树中设置的值。在这里的of_xlate函数中,我们做的事情就是根据gpio->base和设备树中的参数计算出目标引脚。

中断使用讲解

龙芯对Linux中断的支持使用了偏移量,这个偏移量就是8,比如,如果你的gpio中断号是59,那么你申请中断号时应该申请的是irq=59 + 8 = 67。这一点尤其需要注意,否则可能错误的使用中断号。

一个GPIO中断要想进入CPU,需要经历三道门槛:

  1. GPIO中对应中断引脚的使能
  2. 针对一组GPIO控制器的中断的使能
  3. CPU对中断的使能与否

这里也就说明了我们在编写gpio中断时需要走的路,或者一个方向。
首先,我们应该确保我们的操作系统是支持中断的或者打开了中断,Linux是默认支持的并且在配置中也使能了。

其次,就是针对一组gpio中断的使能,这里我们需要使用函数request_irq(),当然还有一个函数是irq_enable(irq)可以打开中断,但是我没有试过,一般都直接使用request_irq申请并打开中断,同时链接自己的处理程序为钩子函数。

最后,便是打开GPIO引脚的中断使能,这里会有歧义或者可能由于不同的芯片产商支持不同。platform_get_irq是一个标准的从设备树中获得irq的函数,但是我尝试了一下,在龙芯的平台上并能获得相应的中断号,但是并没有把中断使能,也就是我们是不能使用这个函数去获得中断号的。对于没有使能相应引脚的中断,我们或许会自然的想到手动的操作相应的寄存器,打开中断使能,但是这其实是不现实的,应为该寄存器已经被GPIO控制器申请了,我们是不能操作该寄存器的。既然存在这样的问题,那么一定有右解决的方法,我们可以直接使用gpio控制器驱动体同的gpio_to_irq函数来获得相应的中断并使能。下面我们来看一下GPIO控制器驱动的相关核心代码


static int ls2_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
        struct ls2_gpio_chip *lgpio =
                container_of(chip, struct ls2_gpio_chip, chip);
        unsigned long flags;
        u64 u;

        ls2_gpio_direction_input(chip, offset);
        spin_lock_irqsave(&lgpio->lock, flags);
        u = readq(GPIO_INTEN(lgpio));
        u |= 1UL << offset;
        writeq(u, GPIO_INTEN(lgpio));
        spin_unlock_irqrestore(&lgpio->lock, flags);
        return lgpio->irq_base + (offset/32);
}

这里做了这么几件事:

  1. 设置pin为输入功能
  2. 设置pin的中断使能
  3. 返回pin对应的中断号

所以在gpio相关的中断申请时,我们应该使用gpio_to_irq函数获取中断号,避免使用platform_get_irq。至于platform_get_irq获得中断号的ARM实例可以参考我的博客: arm-irq

驱动源码

这里的代码是用于测试gpio中断的驱动。使用跳线连接两个gpio,一个设置输出,一个设置中断输入,使用测试程序设置输出引脚的电平来产生模拟中断事件,gpio中断引脚收到中断后只是简单的打印一条语句。读者在真实的驱动开发可以自由的选择tasklet或者workqueue等机制来实现,看读者自己的需求来使用。

#include <linux/module.h>
#include <linux/platform_device.h> 
#include <linux/fs.h> 
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h> 
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/miscdevice.h>

#define BEEP_ON		0x01
#define INPUT_PIN	44	
#define OUTPUT_PIN	46
#define IRQ_NUM		67

struct gpio_misc {
	int minor;
	int irq;
	int input_pin;
	int output_pin;
	unsigned int status;

	struct device *dev; 
	struct device_node *dev_node;

	struct miscdevice misc;
};

irqreturn_t gpio_handler(int irq, void *dev_id) {
	printk("<kernel>: irq = %d \n", irq);

	return IRQ_HANDLED;
}

static int beep_open(struct inode *node, struct file *filp)
{ 
	printk("<kernel>: open irq test device !\n"); 

	return 0; 
}

static int beep_close(struct inode *node, struct file *filp)
{
	printk("<kernel>: close irq test device !\n");

	return 0;
}


/* write data to the device-node: beep on || beep off */
static ssize_t beep_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *offset)
{
	char data[2];
	int ret;
	struct gpio_misc *lsgpio = container_of(filp->private_data,
				struct gpio_misc, misc);

	ret = copy_from_user(data, buf, count);
	if (ret){
		printk("<kernel>: copy_from_user failed !\n");
		return -EFAULT;
	}

	if(data[0] == 0x31){	
		gpio_set_value(lsgpio->output_pin, 1);
		lsgpio->status |= BEEP_ON;
	}else{
		gpio_set_value(lsgpio->output_pin, 0);
		lsgpio->status &= ~BEEP_ON;
	}

	return ret;
}

/* read data from gpio-dev */
static ssize_t beep_read(struct file *filp, char __user *buf, 
		size_t count, loff_t *offset)
{
	int ret;
	char status[1];
	struct gpio_misc *lsgpio = container_of(filp->private_data,
				struct gpio_misc, misc);

	if (lsgpio->status & BEEP_ON){
		status[0] = 0x31;
	}else{
		status[0] = 0x30;
	}

	ret = copy_to_user(buf, status, 1);
	if (ret) {
		printk("<kernel>: failed to copy_to_user !\n");
		return EFAULT;
	}

	return ret;
}

static const struct file_operations beep_fops = {
	.owner = THIS_MODULE,
	.read = beep_read,
	.write = beep_write,
	.open = beep_open,
	.release = beep_close
};

static int probe(struct platform_device *pdev)
{
	int ret = 0;
	int irq;
	struct device *dev;
	struct gpio_misc *lsgpio;
	int input_pin, output_pin;

	dev = &pdev->dev;
	if (!dev->of_node){
		printk("<Kernel>: mached the driver, but the struct \
				missed !\n");
		return -ENODEV;
	}
	
	/* 1. get the number of the gpio pin */
	input_pin = of_get_named_gpio(dev->of_node, "input-gpio", 0);
	if (input_pin < 0) {
		printk("<kernel>: Failed to get input-gpio !\n");
		return -ENODEV;
	}

	output_pin = of_get_named_gpio(dev->of_node, "output-gpio", 0);
	if (output_pin < 0) {
		printk("<kernel>: Failed to get output-gpio !\n");
		return -ENODEV;
	}

	/* 2. requst gpio pins */
	ret = gpio_request(input_pin, "input"); 
	if (ret != 0) { 
		printk("<kernel>: Failed to request input pin !\n");
		return -ENODEV;
	}

	ret = gpio_request(output_pin, "output");
	if (ret != 0) {
		printk("<kernel>: Failed to request output pin !\n");
		ret = -ENODEV;

		goto gpio_err;	//goto the error tackle point
	}

	/* 3. get the irq-number
	 * there list 3 ways to get irq number 
	 *
	 * 3.1 irq = IRQ_NUM;
	 * 3.2 irq = gpio_to_irq(gpio_in);
	 * 3.3 irq = platform_get_irq(pdev, 0);
	 */
	irq = gpio_to_irq(input_pin);
	if (irq != IRQ_NUM) {
		printk("Failed to get irq number %d !\n", irq);
		ret = -ENODEV;

		goto irq_err;
	}

	/* 4. malloc a struct space for store info*/
	lsgpio = kzalloc(sizeof(struct gpio_misc), GFP_KERNEL);
	if (!lsgpio){
		printk("<Kernel>: Failed to malloc space !\n");
		ret = -ENOMEM;

		goto irq_err;
	}

	lsgpio->irq = irq;
	lsgpio->input_pin = input_pin; 
	lsgpio->output_pin = output_pin;
	lsgpio->dev_node = dev->of_node;
	lsgpio->dev = dev;
	platform_set_drvdata(pdev, lsgpio);

	/* 5. set the gpio priority and irq */
	ret = gpio_direction_output(lsgpio->output_pin, 0);
	if (ret < 0)
		printk("<Kernel>: Failed to set the pin-out !\n");

	ret = gpio_direction_input(lsgpio->input_pin);
	if (ret < 0)
		printk("<kernel>: Failed to set pin-in !\n");

	ret = request_irq(lsgpio->irq, gpio_handler, 
			IRQF_TRIGGER_RISING,
			"gpio-test", lsgpio
			);	
	if ( ret ) {
		printk("<kernel>: Failed to request-irq for gpio !\n");
		ret = -EINVAL;

		goto req_err;
	}

	/* 6. register misc-device */
	lsgpio->misc.minor = MISC_DYNAMIC_MINOR; 
	lsgpio->misc.name = "beep"; 
	lsgpio->misc.fops = &beep_fops;

	ret = misc_register(&lsgpio->misc);
	if (ret != 0) {
		printk("<Kernel>: Failed to register misc-dev !\n");
		goto misc_err;
	}

	lsgpio->minor = MINOR(lsgpio->misc.minor);
	printk("<kernel>: Success Reigster Driver !\n");

	return ret;

	//tackle all error 
misc_err:
	gpio_free(lsgpio->irq);
 		
req_err:  		
	kfree(lsgpio);

irq_err:
	gpio_free(output_pin);

gpio_err:
	gpio_free(input_pin);
	printk("<kernel>: Failed to Rigster Driver !\n");
	
	return ret;
	

}

static int __exit remove(struct platform_device *pdev)
{ 
	int ret = 0;
	struct gpio_misc *misc_gpio;

	misc_gpio = platform_get_drvdata(pdev);
	misc_deregister(&misc_gpio->misc);
	gpio_free(misc_gpio->input_pin);
	gpio_free(misc_gpio->output_pin);
	free_irq(misc_gpio->irq, misc_gpio);
	kfree(misc_gpio);

	printk("<kernel>: The Driver and Device detected !\n");

	return ret;
}

static const struct of_device_id beep_device_ids[] = {
	{ .compatible = "loongson,ls1a500-beep" },
	{ },
};
MODULE_DEVICE_TABLE(of, beep_device_ids);


static struct platform_driver beep_driver = {
	.probe = probe,
	.remove = remove,
	.driver = {
		.name = "beep",
		.of_match_table = beep_device_ids,
		.owner = THIS_MODULE,
	}
};
module_platform_driver(beep_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("weirdo:<weirdo-xo@outlook.com");
MODULE_DESCRIPTION("The simle Driver !");

其实就这个驱动而言,我还可以写一篇文章,后面在解释一些内部细节或隐藏的东西吧!

posted @ 2021-01-27 20:36  FOFI  阅读(575)  评论(1编辑  收藏  举报