Android上层与驱动交互完整篇(一)
驱动篇
以I2C设备驱动为例,创建与上层交互节点,解析数据并与设备进行通讯。
kernel中编程如同站在巨人肩膀上,有时候,我们并不需要理解I2C总线是如何工作的,也照样可以编写I2C的设备驱动。因为大多数平台厂商已经为我们编写好了I2C的板级驱动,配置好了I2C管脚的pinmux,话不多说,直接上I2C收发代码。
一般收发数据如下:
写数据: deviceaddr[0] + dataaddr + data[0]+data[2]+data[…]
读数据: deviceaddr[0] + dataaddr + deviceaddr[1] + data[0]+data[1]+data[…]
deviceaddr在kernel中为7为器件地址,在示波器或者逻辑分析仪中抓取到的数据为八位地址,最后一位0或者1 分别代表写与读。
写函数代码,可用于大多数平台
#if 1
static int i2c_write(struct i2c_adapter *i2c_adap,
unsigned char address,
unsigned int len, unsigned char const *data)
{
struct i2c_msg msgs[1];
int res;
if (!data || !i2c_adap) {
memc_error("%s:line=%d,error\n",__func__,__LINE__);
return -EINVAL;
}
msgs[0].addr = address;
msgs[0].flags = 0;
msgs[0].buf = (unsigned char *)data;
msgs[0].len = len;
res = i2c_transfer(i2c_adap, msgs, 1);
if (res == 1)
return 0;
else if(res == 0)
return -EBUSY;
else
return res;
}
int i2c_tx_data(struct i2c_client *client, char *txData, int length)
{
int ret = 0;
int i = 0;
for (i = 0; i < 3; i++) {
ret = i2c_write(client->adapter, client->addr, length, txData);
if (!ret)
break;
}
return ret;
}
#endif
i2c_msg 这个结构体比较关键,我们将deviceaddr赋予msgs[0].addr,然后将器件地址 + data[]数据合并赋予msgs[0].buf,而msgs[0].flag=0,代表这是写数据。最后通过i2c_transfer函数将这个msgs发到设备。
读数据代码,可用于大多数平台
static int i2c_read(struct i2c_adapter *i2c_adap,
unsigned char address, unsigned char *wdata,
unsigned int wlen, unsigned char *rdata,unsigned int rlen)
{
struct i2c_msg msgs[2];
int res;
if (!rdata || !i2c_adap) {
memc_error("%s:line=%d,error\n",__func__,__LINE__);
return -EINVAL;
}
msgs[0].addr = address;
msgs[0].flags = 0; /* write */
msgs[0].buf = wdata;
msgs[0].len = wlen;
msgs[1].addr = address;
msgs[1].flags = I2C_M_RD;
msgs[1].buf = rdata;
msgs[1].len = rlen;
res = i2c_transfer(i2c_adap, msgs, 2);
if (res == 2)
return 0;
else if(res == 0)
return -EBUSY;
else
return res;
return res;
}
static int i2c_rx_data(struct i2c_client *client, char * txData,int tlen,char *rxData, int rlen)
{
int ret = 0;
int i = 0;
for (i = 0; i < 2; i++) {
ret = i2c_read(client->adapter, client->addr, txData,tlen,rxData,rlen);
if (ret < 0){
memc_error("i2c_rx_data error\n");
}else{
break;
}
}
return ret;
}
上面讲过,读数据,先要写数据,然后再去读数据,所以这里就需要发送两个msgs,第一个msgs与写数据相同,第二个msgs的flag为1,也就是代码中的I2C_M_RD,而读到的数据存在msgs[1].buf中。
收发函数有了,接下我们要创建节点,代码中创建的节点为/sys/class/memc/memc/cmd,我们可以通过这个节点来于设备通信,例如:
写数据:
echo 7 0 0xc3 0x01 0x00 0x00 0x00 0x00 0x00 > cmd ; 7代表写7个数据,0代表没有读的数据。
读数据:echo 0 1 0xc2 0x1c 0x00 0x00 0x00 0x00 0x00 >cmd ;cat cmd ; 0告诉驱动这是读,1 是读一个数据。 然后读到的数据存在cmd buff中,通过cat显示。
创建节点代码
static ssize_t memc_write(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct memc_driver *memc_driver = dev_get_drvdata(dev);
return i2c_write_cmd(memc_driver->client,buf,count);
}
static ssize_t memc_read(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct memc_driver *memc_driver = dev_get_drvdata(dev);
return i2c_read_cmd(memc_driver->client,buf);
}
static DEVICE_ATTR(cmd, 0644,
memc_read, memc_write);
static struct attribute *attrs[] = {
&dev_attr_name.attr,
//&dev_attr_cmd.attr,
NULL, /* need to NULL terminate the list of attributes */
};
static struct attribute_group attr_group = {
.attrs = attrs,
};
memc_class = class_create(THIS_MODULE, DRIVER_NAME);
if (IS_ERR(memc_class)) {
ret= PTR_ERR(memc_class);
memc_error("memc_class: class create failed\n");
goto malloc_error;
}
memc_driver->dev = device_create(memc_class,
&client->dev, 0, NULL, DEVICE_NAME);
if (unlikely(IS_ERR(memc_driver->dev))) {
ret = PTR_ERR(memc_driver->dev);
memc_driver->dev = NULL;
goto destory_class;
}
ret = sysfs_create_group(&memc_driver->dev->kobj, &attr_group);
if(ret){
memc_error("sysfs_create_group in memc driver\n");
goto destory_device;
}
上面的class_create会创建名字为DRIVER_NAME的目录,存在于/sys/class/中,而device_create会创建DEVICE_NAME的目录,存在于/sys/class/DRIVER_NAME中。
最后sysfs_create_group会创建目录下的需要的节点。
echo … > cmd 命令 调用memc_write函数。
cat cmd 命令 调用 上面memc_read()函数。
节点创建完成,收发函数也编写完成,那么数据该如何解析呢,怎么将echo 进来的字符串转成 一个一个字节的数据呢?这个也是关键。
static int bytes_to_string(char *buf, u8 *data, int data_size, int base)
{
int pos = 0, i;
/*if(base == 10){ // denary format
pos += snprintf(buf + pos, PAGE_SIZE - pos,"denary: ");
}else{ //default hex format
pos += snprintf(buf + pos, PAGE_SIZE - pos,"hex: ");
}*/
for(i = 0; i < data_size; i++){
if(base == 10){ // denary format
//KLOGD("data[%d] = %d \n",i,data[i]);
pos += snprintf(buf + pos, PAGE_SIZE - pos,"%d ",data[i]);
}else{ //default hex format
//KLOGD("data[%d] = 0x%02x \n",i,data[i]);
pos += snprintf(buf + pos, PAGE_SIZE - pos,"0x%02x ",data[i]);
}
}
pos += snprintf(buf + pos, PAGE_SIZE - pos,"\n");
//delete end space
//data[pos-1] = '\0';
return pos;
}
static int strings_parse(char *kbuf, char *argv[], const char *cchar)
{
int argn;
for(argn = 0; argn < MAX_ARG_NUM + 1 ; argn++){
argv[argn] = strsep(&kbuf, cchar);
if (argv[argn] == NULL){
break;
}
}
if(argn > MAX_ARG_NUM + 1){
memc_notice("get argn is %d ,but max is %d\n", argn, MAX_ARG_NUM);
return -EINVAL;
}
return argn;
}
static int cmd_parse(struct i2c_client *client, int argn, char *argv[])
{
ssize_t ret;
int num = 0,i;
u8 value;
u8 wbuf[CMD_W_MAX_SIZE] = {0};
u8 rbuf[CMD_R_MAX_SIZE] = {0};
u8 wnum = 0,rnum = 0;
if ((argn < CMD_MIN_SIZE) || (argn > CMD_MAX_SIZE + 3)){
memc_info("input format error, argn = %d \n",argn);
haier_cmd_help();
goto err;
}
ret = kstrtou8(argv[0], 0, &value);// auto data format
if (ret == 0)
wnum = value;
ret = kstrtou8(argv[1], 0, &value);// auto data format
if (ret == 0)
rnum = value;
//memc_info("wnum=%d ,rnum=%d\n",wnum,rnum);
if((!(wnum | rnum))||(wnum > CMD_W_MAX_SIZE)||(rnum > CMD_R_MAX_SIZE)
||((argn-CMD_MIN_SIZE) > CMD_W_MAX_SIZE))
{
memc_error("input arguments error \n");
goto err;
}
if(argn > CMD_MIN_SIZE){// get data
for(i = 0; i < argn-CMD_MIN_SIZE; i++) {
ret = kstrtou8(argv[i+CMD_MIN_SIZE], 0, &value);//auto data format
if (ret == 0){
wbuf[i] = value;
//memc_info("data[%d]=0x%02x\n",i,wbuf[i]);
}
}
num = i;
}
if(wnum > 0){
if(wnum!=num){
memc_error("data not match,wnum %d ,input num %d \n",wnum,num);
goto err;
}else{
if(haier_i2c_tx_data(client,wbuf,wnum)){
memc_error("write failed !!! \n");
goto err;
}else{
//here need store write data to dlp struct member !!!!!!
memc_info("write successful \n");
}
}
}
mdelay(10);
if (rnum > 0){
if((argn > CMD_MIN_SIZE)&&(wnum == 0)){
ret = haier_i2c_rx_data(client,wbuf,num,rbuf,rnum);
}
if(ret < 0){
memc_error("read cmd ailed \n");
goto err;
}else{
for(i=0;i<rnum;i++){
//if(ddata->debug_enable)
g_rbuf[i] = rbuf[i];
g_rnum = rnum;
memc_info("read data[%d]=0x%02x \n",i, rbuf[i]);
}
}
}
err:
return -EINVAL;
}
static int i2c_write_cmd(struct i2c_client *client,const char *buf, size_t size)
{
char *argv[MAX_ARG_NUM];
int argn;
char *kbuf;
if(!client){
memc_error("no driver device \n");
return -EINVAL;;
}
memc_info("wbuf %s \n",buf);
kbuf = kstrdup(buf, GFP_KERNEL);
argn = haier_strings_parse(kbuf,argv," ");
if(argn <= 0){
goto out;
}
if (argn == 1){
if (!strncmp(argv[0], "help",4) || !strncmp(argv[0], "h", 1)){
haier_cmd_help();
goto out;
}
}
cmd_parse(client,argn,argv);
out:
kfree(kbuf);
return size;
}
static int i2c_read_cmd(struct i2c_client *client,char *buf)
{
int pos = 0;
int i = 0;
if (g_rnum == 0){
haier_cmd_help();
pos = snprintf(buf, PAGE_SIZE, "read data error");
}else{
pos = bytes_to_string(buf,g_rbuf,g_rnum,16);
}
//clear g_rnum and clear g_rbuf to 0xff
for(i=0;i<g_rnum;i++){
g_rbuf[i]=0xff;
}
g_rnum = 0;
return pos;
}
上面字符串的处理有点复杂,繁琐,当然有更好的算法。
接下来看平台注册代码,因为是I2C设备,所以注册函数为i2c_add_driver
static int memc_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret = 0;
struct memc_driver *memc_driver = NULL;
static struct class *memc_class;
memc_driver = kzalloc(sizeof(struct memc_driver), GFP_KERNEL);
if (memc_driver == NULL) {
printk("failed to create our memc_driver\n");
return -ENOMEM;
}
memc_driver->client = client;
i2c_set_clientdata(client,memc_driver);
ret = dts_parse(client);
if (ret){
memc_error("dts parse error\n");
goto malloc_error;
}
memc_class = class_create(THIS_MODULE, DRIVER_NAME);
if (IS_ERR(memc_class)) {
ret= PTR_ERR(memc_class);
memc_error("memc_class: class create failed\n");
goto malloc_error;
}
memc_driver->dev = device_create(memc_class,
&client->dev, 0, NULL, DEVICE_NAME);
if (unlikely(IS_ERR(memc_driver->dev))) {
ret = PTR_ERR(memc_driver->dev);
memc_driver->dev = NULL;
goto destory_class;
}
dev_set_drvdata(memc_driver->dev, memc_driver);
ret = sysfs_create_group(&memc_driver->dev->kobj, &attr_group);
if(ret){
memc_error("sysfs_create_group in memc driver\n");
goto destory_device;
}
memc_info("haier_memc_probe\n");
return 0;
destory_device:
device_destroy(memc_class,0);
destory_class:
class_destroy(memc_class);
malloc_error:
kfree(memc_driver);
return -ENODEV;
}
static int memc_remove(struct i2c_client *client)
{
struct memc_driver *memc_driver = i2c_get_clientdata(client);
kfree(memc_driver);
return 0;
}
static const struct of_device_id i2c_memc_of_match[] = {
{ .compatible = "max,memc" },
{ }
};
static const struct i2c_device_id memc_device_id[] = {
{DEVICE_NAME, 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, memc_device_id);
static struct i2c_driver memc_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(i2c_memc_of_match),
},
.probe = memc_probe,
.remove = memc_remove,
.id_table = memc_device_id,
};
static int __init memc_init(void)
{
memc_info("memc_init\n");
i2c_add_driver(&memc_driver);
return 0;
}
static void __exit memc_exit(void)
{
i2c_del_driver(&memc_driver);
}
module_init(memc_init);
module_exit(memc_exit);
MODULE_DESCRIPTION("3251 Memc Driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("shangbaorun@haier.com");
Dts注册就比较简单了,先弄明白平台使用的是那一路I2C,然后在I2
c节点点下添加与compatible 对应的字符串即可。
&i2c1 {
status = "okay";
clock-frequency = <300000>;
pinctrl-names="default";
pinctrl-0=<&i2c1_h_pins>;
memc@1c{
compatible = "max,memc";
reg = <0x1c>;
status = "okay";
};
};
其中0x1c即deviceaddr 的7位地址,i2c1_h_pins即GPIO的pinmux配置,这里已经将其配置作为i2c管脚使用。
全部代码在此链接,有需要的可以下载。
代码链接
共同进步!