【Linux驱动开发】PS2游戏手柄驱动开发与输入子系统框架

为什么需要输入子系统框架

我们首先来看字符类驱动框架:

1)写file_operations结构体的成员函数: .open()、.read()、.write()
2)在入口函数里通过register_chrdev()创建驱动名,生成主设备号,赋入file_operations结构体
3)在出口函数里通过unregister_chrdev() 卸载驱动

若有多个不同的驱动程序时,应用程序就要打开多个不同的驱动设备,由于是自己写肯定会很清楚,如果给别人来使用时是不是很麻烦?
所以需要使用输入子系统, 使应用程序无需打开多个不同的驱动设备便能实现。

输入子系统框架

驱动层

将底层的硬件输入转化为统一事件形式,想输入核心(Input Core)汇报。
输入子系统核心层
它承上启下为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。

事件处理层

主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。
设备描述

input_dev结构是实现设备驱动核心工作:向系统报告按键、触摸屏等输入事件(event,通过input_event结构描述),不再需要关心文件操作接口。驱动报告事件经过inputCore和Eventhandler到达用户空间。

注册输入设备函数:

int input_register_device(struct input_dev *dev)

注销输入设备函数:

void input_unregister_device(struct input_dev *dev)

驱动实现——初始化(事件支持)

set_bit()告诉input输入子系统支持哪些事件,哪些按键。例如:

set_bit(EV_KEY,button_dev.evbit) (其中button_dev是struct input_dev类型)

struct input_dev中有两个成员为:
1)evbit事件类型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)。
2)keybit按键类型(当事件类型为EV_KEY时包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)。

驱动实现——报告事件

用于报告EV_KEY,EV_REL,EV_ABS事件的函数分别为:
void input_report_key(struct input_dev *dev,unsigned int code,int value)
void input_report_rel(struct input_dev *dev,unsigned int code,int value)
void input_report_abs(struct input_dev *dev,unsigned int code,int value)

驱动实现——报告结束

input_sync()同步用于告诉input core子系统报告结束,触摸屏设备驱动中,一次点击的整个报告过程如下:
input_reprot_abs(input_dev,ABS_X,x); //x坐标
input_reprot_abs(input_dev,ABS_Y,y); // y坐标
input_reprot_abs(input_dev,ABS_PRESSURE,1);
input_sync(input_dev);//同步结束

实例分析(按键中断程序):

//按键初始化

static int __init button_init(void)
{//申请中断
    if(request_irq(BUTTON_IRQ,button_interrupt,0,”button”,NUll))
        return –EBUSY;
    set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件
    set_bit(BTN_0,button_dev.keybit); //支持设备两个键
    set_bit(BTN_1,button_dev.keybit); //
    input_register_device(&button_dev);//注册input设备
}

/*在按键中断中报告事件*/
Static void button_interrupt(int irq,void *dummy,struct pt_regs *fp)
{
    input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));//读取寄存器BUTTON_PORT0的值
    input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1));
    input_sync(&button_dev);
}

总结:input子系统仍然是字符设备驱动程序,但是代码量减少很多,input子系统只需要完成两个工作:初始化和事件报告(这里在linux中是通过中断来实现的)。

关于事件报告的实现方法

中断实现 常用于实体按键

按键输入示例:


#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>


#include <../arch/arm/mach-mx28/mx28_pins.h>


#define DEVICE_NAME  "input_key"

struct input_dev *inputdev;

struct imx28x_key_struct {
    int key_code;  /* 按键能产生的键值*/
    int gpio;  /* 按键连接的 GPIO */
    struct work_struct work;  /* 按键的工作队列  */
};

struct imx28x_key_struct keys_list[] ={
    {.key_code = KEY_A, .gpio = MXS_PIN_TO_GPIO(PINID_LCD_D17)},
    {.key_code = KEY_B, .gpio = MXS_PIN_TO_GPIO(PINID_LCD_D18)},
    {.key_code = KEY_C, .gpio = MXS_PIN_TO_GPIO(PINID_SSP0_DATA4)},
    {.key_code = KEY_D, .gpio = MXS_PIN_TO_GPIO(PINID_SSP0_DATA5)},
    {.key_code = KEY_E, .gpio = MXS_PIN_TO_GPIO(PINID_SSP0_DATA6)}
};

static irqreturn_t imx28x_key_intnerrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    int i = (int)dev_id;
    int gpio = keys_list[i].gpio; /* 获取按键的 GPIO */
    int code = keys_list[i].key_code; /* 获取按键的键值  */
    /*
    * 延迟 20uS,看按键是不是按下,如果不是,就是抖动
    */
    printk(KERN_INFO "%d was pressed",code);
    udelay(20);
    if (gpio_get_value(gpio)) {
    return IRQ_HANDLED;
    }
    input_report_key(inputdev, code, 1);  /* 先报告键按下事件 */
    input_sync(inputdev);
    schedule_work(&(keys_list[i].work));  /* 提交工作队列,实现中断的下半部处理 */
    return IRQ_HANDLED;
}


static void imx28x_scankeypad(struct work_struct *_work)
{
    /* 通过工作队列指针而获得它所属的 imx28x_key_struct 类型的对象 */
    struct imx28x_key_struct *key_tmp = container_of(_work, struct imx28x_key_struct, work);
    int gpio = key_tmp->gpio;
    int code = key_tmp->key_code;
    /* 每隔 10mS 检查按键是否已经提起,如果没有提起就一直等待 */
    while(!gpio_get_value(gpio)){ 
    mdelay(10); 
    }
    input_report_key(inputdev, code, 0);  /* 报告按键提起事件 */
    input_sync(inputdev);
}

static int __devinit iMX28x_key_init(void)
{
    int i = 0, ret = 0;
    int irq_no = 0;
    int code, gpio;
    inputdev = input_allocate_device(); /* 为输入设备驱动对象申请内存空间*/
    if (!inputdev) {
    return -ENOMEM;
    }
    inputdev->name = DEVICE_NAME;
    set_bit(EV_KEY, inputdev->evbit); /* 设置输入设备支持按键事件 */
        for (i = 0; i < sizeof(keys_list)/sizeof(keys_list[0]); i++) {
        code = keys_list[i].key_code;
        gpio = keys_list[i].gpio;
        /* 为每个按键都初始化工作队列  */
        INIT_WORK(&(keys_list[i].work), imx28x_scankeypad);
        set_bit(code, inputdev->keybit); /* 设置输入设备支持的键值  */
        /* 为每个按键都初始化 GPIO */
        gpio_free(gpio);
        ret = gpio_request(gpio, "key_gpio");
            if (ret) {
            printk("request gpio failed %d \n", gpio);
            return -EBUSY;
            }
        /* 当 GPIO 被设置为输入工作状态后,就可以检测中断信号 */
        gpio_direction_input(gpio);
        /* 把每个 GPIO 中断响应方式都设置为下降沿响应 */
        irq_no = gpio_to_irq(gpio);
        set_irq_type(gpio, IRQF_TRIGGER_FALLING);
        /* 为每个按键的中断都安装中断处理函数,其私有数据为按键信息在 keys_list 数组下的索引 */
        ret = request_irq(irq_no, imx28x_key_intnerrupt, IRQF_DISABLED, "imx28x_key", (void *)i);
            if (ret) {
            printk("request irq faile %d!\n", irq_no);
            return -EBUSY;
            }
        }
    input_register_device(inputdev); /* 注册设备驱动  */
    printk("EasyARM-i.MX28x key driver up \n");
    return 0;
}

static void __exit iMX28x_key_exit(void)
{
    int i = 0;
    int irq_no;
    for (i = 0; i < sizeof(keys_list)/sizeof(keys_list[0]); i++) {
    irq_no = gpio_to_irq(keys_list[i].gpio); /* 为每个按键释放 GPIO */
    free_irq(irq_no, (void *)i); /* 为每个按键卸载中断处理函数 */
    }
    input_unregister_device(inputdev); /* 注销输入设备驱动 */
    printk(" key driver remove \n");
}

module_init(iMX28x_key_init);
module_exit(iMX28x_key_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("YangYue");
内核定时器实现,用于没有触发信号的输入事件

例如没有触发的触摸屏事件。

动态 timer 由内核自身使用,其实也是其他 Timer 的实现基础。使用动态 Timer 的接口函数有三个:

add_timer()
del_timer()
init_timer()

使用时,先调用 init_timer() 初始化一个定时器,指定到期时间和到期处理函数;初始化完成后,内核代码可以用 add_timer() 启动定时器,或者用 del_timer() 来取消一个已经启动的定时器。
add_timer 采用时间轮算法将定时器加入 per CUP 变量 tvec_bases 中,根据其 expire 时间,可能被加入 5 个 Timer Vector 之一。此后,tick 中断将根据时间轮算法处理。当本 timer 到期时,触发其处理函数。
动态 Timer 有两个方面的用途:一是内核自己使用,比如某些驱动程序需要定时服务的时候使用它;二是用来实现用户层 Timer。下面首先讲解间隔 Timer。

比如要实现一个100ms的中断
可以这样设置:
ps2timer.expires=jiffies +HZ100/1000;
mod_timer(&ps2timer,jiffies+ HZ
100/1000 );

PS2驱动开发

硬件原理

在这里插入图片描述
DI/DAT:信号流向,从手柄到主机,此信号是一个 8bit 的串行数据,同步传送于时钟
的下降沿。信号的读取在时钟由高到低的变化过程中完成。
DO/CMD:信号流向,从主机到手柄,此信号和 DI 相对,信号是一个 8bit 的串行数据,
同步传送于时钟的下降沿。
NC:空端口;
GND:电源地;
VDD:接收器工作电源,电源范围 3~5V;
CS/SEL:用于提供手柄触发信号。在通讯期间,处于低电平;
CLK:时钟信号,由主机发出,用于保持数据同步;
NC:空端口;
ACK:从手柄到主机的应答信号。此信号在每个 8bits 数据发送的最后一个周期变低并
且 CS 一直保持低电平,如果 CS 信号不变低,约 60 微秒 PS 主机会试另一个外设。在编程
时未使用 ACK 端口。

在这里插入图片描述

软件实现

/*
 * @Author: your name
 * @Date: 2021-04-11 18:25:16
 * @LastEditTime: 2021-04-11 23:46:33
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /DriverDefine/PS2/PS2.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/timer.h>

#define DEVICE_NAME  "joystick"

#define PSB_SELECT      1
#define PSB_L3          2
#define PSB_R3          3
#define PSB_START       4
#define PSB_PAD_UP      5
#define PSB_PAD_RIGHT   6
#define PSB_PAD_DOWN    7
#define PSB_PAD_LEFT    8
#define PSB_L2          9
#define PSB_R2          10
#define PSB_L1          11
#define PSB_R1          12
#define PSB_GREEN       13
#define PSB_RED         14
#define PSB_BLUE        15
#define PSB_PINK        16
#define PSB_TRIANGLE    13
#define PSB_CIRCLE      14
#define PSB_CROSS       15
#define PSB_SQUARE      16

#define PSS_RX          5           
#define PSS_RY          6
#define PSS_LX          7
#define PSS_LY          8

static struct timer_list ps2timer; 
u16 Handkey;
u8 Comd[2]={0x01,0x42};	
u8 Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; //Êý¾Ý´æ´¢Êý×é
u16 MASK[]={
    PSB_SELECT,
    PSB_L3,
    PSB_R3 ,
    PSB_START,
    PSB_PAD_UP,
    PSB_PAD_RIGHT,
    PSB_PAD_DOWN,
    PSB_PAD_LEFT,
    PSB_L2,
    PSB_R2,
    PSB_L1,
    PSB_R1 ,
    PSB_GREEN,
    PSB_RED,
    PSB_BLUE,
    PSB_PINK
};	

typedef struct  
{
    int data_pin;
    int cmd_pin;
    int cs_pin;
    int clk_pin;
}ps2_handler;

ps2_handler ps_define={
    2*32+5,2*32+7,2*32+13,2*32+15,
};

#define DI      gpio_get_value(ps_define.data_pin)
#define DO_H    gpio_direction_output(ps_define.cmd_pin,1)
#define DO_L    gpio_direction_output(ps_define.cmd_pin,0)

#define CS_H    gpio_direction_output(ps_define.cs_pin,1)    
#define CS_L    gpio_direction_output(ps_define.cs_pin,0)    

#define CLK_H   gpio_direction_output(ps_define.clk_pin,1)     
#define CLK_L   gpio_direction_output(ps_define.clk_pin,0)   


typedef struct 
{
   int PHYKEY;
   int LOGICKEY;
}KEY_MAP;
KEY_MAP key_map[16]={
    {PSB_SELECT,    KEY_1},
    {PSB_L3,        KEY_A},
    {PSB_R3,        KEY_B},
    {PSB_START,     KEY_C},
    {PSB_PAD_UP,    KEY_A},
    {PSB_PAD_RIGHT, KEY_A},
    {PSB_PAD_DOWN,  KEY_A},
    {PSB_PAD_LEFT,  KEY_A},
    {PSB_L2,        KEY_1},
    {PSB_R2,        KEY_2},
    {PSB_L1,        KEY_3},
    {PSB_R1,        KEY_4},
    {PSB_GREEN,     KEY_A},
    {PSB_RED,       KEY_ENTER},
    {PSB_BLUE,      KEY_7},
    {PSB_PINK,      KEY_8},
};

struct input_dev *inputdev;
void PS2_Cmd(u8 CMD)
{
	volatile u16 ref=0x01;
	Data[1] = 0;
	for(ref=0x01;ref<0x0100;ref<<=1)
	{
		if(ref&CMD)
		{
			DO_H;               
		}
		else DO_L;
		CLK_H;                     
		udelay(50);
		CLK_L;
		udelay(50);
		CLK_H;
		if(DI)
			Data[1] = ref|Data[1];
	}
}
void PS2_ReadData(void)
{
	volatile u8 byte=0;
	volatile u16 ref=0x01;
	CS_L;
	PS2_Cmd(Comd[0]); 
	PS2_Cmd(Comd[1]);  

	for(byte=2;byte<9;byte++)        
	{
		for(ref=0x01;ref<0x100;ref<<=1)
		{
			CLK_H;
			CLK_L;
			udelay(50);
			CLK_H;
		      if(DI)
		      Data[byte] = ref|Data[byte];
		}
        udelay(50);
	}
	CS_H;	
}


void PS2_ClearData()
{
	u8 a;
	for(a=0;a<9;a++)
		Data[a]=0x00;
}
u8 PS2_DataKey()
{
	u8 index;
	PS2_ClearData();
	PS2_ReadData();
	Handkey=(Data[4]<<8)|Data[3];    
	for(index=0;index<16;index++)
	{	    
		if((Handkey&(1<<(MASK[index]-1)))==0)
		return index+1;
	}
	return 0;      
}
u8 PS2_AnologData(u8 button)
{
	return Data[button];
}
void PS2_ShortPoll(void)
{
	CS_L;
	udelay(16);
	PS2_Cmd(0x01);
	PS2_Cmd(0x42);
	PS2_Cmd(0X00);
	PS2_Cmd(0x00);
	PS2_Cmd(0x00);
	CS_H;
	udelay(16);
}

void PS2_EnterConfing(void)
{
	CS_L;
	udelay(16);
	PS2_Cmd(0x01);
	PS2_Cmd(0x43);
	PS2_Cmd(0X00);
	PS2_Cmd(0x01);
	PS2_Cmd(0x00);
	PS2_Cmd(0X00);
	PS2_Cmd(0X00);
	PS2_Cmd(0X00);
	PS2_Cmd(0X00);
	CS_H;
	udelay(16);
}

void PS2_TurnOnAnalogMode(void)
{
    CS_L;
    PS2_Cmd(0x01);
    PS2_Cmd(0x44);
    PS2_Cmd(0X00);
    PS2_Cmd(0x01);
    PS2_Cmd(0xEE); 
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    CS_H;
    udelay(16);
}

void PS2_VibrationMode(void)
{
	CS_L;
	udelay(16);
	PS2_Cmd(0x01);
	PS2_Cmd(0x4D);
	PS2_Cmd(0X00);
	PS2_Cmd(0x00);
	PS2_Cmd(0X01);
	CS_H;
	udelay(16);
}

void PS2_ExitConfing(void)
{
	CS_L;
	udelay(16);
	PS2_Cmd(0x01);
	PS2_Cmd(0x43);
	PS2_Cmd(0X00);
	PS2_Cmd(0x00);
	PS2_Cmd(0x5A);
	PS2_Cmd(0x5A);
	PS2_Cmd(0x5A);
	PS2_Cmd(0x5A);
	PS2_Cmd(0x5A);
	CS_H;
	udelay(16);
}

void PS2_SetInit(void)
{
	PS2_ShortPoll();
	PS2_ShortPoll();
	PS2_ShortPoll();
	PS2_EnterConfing(); 
	PS2_TurnOnAnalogMode(); 
	PS2_VibrationMode(); 
	PS2_ExitConfing();
}

void PS2_Vibration(u8 motor1, u8 motor2)
{
	CS_L;
	udelay(16);
	PS2_Cmd(0x01); 
	PS2_Cmd(0x42); 
	PS2_Cmd(0X00);
	PS2_Cmd(motor1);
	PS2_Cmd(motor2);
	PS2_Cmd(0X00);
	PS2_Cmd(0X00);
	PS2_Cmd(0X00);
	PS2_Cmd(0X00);
	CS_H;
	udelay(16);
}


void ps2_timer_function(unsigned long data)
{
    u8 val;
    val=PS2_DataKey();
    printk("%d \t\n",val);
    if(val==0)
    {
        input_event(inputdev,EV_KEY,key_map[val].LOGICKEY, 0);  //上报EV_KEY类型,button按键,0(没按下)
        input_sync(inputdev);
    }else
    {
        input_event(inputdev,EV_KEY,key_map[val].LOGICKEY, 1);  //上报EV_KEY类型,button按键,0(没按下)
        input_event(inputdev,EV_REL,PS2_AnologData(), 1); 
        input_sync(inputdev);        
        //printk("%d \t\n",key_map[val].LOGICKEY);
    }
    mod_timer(&ps2timer,jiffies+ HZ*100/1000  );
}

static int PS2_init(void)
{
    int i = 0;
    inputdev = input_allocate_device(); /* 为输入设备驱动对象申请内存空间*/
    if (!inputdev) {return -ENOMEM;}
    inputdev->name = DEVICE_NAME;
    
    set_bit(EV_KEY, inputdev->evbit); /* 设置输入设备支持按键事件 */
    set_bit(EV_REL, inputdev->evbit); 

    set_bit(KEY_A,inputdev->keybit);                  //支持按键 L
    set_bit(KEY_B,inputdev->keybit);                //支持按键 S
    set_bit(KEY_C,inputdev->keybit);      //支持按键 空格
    set_bit(KEY_ENTER,inputdev->keybit);


    //GPIO
    gpio_request(ps_define.data_pin, "data");
    gpio_direction_input(ps_define.data_pin);
    gpio_request(ps_define.cs_pin, "cs");
    gpio_direction_output(ps_define.cs_pin,0); 
    gpio_request(ps_define.clk_pin, "clk");
    gpio_direction_output(ps_define.clk_pin,0);
    gpio_request(ps_define.cmd_pin, "cmd");
    gpio_direction_output(ps_define.cmd_pin,0);
    PS2_SetInit();
    input_register_device(inputdev); /* 注册设备驱动  */
    
    init_timer(&ps2timer);
    ps2timer.function=ps2_timer_function;
    ps2timer.expires=jiffies +HZ*100/1000;
    add_timer(&ps2timer);

    printk("YURI PS2 driver up \n");
    return 0;
}

static void PS2_exit(void)
{
    del_timer(&ps2timer);
    input_unregister_device(inputdev); /* 注销输入设备驱动 */
    input_free_device(inputdev);
    printk("PS2 driver remove \n");
}

module_init(PS2_init);
module_exit(PS2_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("YURI YANG");

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与光同程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值