且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

OK335xS knob driver hacking

更新时间:2022-08-12 21:11:25

/*************************************************************************
 *                     OK335xS knob driver hacking
 * 说明:
 *     本文主要是为了分析knob设备的创建,驱动层如何注册,发送信息。
 *
 *                                  2015-11-18 晴 深圳 南山平山村 曾剑锋
 ************************************************************************/


#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/semaphore.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <plat/mux.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/timer.h>
#include <asm/uaccess.h>
#include <linux/completion.h>
#include <linux/input.h>

/**
 * reference: AM335x ARM® Cortex™-A8 Microprocessors (MPUs) Technical Reference Manual
 *              Table 2-2. L4_WKUP Peripheral Memory Map (continued)
 * +----------------+---------------------+-------------------+---------------------------------+
 * | Region Name    | Start Address (hex) | End Address (hex) | Size  Description               | 
 * +----------------+---------------------+-------------------+---------------------------------+
 * | Control Module | 0x44E1_0000         | 0x44E1_1FFF       | 128KB Control Module Registers  | 
 * +----------------+---------------------+-------------------+---------------------------------+
 */
#define Control_Module_address                0x44E10000
/**
 * reference: AM335x ARM® Cortex™-A8 Microprocessors (MPUs) Technical Reference Manual
 *              Table 9-10. CONTROL_MODULE REGISTERS (continued)
 * +--------+-----------------------+----------------------+----------------+
 * | Offset | Acronym               | Register Description | Section        | 
 * +--------+-----------------------+----------------------+----------------+
 * | 9B0h   | conf_xdma_event_intr0 |                      | Section 9.3.51 | 
 * +--------+-----------------------+----------------------+----------------+
 */
#define CONFIG_XDMA_EVENT_INTR0_offset        0x9B0    
/**
 * reference: AM335x ARM® Cortex™-A8 Microprocessors (MPUs) Technical Reference Manual
 *              Table 9-10. CONTROL_MODULE REGISTERS (continued)
 * +--------+-----------------------+----------------------+----------------+
 * | Offset | Acronym               | Register Description | Section        | 
 * +--------+-----------------------+----------------------+----------------+
 * | 868h   | conf_gpmc_a10         |                      | Section 9.3.51 | 
 * +--------+-----------------------+----------------------+----------------+
 */
#define CONFIG_GPMC_A10_offset                0x868    
/**
 * reference: AM335x ARM® Cortex™-A8 Microprocessors (MPUs) Technical Reference Manual
 *              Table 9-10. CONTROL_MODULE REGISTERS (continued)
 * +--------+-----------------------+----------------------+----------------+
 * | Offset | Acronym               | Register Description | Section        | 
 * +--------+-----------------------+----------------------+----------------+
 * | 86Ch   | conf_gpmc_a11         |                      | Section 9.3.51 | 
 * +--------+-----------------------+----------------------+----------------+
 */
#define CONFIG_GPMC_A11_offset                0x86C    
/**
 * reference: AM335x ARM® Cortex™-A8 Microprocessors (MPUs) Technical Reference Manual
 *              Table 9-10. CONTROL_MODULE REGISTERS (continued)
 * +--------+-----------------------+----------------------+----------------+
 * | Offset | Acronym               | Register Description | Section        | 
 * +--------+-----------------------+----------------------+----------------+
 * | 994h   | conf_mcasp0_fsx       |                      | Section 9.3.51 | 
 * +--------+-----------------------+----------------------+----------------+
 */
#define CONFIG_MACSP0_FSX_offset            0x994    

#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))

#define GET_STRUCT_ADDR (ptr,type,member)\
     ((unsigned long )ptr - (unsigned long)((type*)0->member)) 

/**
 * AM335X_INTR0 --> [XDMA_EVENT_INTR0//TIMER4/CLKOUT1/SPI1_CS1/PR1_PRU1_PRU_R31_16/EMU2/GPIO0_19]
 */
#define KNOD_IRQ                GPIO_TO_PIN(0,19)
/**
 * AM335x_BOTT_INT --> [MCASP0_FSX/EHRPWM0B//SPI1_D0/MMC1_SDCD/PR1_PRU0_PRU_R30_1/PR1_PRU0_PRU_R31_1/GPIO3_15]
 */
#define KNOD_IRQ_BUTTON            GPIO_TO_PIN(3,15)
/**
 * reference: AM335x ARM® Cortex™-A8 Microprocessors (MPUs) Technical Reference Manual
 *              9.3.51 conf_<module>_<pin> Register (offset = 800h–A34h)
 *            Table 9-61. conf_<module>_<pin> Register Field Descriptions
 * +-------+-------------------------+------------+--------------------------------------------+-
 * | Bit   | Field                   | Type Reset | Description                                | 
 * +-------+-------------------------+------------+--------------------------------------------+-
 * | 31-20 | Reserved                | R    0h    |                                            | 
 * +-------+-------------------------+------------+--------------------------------------------+-
 * | 19-7  | Reserved                | R    0h    |                                            | 
 * +-------+-------------------------+------------+--------------------------------------------+-
 * | 6     | conf_<module>_<pin>_sle | R/W  X     | Select between faster or slower slew rate  | 
 * |       | wctrl                   |            | 0: Fast                                    | 
 * |       |                         |            | 1: Slow                                    | 
 * |       |                         |            | Reset value is pad-dependent.              | 
 * +-------+-------------------------+------------+--------------------------------------------+-
 * | 5     | conf_<module>_<pin>_rx  | R/W  1h    | Input enable value for the PAD             | 
 * |       | active                  |            | 0: Receiver disabled                       | 
 * |       |                         |            | 1: Receiver enabled                        | 
 * +-------+-------------------------+------------+--------------------------------------------+-
 * | 4     | conf_<module>_<pin>_pu  | R/W  X     | Pad pullup/pulldown type selection         | 
 * |       | typesel                 |            | 0: Pulldown selected                       | 
 * |       |                         |            | 1: Pullup selected                         | 
 * |       |                         |            | Reset value is pad-dependent.              | 
 * +-------+-------------------------+------------+--------------------------------------------+-
 * | 3     | conf_<module>_<pin>_pu  | R/W  X     | Pad pullup/pulldown enable                 | 
 * |       | den                     |            | 0: Pullup/pulldown enabled                 | 
 * |       |                         |            | 1: Pullup/pulldown disabled                | 
 * |       |                         |            | Reset value is pad-dependent.              | 
 * +-------+-------------------------+------------+--------------------------------------------+-
 * | 2-0   | conf_<module>_<pin>_m   | R/W  X     | Pad functional signal mux select.          | 
 * |       |                         |            | mode Reset value is pad-dependent.         | 
 * +-------+-------------------------+------------+--------------------------------------------+-
 *
 *      Table 9-2. Mode Selection
 * +---------+------------------------+
 * | MUXMODE | Selected Mode          | 
 * +---------+------------------------+
 * | 000b    | Primary Mode = Mode 0  | 
 * +---------+------------------------+
 * | 001b    | Mode 1                 | 
 * +---------+------------------------+
 * | 010b    | Mode 2                 | 
 * +---------+------------------------+
 * | 011b    | Mode 3                 | 
 * +---------+------------------------+
 * | 100b    | Mode 4                 | 
 * +---------+------------------------+
 * | 101b    | Mode 5                 | 
 * +---------+------------------------+
 * | 110b    | Mode 6                 | 
 * +---------+------------------------+
 * | 111b    | Mode 7                 | 
 * +---------+------------------------+
 *
 * 0x37 = 0b0011 0111
 */
#define PINMUX_KNOD_IRQ            0X37
/**
 * AM335X_IO_ROTARYA --> [GPMC_A10/GMII2_RXD1/RGMII2_RD1/RMII2_RXD1/GPMC_A26/PR1_MII1_RXDV/MCASP0_AXR0/GPIO1_26]
 */
#define KNOD_ROTARYA            GPIO_TO_PIN(1,26)
#define PINMUX_KNOD_ROTARYA        0x37    
/**
 * AM335X_IO_ROTARYB --> [GPMC_A11/GMII2_RXD0/RGMII2_RD0/RMII2_RXD0/GPMC_A27/PR1_MII1_RXER/MCASP0_AXR1/GPIO1_27]
 */
#define KNOD_ROTARYB            GPIO_TO_PIN(1,27)
#define PINMUX_KNOD_ROTARYB        0x37    

#define KNOB_LEFT            0X01
#define KNOB_RITH            0X02
#define KNOB_UPDATA        0X10
#define KNOB_DEBOUNCED_TIMERS        10

/**
 * completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成。
 */
DECLARE_COMPLETION(knob_completion);

/**
 * 用于表示输入设备数据结构
 */
struct input_dev * knob_input;
int knob_value;
int irq_num_knob_in;
int irq_num_knob_in_button;
int knob_value_l;
int knob_value_r;
int knob_value_read_count;

/**
 * 全局变量jiffies用来记录自系统启动以来产生的节拍的总数。
 * 启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。
 * 一秒内时钟中断的次数等于Hz,所以jiffies一秒内增加的值也就是Hz。
 */
static unsigned long knob_new_jiffies;
static unsigned long knob_old_jiffies;

static irqreturn_t knob_botton_irq_handle(int irq ,void *dev_id)
{       
    int tmp;
    tmp = gpio_get_value(KNOD_IRQ_BUTTON);
    printk("botton = %d\n ",tmp);
    if (tmp)
    {
        // 按下去了
        input_report_key(knob_input,KEY_OK,1);
        input_sync(knob_input);
    }
    else
    {
        // 抬起来了
        input_report_key(knob_input,KEY_OK,0);
        input_sync(knob_input);
    }
    return IRQ_NONE;
}

static irqreturn_t knob_irq_handle(int irq ,void *dev_id)
{
    int newa, newb ,i;
    static int timer_flag = 1,olda = 0, oldb = 0,oolda=0,ooldb=0;
    
    knob_new_jiffies = jiffies;
    
    /**
     * 这里貌似永远也进不了判断。
     * 貌似是为了处理抖动的问题
     */
    timer_flag = 0;
    if (timer_flag) { 
        if (knob_new_jiffies - knob_old_jiffies < 3) {
            goto RE;    
        } else {
            timer_flag = 0;
        }
    }

    /**
     * 获取旋转值对应的gpio的值
     */
    newa = gpio_get_value(KNOD_ROTARYA);
    newb = gpio_get_value(KNOD_ROTARYB);

    if ((newa == olda) && (newb == oldb)) {
        goto RE;
    } else {
        if ((newa == newb) && (newa == 0) && (oolda == ooldb) && (oolda == 1)) {
            if ((olda == 1) && (oldb == 0))
                knob_value = KNOB_LEFT | KNOB_UPDATA    ;
            if ((olda == 0) && (oldb == 1))
                knob_value = KNOB_RITH | KNOB_UPDATA    ;
        }

        oolda = olda;
        ooldb = oldb;
    
        olda = newa;
        oldb = newb;
    }
    
    if (knob_value & KNOB_RITH ) {
        knob_value_l = 0;
        knob_value_r = 1;
        /*
        if (knob_value_read_count)
            complete(&knob_completion);
        knob_value_read_count = 0;
        */
        input_report_key(knob_input,KEY_RIGHT,1);
        input_sync(knob_input);
        input_report_key(knob_input,KEY_RIGHT,0);
        input_sync(knob_input);
    }
    if (knob_value & KNOB_LEFT ) {
        knob_value_l = 1;
        knob_value_r = 0;
        /*
        if (knob_value_read_count)
            complete(&knob_completion);
        knob_value_read_count = 0;
        */
        input_report_key(knob_input,KEY_LEFT,1);
        input_sync(knob_input);
        input_report_key(knob_input,KEY_LEFT,0);
        input_sync(knob_input);
    }
    knob_old_jiffies = jiffies;

    knob_value = 0; 
       return IRQ_NONE;

RE:
    knob_value = 0; 
       return IRQ_NONE;
}


static int knob_open(struct input_dev *dev)
{
    return 0;
}
static int knob_close(struct input_dev *dev)
{
    return 0;
}
static int knob_init()
{
    int err;
    /* Allocating GPIOs and setting direction */

    /**
     *                                    Table 2-1. L3 Memory Map
     * +-------------------+---------------------+-------------------+-------+---------------------------+
     * | Block Name        | Start_address (hex) | End_address (hex) | Size  | Description               | 
     * +-------------------+---------------------+-------------------+-------+---------------------------+
     * | GPMC              | 0x0000_0000(1)      | 0x1FFF_FFFF       | 512MB | 8-/16-bit External Memory | 
     * | (External Memory) |                     |                   |       | (Ex/R/W)                  | 
     * +-------------------+---------------------+-------------------+-------+---------------------------+
     */
    void __iomem * base = ioremap(Control_Module_address , 0x1FFF);
    __raw_writel(PINMUX_KNOD_IRQ     ,(base + CONFIG_XDMA_EVENT_INTR0_offset     ));
    __raw_writel(PINMUX_KNOD_ROTARYA ,(base + CONFIG_GPMC_A10_offset         ));
    __raw_writel(PINMUX_KNOD_ROTARYB ,(base + CONFIG_GPMC_A11_offset         ));
    // pad Pulldown selected
    __raw_writel(0x27, (base + CONFIG_MACSP0_FSX_offset));

    /**
     * #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
     * 通过这里,我们也就知道内核中的GPIO_TO_PIN是如何工作的了
     * 这里需要注意的就是,首先要配置GPIO的硬件选项,gpio_request属于内核软件层的内容
     * 当然,下面并没有做当出现错误的时候,需要释放前面已经申请了gpio口
     */
    err = gpio_request(KNOD_IRQ_BUTTON,"knob_irq_button");
    if ( err ) {
        pr_err("failed to request gpio knob_irq\n");
    //    return err;
    }
    err = gpio_request(KNOD_IRQ,"knob_irq");
    if ( err ) {
        pr_err("failed to request gpio knob_irq\n");
    //    return err;
    }
    err = gpio_request(KNOD_ROTARYA,"knob_rotarya");
    if ( err ) {
        pr_err("failed to request gpio knob_rotarya\n");
    //    return err;
    }
    err = gpio_request(KNOD_ROTARYB,"knob_rotarab");
    if ( err ) {
        pr_err("failed to request gpio knob_rotarab\n");
    //    return err;
    }

    err = gpio_direction_input(KNOD_IRQ);
    if ( err ) {
        pr_err("failed to set knob_irq ro input module.\n");
        return err;
    }
    err = gpio_direction_input(KNOD_IRQ_BUTTON);
    if ( err ) {
        pr_err("failed to set knob_irq ro input module.\n");
        return err;
    }
    err = gpio_direction_input(KNOD_ROTARYA);
    if ( err ) {
        pr_err("failed to set knob_rotarya ro input module.\n");
        return err;
    }
    err = gpio_direction_input(KNOD_ROTARYB);
    if ( err ) {
        pr_err("failed to set knob_rotaryb ro input module.\n");
        return err;
    }

    /**
     * input_allocate_device()函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化.  
     */
    knob_input = input_allocate_device();
    if (knob_input == NULL)
        return -ENOMEM;

    /**
     * set_bit()告诉input输入子系统支持哪些事件,哪些按键。
     * EV_KEY:      设定evbit支持EV_KEY 
     * KEY_LEFT:    支持向左按键
     * KEY_RIGHT:   支持向右按键
     * KEY_OK:      支持OK按键,目前不知道OK代表那个键
     */
    set_bit( EV_KEY, knob_input->evbit);
    set_bit( KEY_LEFT, knob_input->keybit);
    set_bit( KEY_RIGHT, knob_input->keybit);
    set_bit( KEY_OK    , knob_input->keybit);

    /**
     * cat cat /proc/bus/input/devices 
     *    ......
     *    I: Bus=0000 Vendor=0000 Product=0000 Version=0000
     *    N: Name="input_knob"
     *    P: Phys=
     *    S: Sysfs=/devices/virtual/input/input_knob_t
     *    U: Uniq=
     *    H: Handlers=kbd event1 
     *    B: PROP=0
     *    B: EV=3
     *    B: KEY=1 0 0 0 0 0 0 0 1680 0 0 0
     */
    knob_input->name = "input_knob";
    knob_input->dev.init_name = "input_knob_t";
    knob_input->open = knob_open;
    knob_input->close = knob_close;
    
    /**
     * 注册为输入设备
     */
    err = input_register_device(knob_input);
    if (err)
        return err;

    // 申请gpio口对应的中断
    irq_num_knob_in = gpio_to_irq(KNOD_IRQ);
    irq_num_knob_in_button = gpio_to_irq(KNOD_IRQ_BUTTON);

    /**
     * 1. 在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义:
     *    int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
     *       1. irq是要申请的硬件中断号。
     *       2. handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。
     *       3. irqflags是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版zhon已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)
     *       4. devname设置中断名称,通常是设备驱动程序的名称  在cat /proc/interrupts中可以看到此名称。
     *       5. dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
     *       6. request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。
     * 2. 这里的dev_id参数是&irq_num_knob_in,在knob_irq_handle()中并没有用到,这里应该可以传NULL
     */
    //IRQF_TRIGGER_FALLING 
    err = request_irq(irq_num_knob_in, &knob_irq_handle, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "IRQ_KNOB",&irq_num_knob_in );
    if ( err ) {
        pr_err(" cannot register irq %d .\n", irq_num_knob_in);
        return err;
    }

    err = request_irq(irq_num_knob_in_button, &knob_botton_irq_handle, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING   ,"IRQ_KNOB_BOTTON",&irq_num_knob_in_button);
    if ( err ) {
        pr_err(" cannot register irq %d .\n",irq_num_knob_in_button);
        return err;
    }

    knob_value_read_count = 1;

    printk("knob init success\n");

    return 0;
}

static int __init  knobm_init()
{
    knob_init();
    return 0;
}
static void  __exit  knobm_exit()
{
    /**
     * 这里仅仅释放了irq_num_knob_in中断,并没有释放irq_num_knob_in_button中断,
     * 具体原因未知。
     */
    free_irq(irq_num_knob_in,&irq_num_knob_in);
    /**
     * 注销input设备
     */
    input_unregister_device(knob_input);
}