AM335X——hwmon和input子系统

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2019/01/18/AM335X——hwmon和input子系统/#more

记录两个SPI设备分别采用hwmon子系统和input子系统。

刚开始学Linux驱动的时候,就看了input子系统,现在都忘得差不多了,不过回忆起来也还快,这里再记录一下。

为什么要用各种子系统框架,就目前的理解,一是为了向应用层提供统一的接口,二是简化了编写驱动的流程
各种子系统它们是通过一层一层的函数传递与封装,实现了设备驱动的注册,定义了file_operations结构体里面的各种函数操作,不需要在单独的设备驱动代码中进行注册、定义,直接调用相应的的子系统即可。

1. hwmon子系统简介

hwmon即硬件监控(Hardware monitor),它是用于检测设备状态的一类传感器设备接口,比如CPU温度、风扇转速、模数转换等。

Hwmon子系统的核心代码是drivers/hwmon/hwmon.c
通过同路径下的Kconfig文件,可以得知它在make menuconfig中的配置名字为Hardware Monitoring support
通过hwmon.c中的EXPORT_SYMBOL_GPL()符号,可知对外提供如下几组接口函数:

hwmon_device_register_with_groups() / hwmon_device_register()
hwmon_device_unregister()

devm_hwmon_device_register_with_groups()
devm_hwmon_device_unregister()

1.1 AD7705简介

AD7705是十六位分辨率的A/D转换器,2个通道全差分模拟输入。
内部有8个寄存器,常用的就Communication RegisterSetup registerClock registerData register
在每次做任何操作前,都要写Communication Register来设置即将操作的是哪一个寄存器、是读还是写操作、操作哪一个通道。
其操作流程如下:

  1. 拉低复位引脚,硬件复位;
  1. 在至少32个时钟周期里连续发送高脉冲,以同步时钟;
  2. 配置AD7705时钟(时钟源、分频系数等);
  3. 自校准,并等待就绪引脚拉低;
  4. 从数据寄存器里读取数据;

1.2 完整代码及效果

  • 设备树:
    {% codeblock lang:dts [am335x-evm.dts] %}
    spidev@0 {
    compatible = “titan,ad7705”;
    spi-max-frequency = <2500000>;
    reg = <2>;
    };
    {% endcodeblock %}

  • 驱动代码:
    {% codeblock lang:c [ad7705_drv.c] %}
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <linux/gpio.h>
    #include <linux/spi/spi.h>
    #include <linux/hwmon.h>
    #include <linux/hwmon-sysfs.h>

#define CHANNEL_NUM (2) //AD7705 channel number
#define DRDY_PIN (12) //AD7705 DRDY Pin(GPIO0_12)
#define RESET_PIN (13) //AD7705 RESET Pin(GPIO0_13)

//Communication Register
enum
{
REG_COMM = (0 << 4), //Communication Register RS2:RS1:RS0 = [0:0:0]
REG_SETUP = (1 << 4), //Setup register RS2:RS1:RS0 = [0:0:1]
REG_CLOCK = (2 << 4), //Clock register RS2:RS1:RS0 = [0:1:0]
REG_DATA = (3 << 4), //Data register RS2:RS1:RS0 = [0:1:1]
REG_TEST = (4 << 4), //Test register RS2:RS1:RS0 = [1:0:0]
REG_RESE = (5 << 4), //No operation RS2:RS1:RS0 = [1:0:1]
REG_OFFSET = (6 << 4), //Offset register RS2:RS1:RS0 = [1:1:0]
REG_GAIN = (7 << 4), //Gain register RS2:RS1:RS0 = [1:1:1]

CMD_WRITE        = (0 << 3), //write operation
CMD_READ         = (1 << 3), //read  operation

CH_1             = 0,        //Register Pair 0 (AIN1+  AIN1-)
CH_2             = 1,        //Register Pair 1 (AIN2+  AIN2-)
CH_3             = 2,        //Register Pair 0 (AIN1-  AIN1-)
CH_4             = 3         //Register Pair 2 (AIN1-  AIN2-)

};

//Setup register
enum
{
MD_NORMAL = (0 << 6), //Normal Mode.
MD_CAL_SELF = (1 << 6), //Self-Calibration
MD_CAL_ZERO = (2 << 6), //Zero-Scale System Calibration
MD_CAL_FULL = (3 << 6), //Full-Scale System Calibration

GAIN_1           = (0 << 3), //Gain 1  
GAIN_2           = (1 << 3), //Gain 2  
GAIN_4           = (2 << 3), //Gain 4  
GAIN_8           = (3 << 3), //Gain 8  
GAIN_16          = (4 << 3), //Gain 16 
GAIN_32          = (5 << 3), //Gain 32 
GAIN_64          = (6 << 3), //Gain 64 
GAIN_128         = (7 << 3), //Gain 128

BIPOLAR          = (0 << 2), //Bipolar Operation  
UNIPOLAR         = (1 << 2), //Unipolar Operation

BUF_NO           = (0 << 1), //Buffer Control Off
BUF_EN           = (1 << 1), //Buffer Control On

FSYNC_0          = (0 << 0), //Filter Synchronization Normal
FSYNC_1          = (1 << 0)  //Filter Synchronization Disable

};

//Clock register
enum
{
CLKDIS_0 = (0 << 4), //Master Clock Enable (Use crystal clock source)
CLKDIS_1 = (1 << 4), //Master Clock Disable(Use an external clock source)

CLKDIV_0         = (0 << 3), //Clock Divider 0
CLKDIV_1         = (1 << 3), //Clock Divider 2 (4.9152Mhz/2=2.4576Mhz)

CLK_0            = (0 << 2), //Clock Bit(If master clock 1MHz(CLKDIV = 0) or 2MHz(CLKDIV = 1)) 
CLK_1            = (1 << 2), //Clock Bit(If master clock 2.4576MHz(CLKDIV = 0) or 4.9152MHz(CLKDIV = 1)) 
//If Clock Bit = 0
UPDATE_20        = (0 << 0), //Filter Selection Bits
UPDATE_25        = (1 << 0),
UPDATE_100       = (2 << 0),
UPDATE_200       = (3 << 0),
//If Clock Bit = 1      
UPDATE_50        = (0 << 0), //Filter Selection Bits
UPDATE_60        = (1 << 0),
UPDATE_250       = (2 << 0),
UPDATE_500       = (3 << 0)

};

struct ad7705 {
struct device *hwmon_dev;
struct mutex lock;
};

//Reset
static void ad7705_reset(void)
{
gpio_direction_output(RESET_PIN, 1);
msleep(1);
gpio_direction_output(RESET_PIN, 0);
msleep(2);
gpio_direction_output(RESET_PIN, 1);
msleep(1);
}

//Synchronous SPI timing
static void ad7705_sync_spi(struct spi_device *spi)
{
u8 tx_buf[6]; //Write logic “1” to DIN for at least 32 clocks
memset(tx_buf, 0xFF, sizeof(tx_buf));
spi_write(spi, tx_buf, sizeof(tx_buf));

msleep(1);

}

//Waiting for DRDY pin signal
static int ad7705_wait_DRDY(void)
{
int i = 0;
int time_cnt = 500*1000;

for (i=0; i<time_cnt; i++)
{
    if (0 == gpio_get_value(DRDY_PIN))
        break;
    udelay(1);
}

if (i >= time_cnt)
    return -1; 
else
    return 0;

}

//Self-Calibration
static void ad7705_calib_self(struct spi_device *spi, u8 channel)
{
u8 tx_buf[2] = {0};

tx_buf[0] = REG_SETUP | CMD_WRITE | channel;
tx_buf[1] = MD_CAL_SELF | GAIN_1 | UNIPOLAR | BUF_EN | FSYNC_0;
spi_write(spi, tx_buf, sizeof(tx_buf));

ad7705_wait_DRDY(); //Waiting for the internal operation to complete, the time is long, about 180ms

msleep(50);

}

//Initialize the specified channel
static void ad7705_config_channel(struct spi_device *spi, u8 channel)
{
u8 tx_buf[2] = {0};

tx_buf[0] = REG_CLOCK | CMD_WRITE | channel;
tx_buf[1] = CLKDIS_0 | CLKDIV_1 | CLK_1 | UPDATE_50;
spi_write(spi, tx_buf, sizeof(tx_buf));

ad7705_calib_self(spi, channel); 

}

//Reset and initialize the specified channel
static void ad7705_reset_and_reconfig(struct spi_device *spi)
{
ad7705_reset();

msleep(5);
ad7705_sync_spi(spi);
msleep(5);

ad7705_config_channel(spi, CH_1);
ad7705_config_channel(spi, CH_2);

}

//Read the specified channel value
static int ad7705_read_channel(struct device *dev, u8 channel, u16 *data)
{
struct spi_device *spi = to_spi_device(dev);
struct ad7705 *adc = spi_get_drvdata(spi);
int ret = -1;
u16 value = 0;
u8 tx_buf[1] = {0};
u8 rx_buf[2] = {0};

if (mutex_lock_interruptible(&adc->lock))
    return -ERESTARTSYS;

if (ad7705_wait_DRDY() < 0)
{
    printk(KERN_ERR "[%s] ad7705_wait_DRDY() time out.\n", __FUNCTION__);
    goto fail;
}

tx_buf[0] = REG_DATA | CMD_READ | channel;
ret = spi_write_then_read(spi, tx_buf, sizeof(tx_buf), rx_buf, sizeof(rx_buf));
//printk("channel:%d rx_buf[0]=%d  rx_buf[1]=%d \n", channel, rx_buf[0], rx_buf[1]);
if (0 > ret)
{
    printk(KERN_ERR "[%s] ad7705_read_channel() fail. ret=%d\n", __FUNCTION__, ret);
    goto fail;
}
value = (rx_buf[0] << 8) + rx_buf[1];

#if 0
if (0xFFFF == value)
{
ret = -1;
goto fail;
}
#endif
*data = value;

fail:
mutex_unlock(&adc->lock);

return ret;

}

//sysfs hook function
static ssize_t ad7705_get_sensor_value(struct device *dev, struct device_attribute *devattr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
u16 ad = 0;
int i, ret = -1;
int vol1, vol2;

//After switching channels, the value of the other channel is read for the first time.
for (i=0; i<2; i++) 
{
    ret = ad7705_read_channel(dev, attr->index, &ad);
        continue;
    if (0 > ret)
    {
        ad7705_reset_and_reconfig(spi);
        return ret;
    }
}

#if 0
ret = sprintf(buf, “%u\n”, ad);
#else
vol1 = ad5/65535; //Voltage integral part
vol2 = (ad
51000/65535) - (vol11000);//Voltage fraction part
ret = sprintf(buf, “vol = %d.%dV\n”,vol1, vol2);
#endif

return ret;

}

static struct sensor_device_attribute ad_input[] = {
SENSOR_ATTR(ad7705_ch1, S_IRUGO, ad7705_get_sensor_value, NULL, CH_1),
SENSOR_ATTR(ad7705_ch2, S_IRUGO, ad7705_get_sensor_value, NULL, CH_2),
};

static int ad7705_probe(struct spi_device *spi)
{
struct ad7705 *adc = NULL;
int i, status;

adc = kzalloc(sizeof *adc, GFP_KERNEL);
if (!adc)
    return -ENOMEM;

mutex_init(&adc->lock);
mutex_lock(&adc->lock);

spi_set_drvdata(spi, adc);
for (i=0; i<CHANNEL_NUM; i++)
{
    status = device_create_file(&spi->dev, &ad_input[i].dev_attr);
    if (status)
    {
        dev_err(&spi->dev, "device_create_file() failed.\n");
        goto fail_crete_file;
    }
}

adc->hwmon_dev = hwmon_device_register(&spi->dev);
if (IS_ERR(adc->hwmon_dev))
{
    dev_err(&spi->dev, "hwmon_device_register() fail.\n");
    status = PTR_ERR(adc->hwmon_dev);
    goto fail_crete_file;
}

status = gpio_request(DRDY_PIN, "ad7705_drdy");   //ad7705 DRDY Pin
if (status)
{
    dev_err(&spi->dev, "gpio_request(AD705_DRDY_PIN) fail.\n");
    goto fail_device_register;
}
gpio_direction_input(DRDY_PIN);

status = gpio_request(RESET_PIN, "ad7705_reset");  //ad7705 RESET Pin
if (status)
{
    dev_err(&spi->dev, "gpio_request(RESET_PIN) fail.\n");
    goto fail_request_drdy_pin;
}
gpio_direction_output(RESET_PIN, 1);

ad7705_reset_and_reconfig(spi);

mutex_unlock(&adc->lock);
return 0;

fail_request_drdy_pin:
gpio_free(DRDY_PIN);
fail_device_register:
hwmon_device_unregister(adc->hwmon_dev);
fail_crete_file:
for (i–; i>=0; i–)
device_remove_file(&spi->dev, &ad_input[i].dev_attr);

spi_set_drvdata(spi, NULL);
mutex_unlock(&adc->lock);
kfree(adc);

return status;

}

static int ad7705_remove(struct spi_device *spi)
{
int i;
struct ad7705 *adc = spi_get_drvdata(spi);

mutex_lock(&adc->lock);

gpio_free(DRDY_PIN);
gpio_free(RESET_PIN);
hwmon_device_unregister(adc->hwmon_dev);
for (i=0; i<CHANNEL_NUM; i++)
    device_remove_file(&spi->dev, &ad_input[i].dev_attr);
spi_set_drvdata(spi, NULL);

mutex_unlock(&adc->lock);

kfree(adc);

return 0;

}

static const struct of_device_id of_match_spi[] = {
{ .compatible = “titan,ad7705”, .data = NULL },
{ /* sentinel */ }
};

static struct spi_driver ad7705_driver = {
.driver = {
.name = “ad7705”,
.owner = THIS_MODULE,
.of_match_table = of_match_spi,
},
.probe = ad7705_probe,
.remove = ad7705_remove,
};

module_spi_driver(ad7705_driver);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am335x board spi device: ad7705 driver.”);
MODULE_VERSION(“v1.0”);
{% endcodeblock %}

  • 测试代码:
    因为是子系统的关系,可以在应用层使用cat /sys/class/hwmon/hwmon0/device/ad7705_ch1直接得到结果。
    这里再使用该节点,写了个应用程序,以便进行连续访问:
    {% codeblock lang:c [ad7705_app.c] %}
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>

void read_channel(char *dev_file_path)
{
int fd = 0;
int ret = 0;
unsigned char buff[128] = {0};

fd = open(dev_file_path, O_RDONLY);
if (-1 == fd)
{
    printf("Can't open device file fail\n");
    return ;
}

memset(buff, 0, 128);
ret = read(fd, buff, 128);
if (0 > ret)
{
    printf("Can't read data\n");
}

printf("%s", buff);

close(fd);

}

int main(void)
{
char ch1_path[] = {"/sys/class/hwmon/hwmon0/device/ad7705_ch1"};
char ch2_path[] = {"/sys/class/hwmon/hwmon0/device/ad7705_ch2"};
//char ch1_path[] = {"/sys/bus/spi/drivers/ad7705/spi1.2/ad7705_ch1"};
//char ch2_path[] = {"/sys/bus/spi/drivers/ad7705/spi1.2/ad7705_ch2"};

while (1)
{
    read_channel(ch1_path);
    usleep(1000*1000);

    read_channel(ch2_path);
    usleep(1000*1000);
}

}
{% endcodeblock %}

值得注意的是,通过read()返回的数据不再是整型数据,而是字符串,且返回的字符串样式,在驱动里定义。

  • 效果:
cat /sys/class/hwmon/hwmon0/device/ad7705_ch1
vol = 2.1V

cat /sys/class/hwmon/hwmon0/device/ad7705_ch2
vol = 0.0V


./ad7705_app
vol = 2.1V
vol = 0.0V

1.3 详细分析

1.3.1 驱动与设备树匹配

ad7705_drv.cam335x-evm.dts各自中的compatible属性名字相同时,即调用probe()函数,进入一切的开端。

1.3.2 设置结构体

这里定义了一个ad7705的结构体,包含设备指针和互斥锁。
{% codeblock lang:c %}
struct ad7705 {
struct device *hwmon_dev;
struct mutex lock;
};
{% endcodeblock %}
probe()里定义和分配该结构体:
{% codeblock lang:c %}
struct ad7705 *adc = NULL;

adc = kzalloc(sizeof *adc, GFP_KERNEL);
if (!adc)
    return -ENOMEM;

{% endcodeblock %}
补充下内核中内存申请的相关知识:
内核中常用的内存申请函数有:kmalloc()kzalloc()vmalloc()

  • void *kmalloc(size_t size, gfp_t flags); void kfree(const void *objp);
  • kmalloc()申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。
  • 常用的flags:
      GFP_ATOMIC —— 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断;
      GFP_KERNEL —— 正常分配内存;
      GFP_DMA —— 给 DMA 控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续;

  • void *kzalloc(size_t size, gfp_t flags); void kfree(const void *objp);
  • kzalloc()就是调用的kmalloc(),多加了个__GFP_ZERO标志,即分配内存的时候还会将其清零;
  • 常用的flags:
      同kmalloc()

  • void *vmalloc(unsigned long size); void vfree(const void *addr);
  • vmalloc()会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于vmalloc()没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就可以用此函数了。vmalloc()vfree()可以睡眠,因此不能从中断上下文调用。

1.3.3 设置互斥锁

为了并发控制,加入互斥锁独占资源。
我好像写驱动都没加锁的习惯,以后改正。
互斥锁的使用比较简单,先初始化,再加锁,执行要做的内容,最后解锁。
{% codeblock lang:c %}
mutex_init(&adc->lock);
mutex_lock(&adc->lock);
……
mutex_unlock(&adc->lock);
{% endcodeblock %}

1.3.4 设置私有变量

将定义的ad7705结构体adc,保存到spi_device:
{% codeblock lang:c %}
spi_set_drvdata(spi, adc);
{% endcodeblock %}
后面其它函数就可以通过device得到spi_device,再提取到私有数据:
{% codeblock lang:c %}
struct spi_device *spi = to_spi_device(dev);
struct ad7705 *adc = spi_get_drvdata(spi);
{% endcodeblock %}

1.3.5 创建sysfs属性文件

首先解释一下sysfs
sysfs是Linux所提供的一种虚拟档案系统;
在设备模型中,sysfs文件系统用来表示设备的结构,将设备的层次结构形象的反应到用户空间中,从而可以通过修改sysfs中的文件属性来修改设备的属性值;
sysfs被挂载到根目录下的/sys文件夹下。

使用函数device_create_file()将会调用到sysfs_create_file(),将在/sys/class/hwmon/下创建文件夹,并按照hwmon0hwmon1hwmon2等顺序编号

第二个参数是struct device_attribute结构体,通过定义的sensor_device_attribute结构体成员传给它。
{% codeblock lang:c %}
static struct sensor_device_attribute ad_input[] = {
SENSOR_ATTR(ad7705_ch1, S_IRUGO, ad7705_get_sensor_value, NULL, CH_1),
SENSOR_ATTR(ad7705_ch2, S_IRUGO, ad7705_get_sensor_value, NULL, CH_2),
};
{% endcodeblock %}
参数含义分别是节点名字、节点访问权限(S_IRUGO:用户、组、其它成员都可读)、读函数(cat命令时将调用)、写函数(echo命令时将调用)、索引。

1.3.6 实现device_attribute的函数

前面的sensor_device_attribute只提供了读函数ad7705_get_sensor_value,并未提供写函数NULL,这里就只实现读函数即可。
{% codeblock lang:c %}
static ssize_t ad7705_get_sensor_value(struct device *dev, struct device_attribute *devattr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
u16 ad = 0;
int i, ret = -1;
int vol1, vol2;

//After switching channels, the value of the other channel is read for the first time.
for (i=0; i<2; i++) 
{
    ret = ad7705_read_channel(dev, attr->index, &ad);
        continue;
    if (0 > ret)
    {
        ad7705_reset_and_reconfig(spi);
        return ret;
    }
}

#if 0
ret = sprintf(buf, “%u\n”, ad);
#else
vol1 = ad5/65535; //Voltage integral part
vol2 = (ad
51000/65535) - (vol11000);//Voltage fraction part
ret = sprintf(buf, “vol = %d.%dV\n”,vol1, vol2);
#endif

return ret;

}
{% endcodeblock %}
AD7705有两个ADC通道,它们都调用ad7705_get_sensor_value(),但通过sensor_device_attribute结构体中的index成员,将会读指定通道值。
sprintf()将指定格式的数据传给用户层。

1.3.7 注册hwmon子系统

使用hwmon_device_register注册hwmon设备。

1.3.8 引脚GPIO初始化

AD7705除了SPI引脚,还用到了一个就绪引脚和复位引脚。
这两个脚的信息可以从设备树中指定,再获取,这里直接写死在驱动里面了。
{% codeblock lang:c %}
status = gpio_request(DRDY_PIN, “ad7705_drdy”); //ad7705 DRDY Pin
if (status)
{
dev_err(&spi->dev, “gpio_request(AD705_DRDY_PIN) fail.\n”);
goto fail_device_register;
}
gpio_direction_input(DRDY_PIN);

status = gpio_request(RESET_PIN, "ad7705_reset");  //ad7705 RESET Pin
if (status)
{
    dev_err(&spi->dev, "gpio_request(RESET_PIN) fail.\n");
    goto fail_request_drdy_pin;
}
gpio_direction_output(RESET_PIN, 1);

{% endcodeblock %}
GPIO常见的操作:

1.判断引脚是否合法
 int gpio_is_valid(int number);
2.申请该引脚
 int gpio_request(unsigned gpio, const char *label);
3.设置为输入还是输出
 int gpio_direction_input(unsigned gpio);
 int gpio_direction_output(unsigned gpio, int value);
4.获取/设置引脚电平
 int gpio_get_value(unsigned gpio);
 void gpio_set_value(unsigned gpio, int value);
5.申请作为中断引脚/释放中断
 int gpio_to_irq(unsigned gpio);
 void free_irq(unsigned int irq, void *dev_id);
6.导出到用户态(/sys/class/gpio/gpioN)
 int gpio_export(unsigned gpio, bool direction_may_change);
 void gpio_unexport(unsigned gpio);

1.3.9 复位和配置AD7705

调用ad7705_reset()硬件复位,调用ad7705_sync_spi()同步下时钟,调用ad7705_config_channel()配置时钟和自校准。

2. input子系统简介

输入子系统是对不同类型的输入设备进行统一处理的驱动程序。
一个输入事件,如按键,是通过设备驱动层->系统核心层->事件处理层->用户空间的顺序到达用户空间并传给应用程序使用。

Input子系统由设备驱动层系统核心层事件处理层三部分组成。
事件处理层的驱动程序叫事件驱动程序,它负责和应用程序的接口,其内容是标准的,对所有输入设备都可用,就不需要我们编写了。
设备驱动层的驱动程序叫设备驱动程序,它负责和底层输入设备的通信,需要我们自己编写。

2.1 ADXL345简介

ADXL345是一款小而薄的超低功耗3轴加速度计,分辨率为13位,测量范围达±16g。
数字输出数据为16位二进制补码格式,可通过SPI(3线或4线)或I2C数字接口访问。
芯片默认是SPI四线模式,且CPHA=1 CPOL=1,也就是一般SPI的模式3。
但我使用的ADXL345模块默认是I2C接口,即把12脚SDO接地了,如果使用SPI接口,需要去掉R4这个0欧电阻。

关于ADXL345的使用目的,目前能想到这几个:
1、读取加速度(加速度是矢量,既有大小又有方向),估计某些场合需要这个参数;
2、让ADXL345处于睡眠状态,当被摇动时候,触发最大阈值,从睡眠模式唤醒并从中断引脚输出高电平唤醒主控;
3、根据加速度计算得到角度值,进行屏幕翻转判定等;

Linux内核中自带ADXL345驱动,路径为:

drivers/input/misc/adxl34x-i2c.c
drivers/input/misc/adxl34x-spi.c

drivers/input/misc/adxl34x.c

ADI的官网有对该驱动使用、测试方法有详细介绍

2.2 代码框架介绍

以SPI接口为例,先理一下代码的框架,图中最左边有是设备驱动模型中的设备,可以由设备树或者C文件提供。 它们目的都是向`adxl34x-spi.c`提供时钟速度、片选引脚、中断引脚、SPI模式等信息。

如果是设备树,就得修改下adxl34x-spi.cam335x-evm.dts,使各自中的compatible属性匹配,从而调用adxl34x-spi.c中的probe()函数。
如果是C文件,就需要自己创建个board_info.c文件,填充spi_board_info内容,并注册。这里注意目前的内核已经没有将注册函数spi_register_board_info()导出,不能使用,可以通过spi_busnum_to_master()spi_new_device()的方式注册。最后spi_board_info中的.modaliasspi_driver中的.name名字匹配,则调用adxl34x-spi.c中的probe()函数。

{% codeblock lang:c %}
static int spi_info_init(void)
{
struct spi_master *master = NULL;
struct spi_device *device = NULL;

master = spi_busnum_to_master(am335x_spi1_slave_info->bus_num);
if (!master) 
	return -EINVAL;

device = spi_new_device(master, am335x_spi1_slave_info);
if (!device)
	return -EINVAL;

return 0;
//return spi_register_board_info(am335x_spi1_slave_info, ARRAY_SIZE(am335x_spi1_slave_info));

}
{% endcodeblock %}

然后中间的是设备驱动模型中的驱动,它可以看成三部分:
1、在probe()中调用adxl34x.c中的adxl34x_probe()
2、提供read()write()函数;
3、调用adxl34x.c的电源管理相关函数,支持电源管理模型;
如果使用I2C接口,其目的还是一样的。

最右边的就是ADXL345的具体操作内容,也是后面详细分析的重点。

2.3 完整代码及效果

  • 驱动部分:
    adxl34x-spi.cadxl34x.c的完整代码就没必要贴了。
    这里使用的设备树提供设备信息,只稍微的修改了点adxl34x-spi.c的内容:
    {% codeblock lang:c %}
    static const struct of_device_id of_match_spi[] = {
    { .compatible = “adi,adxl34x”, .data = NULL },
    { /* sentinel */ }
    };

static struct spi_driver adxl34x_driver = {
.driver = {
.name = “adxl34x”,
.owner = THIS_MODULE,
.pm = &adxl34x_spi_pm,
.of_match_table = of_match_spi,
},
.probe = adxl34x_spi_probe,
.remove = adxl34x_spi_remove,
};
{% endcodeblock %}

  • 设备树部分:
    {% codeblock lang:dts %}
    spidev@0 {
    compatible = “adi,adxl34x”;
    spi-max-frequency = <2500000>;
    reg = <0>;

      interrupt-parent = <&gpio0>;
      interrupts = <12 IRQ_TYPE_LEVEL_HIGH>;
      spi-cpha = <1>;
      spi-cpol  = <1>;
    

    };
    {% endcodeblock %}
    AM335X设备树中断的写法有两种,可以参考Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
    这里interrupt-parent指定使用那个GPIO中断控制器,interrupts属性第一个值为该组引脚中的引脚号,第二个为中断触发电平。

  • 测试程序:
    有两个开源的程序可用来读取input event事件的值,evtestgetevent(这个是从android中提取的)。
    如下是evtest源码,前面的ADI官网链接也使用了该测试程序。
    {% codeblock lang:c %}
    /*

  • $Id: evtest.c,v 1.23 2005/02/06 13:51:42 vojtech Exp $
  • Copyright © 1999-2000 Vojtech Pavlik
  • Event device test program
    */

/*

  • This program is free software; you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation; either version 2 of the License, or
  • (at your option) any later version.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
  • You should have received a copy of the GNU General Public License
  • along with this program; if not, write to the Free Software
  • Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  • Should you need to contact me, the author, you can do so either by
  • e-mail - mail your message to vojtech@ucw.cz, or by paper mail:
  • Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
    */

#include <stdint.h>

#include <linux/input.h>

#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

#ifndef EV_SYN
#define EV_SYN 0
#endif

char *events[EV_MAX + 1] =
{
[0 … EV_MAX] = NULL,
[EV_SYN] = “Sync”, [EV_KEY] = “Key”,
[EV_REL] = “Relative”, [EV_ABS] = “Absolute”,
[EV_MSC] = “Misc”, [EV_LED] = “LED”,
[EV_SND] = “Sound”, [EV_REP] = “Repeat”,
[EV_FF] = “ForceFeedback”, [EV_PWR] = “Power”,
[EV_FF_STATUS] = “ForceFeedbackStatus”,
};

char *keys[KEY_MAX + 1] =
{
[0 … KEY_MAX] = NULL,
[KEY_RESERVED] = “Reserved”, [KEY_ESC] = “Esc”,
[KEY_1] = “1”, [KEY_2] = “2”,
[KEY_3] = “3”, [KEY_4] = “4”,
[KEY_5] = “5”, [KEY_6] = “6”,
[KEY_7] = “7”, [KEY_8] = “8”,
[KEY_9] = “9”, [KEY_0] = “0”,
[KEY_MINUS] = “Minus”, [KEY_EQUAL] = “Equal”,
[KEY_BACKSPACE] = “Backspace”, [KEY_TAB] = “Tab”,
[KEY_Q] = “Q”, [KEY_W] = “W”,
[KEY_E] = “E”, [KEY_R] = “R”,
[KEY_T] = “T”, [KEY_Y] = “Y”,
[KEY_U] = “U”, [KEY_I] = “I”,
[KEY_O] = “O”, [KEY_P] = “P”,
[KEY_LEFTBRACE] = “LeftBrace”, [KEY_RIGHTBRACE] = “RightBrace”,
[KEY_ENTER] = “Enter”, [KEY_LEFTCTRL] = “LeftControl”,
[KEY_A] = “A”, [KEY_S] = “S”,
[KEY_D] = “D”, [KEY_F] = “F”,
[KEY_G] = “G”, [KEY_H] = “H”,
[KEY_J] = “J”, [KEY_K] = “K”,
[KEY_L] = “L”, [KEY_SEMICOLON] = “Semicolon”,
[KEY_APOSTROPHE] = “Apostrophe”, [KEY_GRAVE] = “Grave”,
[KEY_LEFTSHIFT] = “LeftShift”, [KEY_BACKSLASH] = “BackSlash”,
[KEY_Z] = “Z”, [KEY_X] = “X”,
[KEY_C] = “C”, [KEY_V] = “V”,
[KEY_B] = “B”, [KEY_N] = “N”,
[KEY_M] = “M”, [KEY_COMMA] = “Comma”,
[KEY_DOT] = “Dot”, [KEY_SLASH] = “Slash”,
[KEY_RIGHTSHIFT] = “RightShift”, [KEY_KPASTERISK] = “KPAsterisk”,
[KEY_LEFTALT] = “LeftAlt”, [KEY_SPACE] = “Space”,
[KEY_CAPSLOCK] = “CapsLock”, [KEY_F1] = “F1”,
[KEY_F2] = “F2”, [KEY_F3] = “F3”,
[KEY_F4] = “F4”, [KEY_F5] = “F5”,
[KEY_F6] = “F6”, [KEY_F7] = “F7”,
[KEY_F8] = “F8”, [KEY_F9] = “F9”,
[KEY_F10] = “F10”, [KEY_NUMLOCK] = “NumLock”,
[KEY_SCROLLLOCK] = “ScrollLock”, [KEY_KP7] = “KP7”,
[KEY_KP8] = “KP8”, [KEY_KP9] = “KP9”,
[KEY_KPMINUS] = “KPMinus”, [KEY_KP4] = “KP4”,
[KEY_KP5] = “KP5”, [KEY_KP6] = “KP6”,
[KEY_KPPLUS] = “KPPlus”, [KEY_KP1] = “KP1”,
[KEY_KP2] = “KP2”, [KEY_KP3] = “KP3”,
[KEY_KP0] = “KP0”, [KEY_KPDOT] = “KPDot”,
[KEY_ZENKAKUHANKAKU] = “Zenkaku/Hankaku”, [KEY_102ND] = “102nd”,
[KEY_F11] = “F11”, [KEY_F12] = “F12”,
[KEY_RO] = “RO”, [KEY_KATAKANA] = “Katakana”,
[KEY_HIRAGANA] = “HIRAGANA”, [KEY_HENKAN] = “Henkan”,
[KEY_KATAKANAHIRAGANA] = “Katakana/Hiragana”, [KEY_MUHENKAN] = “Muhenkan”,
[KEY_KPJPCOMMA] = “KPJpComma”, [KEY_KPENTER] = “KPEnter”,
[KEY_RIGHTCTRL] = “RightCtrl”, [KEY_KPSLASH] = “KPSlash”,
[KEY_SYSRQ] = “SysRq”, [KEY_RIGHTALT] = “RightAlt”,
[KEY_LINEFEED] = “LineFeed”, [KEY_HOME] = “Home”,
[KEY_UP] = “Up”, [KEY_PAGEUP] = “PageUp”,
[KEY_LEFT] = “Left”, [KEY_RIGHT] = “Right”,
[KEY_END] = “End”, [KEY_DOWN] = “Down”,
[KEY_PAGEDOWN] = “PageDown”, [KEY_INSERT] = “Insert”,
[KEY_DELETE] = “Delete”, [KEY_MACRO] = “Macro”,
[KEY_MUTE] = “Mute”, [KEY_VOLUMEDOWN] = “VolumeDown”,
[KEY_VOLUMEUP] = “VolumeUp”, [KEY_POWER] = “Power”,
[KEY_KPEQUAL] = “KPEqual”, [KEY_KPPLUSMINUS] = “KPPlusMinus”,
[KEY_PAUSE] = “Pause”, [KEY_KPCOMMA] = “KPComma”,
[KEY_HANGUEL] = “Hanguel”, [KEY_HANJA] = “Hanja”,
[KEY_YEN] = “Yen”, [KEY_LEFTMETA] = “LeftMeta”,
[KEY_RIGHTMETA] = “RightMeta”, [KEY_COMPOSE] = “Compose”,
[KEY_STOP] = “Stop”, [KEY_AGAIN] = “Again”,
[KEY_PROPS] = “Props”, [KEY_UNDO] = “Undo”,
[KEY_FRONT] = “Front”, [KEY_COPY] = “Copy”,
[KEY_OPEN] = “Open”, [KEY_PASTE] = “Paste”,
[KEY_FIND] = “Find”, [KEY_CUT] = “Cut”,
[KEY_HELP] = “Help”, [KEY_MENU] = “Menu”,
[KEY_CALC] = “Calc”, [KEY_SETUP] = “Setup”,
[KEY_SLEEP] = “Sleep”, [KEY_WAKEUP] = “WakeUp”,
[KEY_FILE] = “File”, [KEY_SENDFILE] = “SendFile”,
[KEY_DELETEFILE] = “DeleteFile”, [KEY_XFER] = “X-fer”,
[KEY_PROG1] = “Prog1”, [KEY_PROG2] = “Prog2”,
[KEY_WWW] = “WWW”, [KEY_MSDOS] = “MSDOS”,
[KEY_COFFEE] = “Coffee”, [KEY_DIRECTION] = “Direction”,
[KEY_CYCLEWINDOWS] = “CycleWindows”, [KEY_MAIL] = “Mail”,
[KEY_BOOKMARKS] = “Bookmarks”, [KEY_COMPUTER] = “Computer”,
[KEY_BACK] = “Back”, [KEY_FORWARD] = “Forward”,
[KEY_CLOSECD] = “CloseCD”, [KEY_EJECTCD] = “EjectCD”,
[KEY_EJECTCLOSECD] = “EjectCloseCD”, [KEY_NEXTSONG] = “NextSong”,
[KEY_PLAYPAUSE] = “PlayPause”, [KEY_PREVIOUSSONG] = “PreviousSong”,
[KEY_STOPCD] = “StopCD”, [KEY_RECORD] = “Record”,
[KEY_REWIND] = “Rewind”, [KEY_PHONE] = “Phone”,
[KEY_ISO] = “ISOKey”, [KEY_CONFIG] = “Config”,
[KEY_HOMEPAGE] = “HomePage”, [KEY_REFRESH] = “Refresh”,
[KEY_EXIT] = “Exit”, [KEY_MOVE] = “Move”,
[KEY_EDIT] = “Edit”, [KEY_SCROLLUP] = “ScrollUp”,
[KEY_SCROLLDOWN] = “ScrollDown”, [KEY_KPLEFTPAREN] = “KPLeftParenthesis”,
[KEY_KPRIGHTPAREN] = “KPRightParenthesis”, [KEY_F13] = “F13”,
[KEY_F14] = “F14”, [KEY_F15] = “F15”,
[KEY_F16] = “F16”, [KEY_F17] = “F17”,
[KEY_F18] = “F18”, [KEY_F19] = “F19”,
[KEY_F20] = “F20”, [KEY_F21] = “F21”,
[KEY_F22] = “F22”, [KEY_F23] = “F23”,
[KEY_F24] = “F24”, [KEY_PLAYCD] = “PlayCD”,
[KEY_PAUSECD] = “PauseCD”, [KEY_PROG3] = “Prog3”,
[KEY_PROG4] = “Prog4”, [KEY_SUSPEND] = “Suspend”,
[KEY_CLOSE] = “Close”, [KEY_PLAY] = “Play”,
[KEY_FASTFORWARD] = “Fast Forward”, [KEY_BASSBOOST] = “Bass Boost”,
[KEY_PRINT] = “Print”, [KEY_HP] = “HP”,
[KEY_CAMERA] = “Camera”, [KEY_SOUND] = “Sound”,
[KEY_QUESTION] = “Question”, [KEY_EMAIL] = “Email”,
[KEY_CHAT] = “Chat”, [KEY_SEARCH] = “Search”,
[KEY_CONNECT] = “Connect”, [KEY_FINANCE] = “Finance”,
[KEY_SPORT] = “Sport”, [KEY_SHOP] = “Shop”,
[KEY_ALTERASE] = “Alternate Erase”, [KEY_CANCEL] = “Cancel”,
[KEY_BRIGHTNESSDOWN] = “Brightness down”, [KEY_BRIGHTNESSUP] = “Brightness up”,
[KEY_MEDIA] = “Media”, [KEY_UNKNOWN] = “Unknown”,
[BTN_0] = “Btn0”, [BTN_1] = “Btn1”,
[BTN_2] = “Btn2”, [BTN_3] = “Btn3”,
[BTN_4] = “Btn4”, [BTN_5] = “Btn5”,
[BTN_6] = “Btn6”, [BTN_7] = “Btn7”,
[BTN_8] = “Btn8”, [BTN_9] = “Btn9”,
[BTN_LEFT] = “LeftBtn”, [BTN_RIGHT] = “RightBtn”,
[BTN_MIDDLE] = “MiddleBtn”, [BTN_SIDE] = “SideBtn”,
[BTN_EXTRA] = “ExtraBtn”, [BTN_FORWARD] = “ForwardBtn”,
[BTN_BACK] = “BackBtn”, [BTN_TASK] = “TaskBtn”,
[BTN_TRIGGER] = “Trigger”, [BTN_THUMB] = “ThumbBtn”,
[BTN_THUMB2] = “ThumbBtn2”, [BTN_TOP] = “TopBtn”,
[BTN_TOP2] = “TopBtn2”, [BTN_PINKIE] = “PinkieBtn”,
[BTN_BASE] = “BaseBtn”, [BTN_BASE2] = “BaseBtn2”,
[BTN_BASE3] = “BaseBtn3”, [BTN_BASE4] = “BaseBtn4”,
[BTN_BASE5] = “BaseBtn5”, [BTN_BASE6] = “BaseBtn6”,
[BTN_DEAD] = “BtnDead”, [BTN_A] = “BtnA”,
[BTN_B] = “BtnB”, [BTN_C] = “BtnC”,
[BTN_X] = “BtnX”, [BTN_Y] = “BtnY”,
[BTN_Z] = “BtnZ”, [BTN_TL] = “BtnTL”,
[BTN_TR] = “BtnTR”, [BTN_TL2] = “BtnTL2”,
[BTN_TR2] = “BtnTR2”, [BTN_SELECT] = “BtnSelect”,
[BTN_START] = “BtnStart”, [BTN_MODE] = “BtnMode”,
[BTN_THUMBL] = “BtnThumbL”, [BTN_THUMBR] = “BtnThumbR”,
[BTN_TOOL_PEN] = “ToolPen”, [BTN_TOOL_RUBBER] = “ToolRubber”,
[BTN_TOOL_BRUSH] = “ToolBrush”, [BTN_TOOL_PENCIL] = “ToolPencil”,
[BTN_TOOL_AIRBRUSH] = “ToolAirbrush”, [BTN_TOOL_FINGER] = “ToolFinger”,
[BTN_TOOL_MOUSE] = “ToolMouse”, [BTN_TOOL_LENS] = “ToolLens”,
[BTN_TOUCH] = “Touch”, [BTN_STYLUS] = “Stylus”,
[BTN_STYLUS2] = “Stylus2”, [BTN_TOOL_DOUBLETAP] = “Tool Doubletap”,
[BTN_TOOL_TRIPLETAP] = “Tool Tripletap”, [BTN_GEAR_DOWN] = “WheelBtn”,
[BTN_GEAR_UP] = “Gear up”, [KEY_OK] = “Ok”,
[KEY_SELECT] = “Select”, [KEY_GOTO] = “Goto”,
[KEY_CLEAR] = “Clear”, [KEY_POWER2] = “Power2”,
[KEY_OPTION] = “Option”, [KEY_INFO] = “Info”,
[KEY_TIME] = “Time”, [KEY_VENDOR] = “Vendor”,
[KEY_ARCHIVE] = “Archive”, [KEY_PROGRAM] = “Program”,
[KEY_CHANNEL] = “Channel”, [KEY_FAVORITES] = “Favorites”,
[KEY_EPG] = “EPG”, [KEY_PVR] = “PVR”,
[KEY_MHP] = “MHP”, [KEY_LANGUAGE] = “Language”,
[KEY_TITLE] = “Title”, [KEY_SUBTITLE] = “Subtitle”,
[KEY_ANGLE] = “Angle”, [KEY_ZOOM] = “Zoom”,
[KEY_MODE] = “Mode”, [KEY_KEYBOARD] = “Keyboard”,
[KEY_SCREEN] = “Screen”, [KEY_PC] = “PC”,
[KEY_TV] = “TV”, [KEY_TV2] = “TV2”,
[KEY_VCR] = “VCR”, [KEY_VCR2] = “VCR2”,
[KEY_SAT] = “Sat”, [KEY_SAT2] = “Sat2”,
[KEY_CD] = “CD”, [KEY_TAPE] = “Tape”,
[KEY_RADIO] = “Radio”, [KEY_TUNER] = “Tuner”,
[KEY_PLAYER] = “Player”, [KEY_TEXT] = “Text”,
[KEY_DVD] = “DVD”, [KEY_AUX] = “Aux”,
[KEY_MP3] = “MP3”, [KEY_AUDIO] = “Audio”,
[KEY_VIDEO] = “Video”, [KEY_DIRECTORY] = “Directory”,
[KEY_LIST] = “List”, [KEY_MEMO] = “Memo”,
[KEY_CALENDAR] = “Calendar”, [KEY_RED] = “Red”,
[KEY_GREEN] = “Green”, [KEY_YELLOW] = “Yellow”,
[KEY_BLUE] = “Blue”, [KEY_CHANNELUP] = “ChannelUp”,
[KEY_CHANNELDOWN] = “ChannelDown”, [KEY_FIRST] = “First”,
[KEY_LAST] = “Last”, [KEY_AB] = “AB”,
[KEY_NEXT] = “Next”, [KEY_RESTART] = “Restart”,
[KEY_SLOW] = “Slow”, [KEY_SHUFFLE] = “Shuffle”,
[KEY_BREAK] = “Break”, [KEY_PREVIOUS] = “Previous”,
[KEY_DIGITS] = “Digits”, [KEY_TEEN] = “TEEN”,
[KEY_TWEN] = “TWEN”, [KEY_DEL_EOL] = “Delete EOL”,
[KEY_DEL_EOS] = “Delete EOS”, [KEY_INS_LINE] = “Insert line”,
[KEY_DEL_LINE] = “Delete line”,
};

char *absval[5] = { “Value”, "Min ", "Max ", "Fuzz ", "Flat " };

char *relatives[REL_MAX + 1] =
{
[0 … REL_MAX] = NULL,
[REL_X] = “X”, [REL_Y] = “Y”,
[REL_Z] = “Z”, [REL_HWHEEL] = “HWheel”,
[REL_DIAL] = “Dial”, [REL_WHEEL] = “Wheel”,
[REL_MISC] = “Misc”,
};

char *absolutes[ABS_MAX + 1] =
{
[0 … ABS_MAX] = NULL,
[ABS_X] = “X”, [ABS_Y] = “Y”,
[ABS_Z] = “Z”, [ABS_RX] = “Rx”,
[ABS_RY] = “Ry”, [ABS_RZ] = “Rz”,
[ABS_THROTTLE] = “Throttle”, [ABS_RUDDER] = “Rudder”,
[ABS_WHEEL] = “Wheel”, [ABS_GAS] = “Gas”,
[ABS_BRAKE] = “Brake”, [ABS_HAT0X] = “Hat0X”,
[ABS_HAT0Y] = “Hat0Y”, [ABS_HAT1X] = “Hat1X”,
[ABS_HAT1Y] = “Hat1Y”, [ABS_HAT2X] = “Hat2X”,
[ABS_HAT2Y] = “Hat2Y”, [ABS_HAT3X] = “Hat3X”,
[ABS_HAT3Y] = “Hat 3Y”, [ABS_PRESSURE] = “Pressure”,
[ABS_DISTANCE] = “Distance”, [ABS_TILT_X] = “XTilt”,
[ABS_TILT_Y] = “YTilt”, [ABS_TOOL_WIDTH] = “Tool Width”,
[ABS_VOLUME] = “Volume”, [ABS_MISC] = “Misc”,
};

char *misc[MSC_MAX + 1] =
{
[ 0 … MSC_MAX] = NULL,
[MSC_SERIAL] = “Serial”, [MSC_PULSELED] = “Pulseled”,
[MSC_GESTURE] = “Gesture”, [MSC_RAW] = “RawData”,
[MSC_SCAN] = “ScanCode”,
};

char *leds[LED_MAX + 1] =
{
[0 … LED_MAX] = NULL,
[LED_NUML] = “NumLock”, [LED_CAPSL] = “CapsLock”,
[LED_SCROLLL] = “ScrollLock”, [LED_COMPOSE] = “Compose”,
[LED_KANA] = “Kana”, [LED_SLEEP] = “Sleep”,
[LED_SUSPEND] = “Suspend”, [LED_MUTE] = “Mute”,
[LED_MISC] = “Misc”,
};

char *repeats[REP_MAX + 1] =
{
[0 … REP_MAX] = NULL,
[REP_DELAY] = “Delay”, [REP_PERIOD] = “Period”
};

char *sounds[SND_MAX + 1] =
{
[0 … SND_MAX] = NULL,
[SND_CLICK] = “Click”, [SND_BELL] = “Bell”,
[SND_TONE] = “Tone”
};

char **names[EV_MAX + 1] =
{
[0 … EV_MAX] = NULL,
[EV_SYN] = events, [EV_KEY] = keys,
[EV_REL] = relatives, [EV_ABS] = absolutes,
[EV_MSC] = misc, [EV_LED] = leds,
[EV_SND] = sounds, [EV_REP] = repeats,
};

#define BITS_PER_LONG (sizeof(long) * 8)
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
#define OFF(x) ((x)%BITS_PER_LONG)
#define BIT(x) (1UL<<OFF(x))
#define LONG(x) ((x)/BITS_PER_LONG)
#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)

int main (int argc, char **argv)
{
int fd, rd, i, j, k;
struct input_event ev[64];
int version;
unsigned short id[4];
unsigned long bit[EV_MAX][NBITS(KEY_MAX)];
char name[256] = “Unknown”;
int abs[5];

if (argc < 2)
{
    printf("Usage: evtest /dev/input/eventX\n");
    printf("Where X = input device number\n");
    return 1;
}

if ((fd = open(argv[argc - 1], O_RDONLY)) < 0)
{
    perror("evtest");
    return 1;
}

if (ioctl(fd, EVIOCGVERSION, &version))
{
    perror("evtest: can't get version");
    return 1;
}

printf("Input driver version is %d.%d.%d\n",
       version >> 16, (version >> 8) & 0xff, version & 0xff);

ioctl(fd, EVIOCGID, id);
printf("Input device ID: bus 0x%x vendor 0x%x product 0x%x version 0x%x\n",
       id[ID_BUS], id[ID_VENDOR], id[ID_PRODUCT], id[ID_VERSION]);

ioctl(fd, EVIOCGNAME(sizeof(name)), name);
printf("Input device name: \"%s\"\n", name);

memset(bit, 0, sizeof(bit));
ioctl(fd, EVIOCGBIT(0, EV_MAX), bit[0]);
printf("Supported events:\n");

for (i = 0; i < EV_MAX; i++)
    if (test_bit(i, bit[0]))
    {
        printf(" Event type %d (%s)\n", i, events[i] ? events[i] : "?");
        if (!i) continue;
        ioctl(fd, EVIOCGBIT(i, KEY_MAX), bit[i]);
        for (j = 0; j < KEY_MAX; j++)
            if (test_bit(j, bit[i]))
            {
                printf(" Event code %d (%s)\n", j, names[i] ? (names[i][j] ? names[i][j] : "?") : "?");
                if (i == EV_ABS)
                {
                    ioctl(fd, EVIOCGABS(j), abs);
                    for (k = 0; k < 5; k++)
                        if ((k < 3) || abs[k])
                            printf(" %s %6d\n", absval[k], abs[k]);
                }
            }
    }


printf("Testing ... (interrupt to exit)\n");

while (1)
{
    rd = read(fd, ev, sizeof(struct input_event) * 64);

    if (rd < (int) sizeof(struct input_event))
    {
        printf("yyy\n");
        perror("\nevtest: error reading");
        return 1;
    }

    for (i = 0; i < rd / sizeof(struct input_event); i++)

        if (ev[i].type == EV_SYN)
        {
            printf("Event: time %ld.%06ld, -------------- %s ------------\n",
                   ev[i].time.tv_sec, ev[i].time.tv_usec, ev[i].code ? "Config Sync" : "Report Sync" );
        }
        else if (ev[i].type == EV_MSC && (ev[i].code == MSC_RAW || ev[i].code == MSC_SCAN))
        {
            printf("Event: time %ld.%06ld, type %d (%s), code %d (%s), value %02x\n",
                   ev[i].time.tv_sec, ev[i].time.tv_usec, ev[i].type,
                   events[ev[i].type] ? events[ev[i].type] : "?",
                   ev[i].code,
                   names[ev[i].type] ? (names[ev[i].type][ev[i].code] ? names[ev[i].type][ev[i].code] : "?") : "?",
                   ev[i].value);
        }
        else
        {
            printf("Event: time %ld.%06ld, type %d (%s), code %d (%s), value %d\n",
                   ev[i].time.tv_sec, ev[i].time.tv_usec, ev[i].type,
                   events[ev[i].type] ? events[ev[i].type] : "?",
                   ev[i].code,
                   names[ev[i].type] ? (names[ev[i].type][ev[i].code] ? names[ev[i].type][ev[i].code] : "?") : "?",
                   ev[i].value);
        }

}

}
{% endcodeblock %}

  • 测试效果:
# ./evtest /dev/input/event2
Input driver version is 1.0.1
Input device ID: bus 0x1c vendor 0x0 product 0x159 version 0x0
Input device name: "ADXL34x accelerometer"
Supported events:
 Event type 0 (Sync)
 Event type 1 (Key)
 Event code 330 (Touch)
 Event type 3 (Absolute)
 Event code 0 (X)
 Value      0
 Min   -4096
 Max    4096
 Fuzz       3
 Flat       3
 Event code 1 (Y)
 Value      0
 Min   -4096
 Max    4096
 Fuzz       3
 Flat       3
 Event code 2 (Z)
 Value      0
 Min   -4096
 Max    4096
 Fuzz       3
 Flat       3
Testing ... (interrupt to exit)
Event: time 3949236.075868, type 3 (Absolute), code 0 (X), value -223
Event: time 3949236.075868, type 3 (Absolute), code 1 (Y), value -10
Event: time 3949236.075868, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949236.075868, -------------- Report Sync ------------
Event: time 3949236.115188, type 3 (Absolute), code 0 (X), value -235
Event: time 3949236.115188, -------------- Report Sync ------------
Event: time 3949236.154590, type 3 (Absolute), code 0 (X), value -234
Event: time 3949236.154590, -------------- Report Sync ------------
Event: time 3949236.824446, type 3 (Absolute), code 2 (Z), value -14
Event: time 3949236.824446, -------------- Report Sync ------------
Event: time 3949236.863862, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949236.863862, -------------- Report Sync ------------
Event: time 3949236.903264, type 3 (Absolute), code 2 (Z), value -12
Event: time 3949236.903264, -------------- Report Sync ------------
Event: time 3949236.942659, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949236.942659, -------------- Report Sync ------------
Event: time 3949237.021471, type 3 (Absolute), code 2 (Z), value -12
Event: time 3949237.021471, -------------- Report Sync ------------
Event: time 3949237.257883, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949237.257883, -------------- Report Sync ------------
Event: time 3949238.006540, type 3 (Absolute), code 0 (X), value -233
Event: time 3949238.006540, type 3 (Absolute), code 1 (Y), value -11
Event: time 3949238.006540, -------------- Report Sync ------------
Event: time 3949238.045949, type 3 (Absolute), code 2 (Z), value -14
Event: time 3949238.045949, -------------- Report Sync ------------
Event: time 3949238.085346, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949238.085346, -------------- Report Sync ------------
Event: time 3949238.164144, type 3 (Absolute), code 1 (Y), value -10
Event: time 3949238.164144, -------------- Report Sync ------------
Event: time 3949238.361159, type 3 (Absolute), code 2 (Z), value -12
Event: time 3949238.361159, -------------- Report Sync ------------
Event: time 3949238.400569, type 3 (Absolute), code 0 (X), value -234
Event: time 3949238.400569, -------------- Report Sync ------------
Event: time 3949238.676384, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949238.676384, -------------- Report Sync ------------
Event: time 3949238.755194, type 3 (Absolute), code 0 (X), value -241
Event: time 3949238.755194, type 3 (Absolute), code 1 (Y), value -1
Event: time 3949238.755194, type 3 (Absolute), code 2 (Z), value 19
Event: time 3949238.755194, -------------- Report Sync ------------
Event: time 3949238.794592, type 3 (Absolute), code 0 (X), value -298
Event: time 3949238.794592, type 3 (Absolute), code 1 (Y), value 42
Event: time 3949238.794592, type 3 (Absolute), code 2 (Z), value -33

2.4 详细分析

2.4.1 几个重要结构体

adxl34x.cprobe()一进来就定义了三个结构体:

	struct adxl34x *ac;
	struct input_dev *input_dev;
	const struct adxl34x_platform_data *pdata;

adxl34x就是针对设备量身定制的,包含所有信息,在对其进行一系列的设置后,在adxl34x-spi.cprobe()里,使用spi_set_drvdata()将其保存到spi_device的私有数据里,以后其它函数可以通过spi_get_drvdata()得到该结构体,从而获取设备的所有信息。
input_dev表示一个输入设备,里面有一个输入设备的属性定义。
adxl34x_platform_data包含adxl34x设备的属性定义。

定义完后,进行初始化:
{% codeblock lang:c %}
ac = kzalloc(sizeof(*ac), GFP_KERNEL);
input_dev = input_allocate_device();
if (!ac || !input_dev) {
err = -ENOMEM;
goto err_free_mem;
}

ac->fifo_delay = fifo_delay_default;

pdata = dev_get_platdata(dev);
if (!pdata) {
	dev_dbg(dev,
		"No platform data: Using default initialization\n");
	pdata = &adxl34x_default_init;
}

{% endcodeblock %}
adxl34x使用kzalloc()分配空间并清零。
input_dev使用输入子系统提供的input_allocate_device()分配空间。
adxl34x_platform_data先尝试从device获取,没有的话使用默认的配置,也就是adxl34x_default_init的内容,里面有ADXL34X支持何种输入事件、电源模式等。

2.4.2 判断中断

ADXL34X工作时,通过中断引脚通知主机发生了某中断事件,因此中断是必不可少的,这里检测如果没有中断则返回错误。
{% codeblock lang:c %}
if (!irq) {
dev_err(dev, “no IRQ?\n”);
err = -ENODEV;
goto err_out;
}

{% endcodeblock %}

2.4.3 设置adxl34x

{% codeblock lang:c %}
ac->fifo_delay = fifo_delay_default;

ac->pdata = *pdata;
pdata = &ac->pdata;

ac->input = input_dev;
ac->dev = dev;
ac->irq = irq;
ac->bops = bops;

mutex_init(&ac->mutex);

input_dev->name = "ADXL34x accelerometer";
revid = AC_READ(ac, DEVID);

switch (revid) {
case ID_ADXL345:
	ac->model = 345;
	break;
case ID_ADXL346:
	ac->model = 346;
	break;
default:
	dev_err(dev, "Failed to probe %s\n", input_dev->name);
	err = -ENODEV;
	goto err_free_mem;
}

snprintf(ac->phys, sizeof(ac->phys), "%s/input0", dev_name(dev));

{% endcodeblock %}
input_devadxl34x_platform_data指向adxl34x
将传入的参数赋值给adxl34x,初始化部分adxl34x参数;
还读取设备ID判断是ADXL345,还是ADXL346。

2.4.4 设置input_dev

先使用__set_bit(ac->pdata.ev_type, input_dev->evbit);设置支持哪一类事件,由前面的adxl34x_default_init可知为EV_ABS
如果为EV_ABS类型,再使用_set_bit()input_set_abs_params()设置能产生该类的哪些事件。其中input_set_abs_params()也是调用的__set_bit(),参数依次为input设备指针、坐标轴、最小范围、最大范围、过滤值、丢弃范围值。

    __set_bit(ac->pdata.ev_type, input_dev->evbit);
    ……
    __set_bit(ABS_X, input_dev->absbit);
    __set_bit(ABS_Y, input_dev->absbit);
    __set_bit(ABS_Z, input_dev->absbit);
    ……
    input_set_abs_params(input_dev, ABS_X, -range, range, 3, 3);
    input_set_abs_params(input_dev, ABS_Y, -range, range, 3, 3);
    input_set_abs_params(input_dev, ABS_Z, -range, range, 3, 3);

接下来设置支持EV_KEY类事件,再具体设置支持类中的哪些事件:

    __set_bit(EV_KEY, input_dev->evbit);
    __set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
    __set_bit(pdata->ev_code_tap[ADXL_Y_AXIS], input_dev->keybit);
    __set_bit(pdata->ev_code_tap[ADXL_Z_AXIS], input_dev->keybit);

可以看出,设置input_dev的思路就是先设置支持哪一类事件,再设置类中的具体事件

input子系统中支持的类型含义:

EV_SYN 同步事件 
EV_KEY 键盘事件 
EV_REL 相对坐标事件,用于鼠标 
EV_ABS 绝对坐标事件,用于摇杆 
EV_MSC 其他事件 
EV_LED LED灯事件 
EV_SND 声音事件 
EV_REP 重复按键事件 
EV_FF  受力事件 
EV_PWR 电源事件 
EV_FF_STATUS 受力状态事件

{% codeblock lang:c %}
input_dev->phys = ac->phys;
input_dev->dev.parent = dev;
input_dev->id.product = ac->model;
input_dev->id.bustype = bops->bustype;
input_dev->open = adxl34x_input_open;
input_dev->close = adxl34x_input_close;

input_set_drvdata(input_dev, ac);

__set_bit(ac->pdata.ev_type, input_dev->evbit);

if (ac->pdata.ev_type == EV_REL) {
	__set_bit(REL_X, input_dev->relbit);
	__set_bit(REL_Y, input_dev->relbit);
	__set_bit(REL_Z, input_dev->relbit);
} else {
	/* EV_ABS */
	__set_bit(ABS_X, input_dev->absbit);
	__set_bit(ABS_Y, input_dev->absbit);
	__set_bit(ABS_Z, input_dev->absbit);

	if (pdata->data_range & FULL_RES)
		range = ADXL_FULLRES_MAX_VAL;	/* Signed 13-bit */
	else
		range = ADXL_FIXEDRES_MAX_VAL;	/* Signed 10-bit */

	input_set_abs_params(input_dev, ABS_X, -range, range, 3, 3);
	input_set_abs_params(input_dev, ABS_Y, -range, range, 3, 3);
	input_set_abs_params(input_dev, ABS_Z, -range, range, 3, 3);
}

__set_bit(EV_KEY, input_dev->evbit);
__set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Y_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Z_AXIS], input_dev->keybit);

if (pdata->ev_code_ff) {
	ac->int_mask = FREE_FALL;
	__set_bit(pdata->ev_code_ff, input_dev->keybit);
}

if (pdata->ev_code_act_inactivity)
	__set_bit(pdata->ev_code_act_inactivity, input_dev->keybit);

{% endcodeblock %}

2.4.5 设置中断

ADXL345的中断引脚默认为高电平有效,也可自行修改寄存器改为低电平有效。
所有功能都可以同时使用,但是,一些功能可能需要共享中断引脚。
中断功能的详细介绍参考手册,内容有点多,有产生新数据就产生中断,加速度超过某个值产生中断,两次加速度超过某个值产生中断等。

这里根据adxl34x_platform_data的内容,设置adxl34xint_mask
再通过request_threaded_irq()申请线程中断,绑定中断处理函数,并把adxl34x传给中断处理函数。
{% codeblock lang:c %}
ac->int_mask |= ACTIVITY | INACTIVITY;

if (pdata->watermark) {
	ac->int_mask |= WATERMARK;
	if (!FIFO_MODE(pdata->fifo_mode))
		ac->pdata.fifo_mode |= FIFO_STREAM;
} else {
	ac->int_mask |= DATA_READY;
}

if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN))
	ac->int_mask |= SINGLE_TAP | DOUBLE_TAP;

if (FIFO_MODE(pdata->fifo_mode) == FIFO_BYPASS)
	ac->fifo_delay = false;

AC_WRITE(ac, POWER_CTL, 0);

err = request_threaded_irq(ac->irq, NULL, adxl34x_irq,
			   IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
			   dev_name(dev), ac);
if (err) {
	dev_err(dev, "irq %d busy?\n", ac->irq);
	goto err_free_mem;
}

{% endcodeblock %}

在产生中断时,进入中断处理函数adxl34x_irq(),读取中断源寄存器,判断产生的何种中断,调用相应的处理函数。
调用input_event()设置要上报的数据值,再调用input_sync()上报。

2.4.6 创建sysfs属性文件

同前面的AD7705一样,这里也在/sys/class/input/下创建节点,直接可以访问。
{% codeblock lang:c %}
err = sysfs_create_group(&dev->kobj, &adxl34x_attr_group);
if (err)
goto err_free_irq;
{% endcodeblock %}

根据传入的attribute_group结构体可以找到具体实现了哪些功能:
{% codeblock lang:c %}
static struct attribute *adxl34x_attributes[] = {
&dev_attr_disable.attr,
&dev_attr_calibrate.attr,
&dev_attr_rate.attr,
&dev_attr_autosleep.attr,
&dev_attr_position.attr,
#ifdef ADXL_DEBUG
&dev_attr_write.attr,
#endif
NULL
};

static const struct attribute_group adxl34x_attr_group = {
.attrs = adxl34x_attributes,
};

static DEVICE_ATTR(position, S_IRUGO, adxl34x_position_show, NULL);

static DEVICE_ATTR(autosleep, 0664,
adxl34x_autosleep_show, adxl34x_autosleep_store);

static DEVICE_ATTR(rate, 0664, adxl34x_rate_show, adxl34x_rate_store);

static DEVICE_ATTR(calibrate, 0664,
adxl34x_calibrate_show, adxl34x_calibrate_store);

static DEVICE_ATTR(disable, 0664, adxl34x_disable_show, adxl34x_disable_store);
{% endcodeblock %}

可以看出提供了position,获取当前三轴状态;autosleep,设置自动休眠;rate,设置输出数据速率;calibrate,设备校准;disable,启用/禁用设备。

与实测内容吻合。

# ls /sys/class/input/input2
capabilities  id            name          properties    uniq
device        modalias      phys          subsystem
event2        mouse1        power         uevent

2.4.7 注册输入子系统

{% codeblock lang:c %}
err = input_register_device(input_dev);
if (err)
goto err_remove_attr;
{% endcodeblock %}

2.4.8 硬件操作

最后,使用adxl34x-spi.c提供的传输函数,根据adxl34x的内容操作ADXL345。

{% codeblock lang:c %}
AC_WRITE(ac, OFSX, pdata->x_axis_offset);
ac->hwcal.x = pdata->x_axis_offset;
AC_WRITE(ac, OFSY, pdata->y_axis_offset);
ac->hwcal.y = pdata->y_axis_offset;
AC_WRITE(ac, OFSZ, pdata->z_axis_offset);
ac->hwcal.z = pdata->z_axis_offset;
AC_WRITE(ac, THRESH_TAP, pdata->tap_threshold);
AC_WRITE(ac, DUR, pdata->tap_duration);
AC_WRITE(ac, LATENT, pdata->tap_latency);
AC_WRITE(ac, WINDOW, pdata->tap_window);
AC_WRITE(ac, THRESH_ACT, pdata->activity_threshold);
AC_WRITE(ac, THRESH_INACT, pdata->inactivity_threshold);
AC_WRITE(ac, TIME_INACT, pdata->inactivity_time);
AC_WRITE(ac, THRESH_FF, pdata->free_fall_threshold);
AC_WRITE(ac, TIME_FF, pdata->free_fall_time);
AC_WRITE(ac, TAP_AXES, pdata->tap_axis_control);
AC_WRITE(ac, ACT_INACT_CTL, pdata->act_axis_control);
AC_WRITE(ac, BW_RATE, RATE(ac->pdata.data_rate) |
(pdata->low_power_mode ? LOW_POWER : 0));
AC_WRITE(ac, DATA_FORMAT, pdata->data_range);
AC_WRITE(ac, FIFO_CTL, FIFO_MODE(pdata->fifo_mode) |
SAMPLES(pdata->watermark));

if (pdata->use_int2) {
	/* Map all INTs to INT2 */
	AC_WRITE(ac, INT_MAP, ac->int_mask | OVERRUN);
} else {
	/* Map all INTs to INT1 */
	AC_WRITE(ac, INT_MAP, 0);
}

if (ac->model == 346 && ac->pdata.orientation_enable) {
	AC_WRITE(ac, ORIENT_CONF,
		ORIENT_DEADZONE(ac->pdata.deadzone_angle) |
		ORIENT_DIVISOR(ac->pdata.divisor_length));

	ac->orient2d_saved = 1234;
	ac->orient3d_saved = 1234;

	if (pdata->orientation_enable & ADXL_EN_ORIENTATION_3D)
		for (i = 0; i < ARRAY_SIZE(pdata->ev_codes_orient_3d); i++)
			__set_bit(pdata->ev_codes_orient_3d[i],
				  input_dev->keybit);

	if (pdata->orientation_enable & ADXL_EN_ORIENTATION_2D)
		for (i = 0; i < ARRAY_SIZE(pdata->ev_codes_orient_2d); i++)
			__set_bit(pdata->ev_codes_orient_2d[i],
				  input_dev->keybit);
} else {
	ac->pdata.orientation_enable = 0;
}

AC_WRITE(ac, INT_ENABLE, ac->int_mask | OVERRUN);

ac->pdata.power_mode &= (PCTL_AUTO_SLEEP | PCTL_LINK);

{% endcodeblock %}

3. 总结

通过这两个子系统的驱动代码,总结几个之前写驱动没有意识。
1、自定义一个结构体,包含device和设备私有属性,其它函数可通过它获取到所有数据;
2、使用互斥锁的意识;
3、创建sysfs内容,方便应用层直接访问;

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值