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,需要经历三道门槛:
- GPIO中对应中断引脚的使能
- 针对一组GPIO控制器的中断的使能
- 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);
}
这里做了这么几件事:
- 设置pin为输入功能
- 设置pin的中断使能
- 返回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 !");
其实就这个驱动而言,我还可以写一篇文章,后面在解释一些内部细节或隐藏的东西吧!