PCF8591 I2C AD/DA KMOD 开发记录

PCF8591四路AD, 一路DA, 好测试, 时序比较简单.

 

模块基础

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <linux/module.h>       // module basis
#include <linux/kernel.h> // module basis
#include <linux/init.h> // module basis
#include <linux/slab.h> // kzalloc, kmalloc
#include <linux/errno.h> // E* MACRO
#include <linux/i2c.h> // i2c driver base
#include <linux/mutex.h> // mutex base
#include <linux/cdev.h> // character device base
#include <linux/fs.h> // always use with cdev.h
#include <linux/uaccess.h> // always use with fs.h
#include <linux/device.h> // always with cdev.h

MODULE_LICENSE("GPL");

OF匹配构建

1
2
3
4
5
6
7
8
9
static const struct of_device_id pcf8591_ids[] = {
{.compatible = "nxpl, pcf8591", },
{}
};
static const struct i2c_device_id pcf8591_id[] = {
{"pcf8591", 0},
{},
};
MODULE_DEVICE_TABLE(i2c, pcf8591_id);

i2c驱动构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int pcf8591_probe(struct i2c_client *client, 
const struct i2c_device_id *id);
static int pcf8591_remove(struct i2c_client *client);

static struct i2c_driver pcf8591_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "pcf8591",
.of_match_table = of_match_ptr(pcf8591_ids),
},
.probe = pcf8591_probe,
.remove = pcf8591_remove,
.id_table = pcf8591_id,
};
module_i2c_driver(pcf8591_i2c_driver);

note: MODULE_DEVICE_TABLElinux/module.h, 注释为Creates an alias so file2alias.c can find device table.

<<Linux Device Drivers Developme>> - John Madieu
Platform Device Drivers > How can platform devices and platform drivers match
So far, we have only discussed how to fill different structures of both devices and drivers.But now we will see how they are registered with the kernel, and how Linux knows which devices are handled by which driver. The answer is MODULE_DEVICE_TABLE. This macro lets a driver expose its ID table, which describes which devices it can support. In the meantime, if the driver can be compiled as a module, the driver.name field should match the module name. If it does not match, the module won’t be automatically loaded, unless we have used the MODULE_ALIAS macro to add another name for the module. At compilation time, that information is extracted from all the drivers in order to build a device table. When the kernel has to find the driver for a device (when a matching needs to be performed), the device table is walked through by the kernel. If an entry is found matching the compatible (for device tree), device/vendor id or name (for device ID table or name) of the added device, then the module providing that match is loaded (running the module’s init function), and the probe function is called. The MODULE_DEVICE_TABLE macro is defined in linux/module.h.

简单来讲就是MODULE_DEVICE_TABLE告诉内核这个驱动可以用来处理哪些设备. 内核通过匹配ID的方式加载驱动并调用init函数. 由init函数进一步调用probe函数. 一般init函数由平台给出. 编译为模块时driver.name与name应该匹配. 需要加载时可以只匹配一部分. (制造商ID或名称).

字符设备输出构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define PCF_DEVICE_NAME "pcf8591"
static unsigned int pcf_major = 0;
static unsigned int minor = 0;
static struct class *pcf_class = NULL;

int pcf8591_open(struct inode *inode, struct file *filp);
int pcf8591_release(struct inode *inode, struct file *filp);
ssize_t pcf8591_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_ops);
ssize_t pcf8591_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_ops);
struct file_operations pcf8591_fops = {
.owner = THIS_MODULE,
.open = pcf8591_open,
.release = pcf8591_release,
.write = pcf8591_write,
.read = pcf8591_read,
};

私有变量定义

pcf8591有4个通道

1
2
3
4
#define PCF_CH0 0x00
#define PCF_CH1 0X01
#define PCF_CH2 0x02
#define PCF_CH3 0x03

pcf8591有4种工作方式

1
2
3
4
5
6
7
8
9
10
11
#define PCF_MODE0 0X00 << 4
#define PCF_MODE1 0X01 << 4
#define PCF_MODE2 0X02 << 4
#define PCF_MODE3 0x03 << 4

#define PCF_FOUR_CHANNEL PCF_MODE0
#define PCF_THREE_DIFF_CHANNEL PCF_MODE1
#define PCF_ONE_DIFF_CHANNEL PCF_MODE2
#define PCF_TWO_DIFF_CHANNEL PCF_MODE3

const u8 channel_num[] = {4, 3, 3, 2};

驱动中用不上自增, pcf8591有一个DA输出

1
#define PCF_DA_ENABLE 0x01 << 6

预构设备树

1
2
3
4
5
6
7
8
pcf8591: pcf8591@48{
compatible = "nxpl, pcf8591";
reg = <0x48>;
status = "okay";
mode = <0x00>;
enable_da;
da_value = <0x80>;
};

确定从设备树中取到什么

1
2
3
4
uint8_t dat0, dat1;
of_property_read_u8(client->dev.of_node, "mode", &dat0);
dat0 |= of_property_read_bool(client->dev.of_node, "enable_da") << 6;
of_property_read_u8(client->dev.of_node, "da_value", &dat1);

所有东西扔到一起, 一个文件一个私有结构体.

1
2
3
4
5
6
7
8
9
10
struct pcf_dev {
unsigned char control;
unsigned char data; // 实际对AD来讲上卵用没有
unsigned char channel; // 实际上也是卵用没有
struct i2c_client *client; //
struct device *device; // use with cdev
struct mutex *lock; // avoid access i2c conflict
struct list_head instance; // to find this shit to free.
struct cdev cdev; // use for delete this shit before free.
};

驱动实体

probe

第一步当然是检查I2C功能性

1
2
if(!i2c_check_functionality(client->adapter I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;

初始化芯片, 输出一个设定DA电压解除高阻态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
u8 init_series[2];
struct i2c_msg msg;
/* get data from device tree */
of_property_read_u8(client->dev.of_node, "mode", &init_series[0]);
init_series[0] |= of_property_read_bool(client->dev.of_node, "enable_da") << 6;
of_property_read_u8(client->dev.of_node, "da_value", &init_series[1]);
control = init_series[0];
/* send control byte */
msg.addr = client->addr;
msg.flags = 0; /* Write */
msg.len = 2; /* control bytes + dac byte */
msg.buf = init_series;
if(i2c_transfer(client->adapter, &msg, 1) < 0)
pr_err("pcf8591: i2c_transfer failed\n"); // hush hush, god knows you guys have used what to result a transfer failure.

创建设备

1
2
3
4
5
6
7
8
9
10
11
12
13
/* GLOBAL VARS START */
static unsigned int pcf_major = 0;
static unsigned int minor = 0;
/* GLOBAL VARS END */

dev_t devno = 0;
int err = 0;
err = alloc_chrdev_region(&devno, 0, 1, PCF_DEVICE_NAME); // beg kernel to give ONE major number which minor is ZERO and named with PCF_DEVICE_NAME for us.
if(err < 0){
pr_err("region alloc failed for %s\n", PCF_DEVICE_NAME);
return err;
}
pcf_major = MAJOR(devno);

remove里面释放设备

1
unregister_chrdev_region(MKDEV(pcf_major, 0), 1);

创建设备类

1
2
3
4
5
6
7
8
9
/* GLOBAL VARS START */
static struct class *pcf_class = NULL;
/* GLOBAL VARS END */

pcf_class = class_create(THIS_MODULE, PCF_DEVICE_NAME);
if(IS_ERR(pcf_class)){
err = PTR_ERR(pcf_class);
goto fail;
}

remove里面释放设备类

1
class_destroy(pcf_class);

创建字符设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* prepare */
pcf_device = (struct pcf_dev *)kzalloc(sizeof(struct pcf_dev), GFP_KERNEL);
pcf_device->lock = (struct mutex*)kzalloc(sizeof(struct mutex),GFP_KERNEL);
if((pcf_device == NULL) | (pcf_device->lock == NULL)){
err = -ENOMEM;
goto fail;
}
INIT_LIST_HEAD(&pcf_device->instance);
i2c_set_clientdata(client, pcf_device); // when i2c_client allocate?
da_en = control & PCF_DA_ENABLE;
channels = channel_num[(control>>4)&0x03] + da_en;

/* setup adc chrdev */
while(channels -- ){
struct pcf_dev *dev;
dev = (struct pcf_dev*)kzalloc(sizeof(struct pcf_dev), GFP_KERNEL);
list_add(&dev->instance, &pcf_device->instance);
/* cdev first step */
cdev_init(&dev->cdev, &pcf8591_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if(err){
pr_err("add cdev fail\n");
goto fail;
}

/* cdev second step */
if(da_en){
dev->device = device_create(pcf_class, NULL, devno, NULL, "pcf_DA");
}
else{
dev->device = device_create(pcf_class, NULL, devno, NULL, "pcf_AD%d", channels);
}
if(IS_ERR(dev->device)){
err = PTR_ERR(dev->device);
pr_err("device create failed at %d", channels);
cdev_del(&dev->cdev);
goto fail;
};
devno ++;

/* prepare data */
dev->channel = channels;
dev->control = (control &~ 0x03)|(channels & 0x03);
dev->client = client;
dev->lock = pcf_device->lock;
if(da_en){
da_en = 0;
dev->channel = 0xff;
}
};

remove中删除字符设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct pcf_dev *pcf_device = (struct pcf_dev*)i2c_get_clientdata(client);
if(pcf_class != NULL){
device_destroy(pcf_class, MKDEV(pcf_major, minor));
}
if(pcf_device!=NULL){
struct list_head prev, end, *cur;
struct pcf_dev *dev;
list_add_tail(&end, &pcf_device->instance); // push end to tail, so end->prev is the end.
prev = *end.prev;
cur = end.prev;
while(cur != &pcf_device->instance){ // all character device instance.
// need to free from back, so can't use kernel's function.
if(cur == NULL) { // dont't know what happend.
break;
}
dev = container_of(cur, struct pcf_dev, instance);
/* cdev second step */
device_destroy(pcf_class, dev->device->devt);
/* cdev first step */
cdev_del(&dev->cdev);
kfree(cur);
cur = prev.prev;
prev = *prev.prev;
};
kfree(pcf_device -> lock);
kfree(pcf_device);
};

编写fail跳转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fail:
/* ofcourse copy from the remove xD */
if(pcf_class != NULL){
device_destroy(pcf_class, MKDEV(pcf_major, minor));
class_destroy(pcf_class);
}
if(pcf_device!=NULL){
struct list_head prev, end, *cur;
list_add_tail(&end, &pcf_device->instance);
prev = *end.prev;
cur = end.prev;
while(cur != &pcf_device->instance){
kfree(cur);
cur = prev.prev;
prev = *prev.prev;
};
kfree(pcf_device);
};
return err;

note: 使用kzalloc可以省一步memset.

open, release

open没什么要干的. 检查一下channel合不合法放完数据就走人.

1
2
3
4
5
6
7
struct pcf_dev *dev = NULL;
dev = container_of(inode->i_cdev, struct pcf_dev, cdev);
if(dev == NULL)return -ENODEV;
if((dev->channel > 3) && (dev->channel != 0xff))
return -ENODEV;
filp->private_data = dev;
return 0;

open都没什么干release更没什么干了.

1
return 0;	

write, read

write本来蛮简单的. 考虑到各种奇怪的用法多了很多检查步骤.
pcf8591的写DA操作要加一个control byte.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct pcf_dev *dev = NULL;
char* dat;
ssize_t err = 0;
struct i2c_msg msg;

dev = filp->private_data;
/* why you guys writing to a AD channel? */
if(dev->channel != 0xff)return -ENODEV;

dat = kmalloc(count+1, GFP_KERNEL);
/* rip. your device's memory is too SMALL to save ONE byte. or you guys ask the driver to write a LOOOOOOOOOOOOG data. */
if(dat == NULL)return -ENOMEM;

dat[0] = dev->control;
/* move the data to kernel space. */
if(copy_from_user(dat+1, buf, count) != 0){
err = -EFAULT;
};

/* prepare to write data. */
msg.addr = dev->client->addr;
msg.flags = 0; // 0 represent write operation
msg.len = count + 1;
msg.buf = dat;
dev->data = msg.buf[count];

mutex_lock(dev->lock);
/* write data to device. */
if(i2c_transfer(dev->client->adapter, &msg, 1) < 0){
err = -EFAULT;
pr_err("pcf: transfer failed.\n");
goto end_write;
}
err = count;

end_write:
mutex_unlock(dev->lock);
kfree(dat);
return err;

read也差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
struct pcf_dev *dev = NULL;
char* dat;
struct i2c_msg msg[2];
ssize_t err;
dev = filp->private_data;

if(dev->channel == 0xff){
/* you said you want to read DA and read MANY bytes? NO SENSE! */
if(count != 1)return -ENODEV;
else copy_to_user(buf, &dev->data, 1);
return 1;
}

/* prepare to read. */
dat = kmalloc(count, GFP_KERNEL);
if(dat == NULL)return -ENOMEM;

/* change to channel */
msg[0].addr = dev->client->addr;
msg[0].flags = 0;
msg[0].buf = &dev->control;
msg[0].len = 1;

/* read data. */
msg[1].addr = dev->client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = dat;
msg[1].len = count;

/* read */
mutex_lock(dev->lock);
if(i2c_transfer(dev->client->adapter, msg, 2) < 0){
err = -EFAULT;
mutex_unlock(dev->lock);
goto end_read;
}
mutex_unlock(dev->lock);

/* copy data to user space */
if(copy_to_user(buf, dat, count) != 0){
err = -EIO;
goto end_read;
};
err = count;

end_read:
kfree(dat);
return err;

note: kmalloc: 反正要复写一遍就不浪费零分配区域了

Debug

奇怪问题. nxp,pcf8591地址0x48nsiway,ns2009触屏芯片地址0x48冲突. 这两个设备还把地址线焊死了改不了!
解决: 修改设备树, 在板级设备树arch/arm/boot/dts/sun8i-v3s-licheepi-zero.dts, 修改alias把serial0 = &uart0改成serial0 = &uart2, 失能uart0, 使能uart2, 添加i2c1的pio, 使能i2c1, 在i2c1节点下添加pcf8591@48节点.
note: 务必对称着写分配和释放, 不然很容易kernel panic. 使用devm分配的内存由炒作系统在dev解除的时候自动释放. 能用devm就用devm.
ps: 参考ns2009的驱动, of_match_table可以不用写. remove可以不用写, 对应到平台有自己的驱动函数可以简化设计. 对应到ADC是工业IO.