Linux驱动开发-①regmap②IIO子系统
Linux驱动开发-IIO驱动
- 一,regmap
- 二,IIO子系统
- 2.1初始化相关工作
- 2.2 通道
- 2.3 读实现
- over
一,regmap
对于spi和i2c,读写寄存器的框架不同,但设备本质一样,因此就有了regmap模型来对其进行简化,提供统一的接口函数来访问寄存器,而且使用很方便,使用时候,调用regmap的读写寄存器函数即可。
整体框架:
struct regmap *regmap;struct regmap_config regmap_config;/*对于spi的设备,之前的读写还需要对t->tx_buf,t->rx_buf等进行操作,再t放到message中,最后再利用spi_sync发送,比较麻烦*/static unsigned char icm20608c_read_one_reg(struct icm20608c_dev *dev,u8 reg){u8 ret;unsigned int data;ret = regmap_read(dev->regmap,reg,&data);//调用regmap的读函数return (u8)data;}void icm20608c_write_one_reg66(struct icm20608c_dev *dev,u8 reg,unsigned char data){regmap_write(dev->regmap,reg,data);//调用regmap的写函数}static int icm20608c_probe(struct spi_device *spi){int ret = 0;icm20608c.regmap_config.reg_bits = 8;//寄存器大小为8biticm20608c.regmap_config.val_bits = 8;//寄存器数据值8bit//icm20608c.regmap_config.read_flag_mask = 0x80;//读掩码icm20608c.regmap = regmap_init_spi(spi,&icm20608c.regmap_config);//关键就是这个 函 数,将regmap_config和spi设备关联起来,创建出regmapif(IS_ERR(icm20608c.regmap)){return PTR_ERR(icm20608c.regmap);}........}static int icm20608c_remove(struct spi_device *spi){.........regmap_exit(icm20608c.regmap);return 0;}
二,IIO子系统
Linux驱动喜欢把程序分层,并且一类的放到一起,IIO子系统就是针对ADC传感器,将比如加速度计,光传感器,气压计,磁力计等等设备,用一样的驱动模型。
结构体iio_dev来表示具体的IIO设备,整体的驱动框架,还是要看设备,比如spi设备,创建的就是spi_driver。主要包含三个内容:①在probe函数中,申请iio_dev内存,进行初始化,注册iio_dev。②iio_chan_spec对通道的建立,比如icm20608设备,有加速度xyz轴,陀螺仪xyz轴和温度,一共七个数据,因此要建立七个通道,在文件中显示,包含:加速度+x或y或z轴+什么数据,比如原始数据,量程等。③iio_info操作函数中,read_raw和write_raw函数具体的实现,具体怎么样实现,还是依靠regmap_read和regmap_write函数,对寄存器进行操作。
2.1初始化相关工作
注意,现在不建立全局变量了,之前会这样写:struct icm20608c_dev icm20608,从而在整个驱动中,直接调用icm20608。比如在remove函数中,想进行注销工作,先indio_dev = spi_get_drvdata(spi);获取indio_dev,再得到dev = iio_priv(indio_dev);,即原来没改过的icm20608地址。
初始化工作中,devm_iio_device_alloc(&spi->dev, sizeof(*dev)) 做了两件事①分配一个struct iio_dev结构的内存,②额外分配sizeof(*dev)大小的内存空间(这里是struct icm20608c_dev的大小。
iio_priv(indio_dev)是一个宏,它返回的是紧跟在iio_dev结构体后面的私有数据区域的指针,dev = iio_priv(indio_dev)获取的是紧跟在iio_dev后面的为驱动私有数据预留的内存区域的指针。
struct icm20608c_dev {struct spi_device *spi;struct regmap *regmap;struct regmap_config regmap_config;struct mutex lock;
};
/*完成设备号注册 节点注册 spi设备初始化*/
static int icm20608c_probe(struct spi_device *spi)
{int ret = 0;struct icm20608c_dev *dev;struct iio_dev *indio_dev;/*1.iio_dev申请内存*/indio_dev = devm_iio_device_alloc(&spi->dev,sizeof(*dev));if(!indio_dev){return ret;}/*2.获取icm20608c_dev结构体地址*/dev = iio_priv(indio_dev);dev->spi = spi;spi_set_drvdata(spi,indio_dev);mutex_init(&dev->lock);/*3.初始化iio_dev成员变量*/indio_dev->dev.parent = &spi->dev;indio_dev->info = &icm20608c_info;indio_dev->name = ICMC20608C_NAME;indio_dev->modes = INDIO_DIRECT_MODE;indio_dev->channels = icm20608_channels;indio_dev->num_channels = ARRAY_SIZE(icm20608_channels);/*4.注册iio_dev*/ret = iio_device_register(indio_dev);if(ret<0){return ret;}/*5.初始化regmap_config并且注册*/dev->regmap_config.reg_bits = 8;//寄存器大小为8bitdev->regmap_config.val_bits = 8;//寄存器数据值8bit//dev.regmap_config.read_flag_mask = 0x80;//读掩码dev->regmap = regmap_init_spi(spi,&dev->regmap_config);if(IS_ERR(dev->regmap)){goto regmap_error;return PTR_ERR(dev->regmap);}/*6.初始化SPI设备*/spi->mode = SPI_MODE_0;//时钟模式spi_setup(spi);/*7.寄存器初始化内容*/icm20608c_reg_init(dev);printk("probe success \r\n");return 0;
regmap_error:iio_device_unregister(indio_dev); return ret;
}
static int icm20608c_remove(struct spi_device *spi)
{/*这段代码,为了获取icm20608c_dev的地址和iio_dev地址,以便进行解除*/struct icm20608c_dev *dev;struct iio_dev *indio_dev;indio_dev = spi_get_drvdata(spi);dev = iio_priv(indio_dev);/*删除regmap,注销iio_dev*/regmap_exit(dev->regmap);iio_device_unregister(indio_dev); printk("remove success \r\n");return 0;
}
2.2 通道
这个 ICM20608_CHAN(_type, _channel2, _index)宏,就相当于给带入里面的数进行赋值,创建出一个通道结构体,_type是表示各种数据,比如加速度IIO_ACCEL,当modified=1,channel2 为通道修饰符,表示对这个_type还有更具体的描述。 info_mask_shared_by_type 为相同type类型通道共有的(但是名字要一样,比如都是加速度类型,in_accel_sacle),info_mask_separate 为这个通道私有的(比如in_accel_x_raw),具体在文件中的顺序,依靠scan_index,为0时候,在最前面。
在文件中显示顺序:
#define ICM20608_CHAN(_type, _channel2, _index) \{ \.type = _type, \.modified = 1, \.channel2 = _channel2, \.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \BIT(IIO_CHAN_INFO_CALIBBIAS), \.scan_index = _index, \.scan_type = { \.sign = 's', \.realbits = 16, \.storagebits = 16, \.shift = 0, \.endianness = IIO_BE, \}, \}
enum inv_icm20608_scan {INV_ICM20608_SCAN_ACCL_X,INV_ICM20608_SCAN_ACCL_Y,INV_ICM20608_SCAN_ACCL_Z,INV_ICM20608_SCAN_TEMP,INV_ICM20608_SCAN_GYRO_X,INV_ICM20608_SCAN_GYRO_Y,INV_ICM20608_SCAN_GYRO_Z,INV_ICM20608_SCAN_TIMESTAMP,
};
static const struct iio_chan_spec icm20608_channels[] = {{.type = IIO_TEMP,.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)|BIT(IIO_CHAN_INFO_OFFSET)|BIT(IIO_CHAN_INFO_SCALE),.scan_index = INV_ICM20608_SCAN_TEMP,.scan_type = {.sign = 's',.realbits = 16,.storagebits = 16,.shift = 0,.endianness = IIO_BE,},},ICM20608_CHAN(IIO_ANGL_VEL,IIO_MOD_X,INV_ICM20608_SCAN_GYRO_X),ICM20608_CHAN(IIO_ANGL_VEL,IIO_MOD_Y,INV_ICM20608_SCAN_GYRO_Y),ICM20608_CHAN(IIO_ANGL_VEL,IIO_MOD_Z,INV_ICM20608_SCAN_GYRO_Z),ICM20608_CHAN(IIO_ACCEL,IIO_MOD_X,INV_ICM20608_SCAN_ACCL_X),ICM20608_CHAN(IIO_ACCEL,IIO_MOD_Y,INV_ICM20608_SCAN_ACCL_Y),ICM20608_CHAN(IIO_ACCEL,IIO_MOD_Z,INV_ICM20608_SCAN_ACCL_Z),
};
2.3 读实现
icm20608c_read_raw的返回值,决定读到的数据值,返回值IIO_VAL_INT为1,读到的数据按照整形返回,只有val,没有val2,返回值IIO_VAL_INT_PLUS_MICRO为2,按照val+val2/1000000反馈, 返回值IIO_VAL_INT_PLUS_NANO为2,按照val+val2/1000000000,返回值为负,读取错误。
对于IIO_CHAN_INFO_SCALE量程数据的读取,读到寄存器值为00 01 10 11,要进行转换成实际值,转换关系有数组gyro_scale_icm20608和accel_scale_icm20608构建,最后进行输出。
最终得到的真实数据,比如重力加速度=in_accel_z_raw(读取的值)×in_accel_scale(加速度量程)。
static const int gyro_scale_icm20608[] = {7629, 15258, 30517, 61035};
static const int accel_scale_icm20608[] = {61035, 122070, 244140, 488281};
static unsigned char icm20608c_read_one_reg(struct icm20608c_dev *dev,u8 reg)
{u8 ret;unsigned int data;ret = regmap_read(dev->regmap,reg,&data);return (u8)data;
}
void icm20608c_write_one_reg66(struct icm20608c_dev *dev,u8 reg,unsigned char data)
{regmap_write(dev->regmap,reg,data);
}
/*计算出要读寄存器的位置,根据带入的axis计算偏移地址+reg*/
static int icm20608_sensor_show(struct icm20608c_dev *dev, int reg,int axis,int *val)
{int ind, result;__be16 d;ind = (axis - IIO_MOD_X) * 2;result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);if (result)return -EINVAL;*val = (short)be16_to_cpup(&d);return IIO_VAL_INT;
}
static int icm20608c_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val, int *val2, long mask)
{ int ret = 0;unsigned char regdata = 0;struct icm20608c_dev *dev = iio_priv(indio_dev);switch(mask){case IIO_CHAN_INFO_RAW://原始值mutex_lock(&dev->lock);switch(chan->type){case IIO_TEMP:ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val);//IIO_MOD_X意思是就从这个ICM20_TEMP_OUT_H开始break;case IIO_ACCEL:ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val);break;case IIO_ANGL_VEL:ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan->channel2, val);break;default:ret = -EINVAL;break;}mutex_unlock(&dev->lock);return ret;case IIO_CHAN_INFO_CALIBBIAS://校准值 switch(chan->type){ //对于加速度和陀螺仪的校准值,首先是校准值寄存器位置不同,其次是分为x,y,zcase IIO_ACCEL:mutex_lock(&dev->lock);ret = icm20608_sensor_show(dev, ICM20_XA_OFFSET_H, chan->channel2, val);mutex_unlock(&dev->lock); break;case IIO_ANGL_VEL:mutex_lock(&dev->lock);ret = icm20608_sensor_show(dev, ICM20_XG_OFFS_USRH, chan->channel2, val);mutex_unlock(&dev->lock); break;default:ret = -EINVAL;break; } break;case IIO_CHAN_INFO_SCALE://度量switch (chan->type) {case IIO_ANGL_VEL:mutex_lock(&dev->lock);regdata = (icm20608c_read_one_reg(dev, ICM20_GYRO_CONFIG) & 0X18) >> 3;*val = 0;*val2 = gyro_scale_icm20608[regdata];mutex_unlock(&dev->lock);return IIO_VAL_INT_PLUS_MICRO; /* 值为val+val2/1000000 */case IIO_ACCEL:mutex_lock(&dev->lock);regdata = (icm20608c_read_one_reg(dev, ICM20_ACCEL_CONFIG) & 0X18) >> 3;*val = 0;*val2 = accel_scale_icm20608[regdata];;mutex_unlock(&dev->lock);return IIO_VAL_INT_PLUS_NANO;/* 值为val+val2/1000000000 */case IIO_TEMP: *val = ICM20608_TEMP_SCALE/ 1000000;*val2 = ICM20608_TEMP_SCALE % 1000000;return IIO_VAL_INT_PLUS_MICRO; /* 值为val+val2/1000000 */default:return -EINVAL;}return ret;case IIO_CHAN_INFO_OFFSET://温度传感器offset值switch(chan->type){case IIO_TEMP:*val = ICM20608_TEMP_OFFSET; break;default:ret = -EINVAL; }return ret; default:break;}printk("icm20608c_read_raw\r\n");return ret;}
实现:
#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/unaligned/be_byteshift.h>
#include "666.h"
#define ICMC20608C_NAME "icm20608c"
#define ICM20608_TEMP_OFFSET 0
#define ICM20608_TEMP_SCALE 326800000struct icm20608c_dev {struct spi_device *spi;struct regmap *regmap;struct regmap_config regmap_config;struct mutex lock;
};
static const int gyro_scale_icm20608[] = {7629, 15258, 30517, 61035};
static const int accel_scale_icm20608[] = {61035, 122070, 244140, 488281};
#define ICM20608_CHAN(_type, _channel2, _index) \{ \.type = _type, \.modified = 1, \.channel2 = _channel2, \.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \BIT(IIO_CHAN_INFO_CALIBBIAS), \.scan_index = _index, \.scan_type = { \.sign = 's', \.realbits = 16, \.storagebits = 16, \.shift = 0, \.endianness = IIO_BE, \}, \}
enum inv_icm20608_scan {INV_ICM20608_SCAN_ACCL_X,INV_ICM20608_SCAN_ACCL_Y,INV_ICM20608_SCAN_ACCL_Z,INV_ICM20608_SCAN_TEMP,INV_ICM20608_SCAN_GYRO_X,INV_ICM20608_SCAN_GYRO_Y,INV_ICM20608_SCAN_GYRO_Z,INV_ICM20608_SCAN_TIMESTAMP,
};
static const struct iio_chan_spec icm20608_channels[] = {{.type = IIO_TEMP,.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)|BIT(IIO_CHAN_INFO_OFFSET)|BIT(IIO_CHAN_INFO_SCALE),.scan_index = INV_ICM20608_SCAN_TEMP,.scan_type = {.sign = 's',.realbits = 16,.storagebits = 16,.shift = 0,.endianness = IIO_BE,},},ICM20608_CHAN(IIO_ANGL_VEL,IIO_MOD_X,INV_ICM20608_SCAN_GYRO_X),ICM20608_CHAN(IIO_ANGL_VEL,IIO_MOD_Y,INV_ICM20608_SCAN_GYRO_Y),ICM20608_CHAN(IIO_ANGL_VEL,IIO_MOD_Z,INV_ICM20608_SCAN_GYRO_Z),ICM20608_CHAN(IIO_ACCEL,IIO_MOD_X,INV_ICM20608_SCAN_ACCL_X),ICM20608_CHAN(IIO_ACCEL,IIO_MOD_Y,INV_ICM20608_SCAN_ACCL_Y),ICM20608_CHAN(IIO_ACCEL,IIO_MOD_Z,INV_ICM20608_SCAN_ACCL_Z),
};static unsigned char icm20608c_read_one_reg(struct icm20608c_dev *dev,u8 reg)
{u8 ret;unsigned int data;ret = regmap_read(dev->regmap,reg,&data);return (u8)data;
}
void icm20608c_write_one_reg66(struct icm20608c_dev *dev,u8 reg,unsigned char data)
{regmap_write(dev->regmap,reg,data);
}static int icm20608_sensor_show(struct icm20608c_dev *dev, int reg,int axis,int *val)
{int ind, result;__be16 d;ind = (axis - IIO_MOD_X) * 2;result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);if (result)return -EINVAL;*val = (short)be16_to_cpup(&d);return IIO_VAL_INT;
}
static int icm20608c_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val, int *val2, long mask)
{ int ret = 0;unsigned char regdata = 0;struct icm20608c_dev *dev = iio_priv(indio_dev);switch(mask){case IIO_CHAN_INFO_RAW://原始值mutex_lock(&dev->lock);switch(chan->type){case IIO_TEMP:ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val);//IIO_MOD_X意思是就从这个ICM20_TEMP_OUT_H开始break;case IIO_ACCEL:ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val);break;case IIO_ANGL_VEL:ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan->channel2, val);break;default:ret = -EINVAL;break;}mutex_unlock(&dev->lock);return ret;case IIO_CHAN_INFO_CALIBBIAS://校准值 switch(chan->type){ //对于加速度和陀螺仪的校准值,首先是校准值寄存器位置不同,其次是分为x,y,zcase IIO_ACCEL:mutex_lock(&dev->lock);ret = icm20608_sensor_show(dev, ICM20_XA_OFFSET_H, chan->channel2, val);mutex_unlock(&dev->lock); break;case IIO_ANGL_VEL:mutex_lock(&dev->lock);ret = icm20608_sensor_show(dev, ICM20_XG_OFFS_USRH, chan->channel2, val);mutex_unlock(&dev->lock); break;default:ret = -EINVAL;break; } break;case IIO_CHAN_INFO_SCALE://度量switch (chan->type) {case IIO_ANGL_VEL:mutex_lock(&dev->lock);regdata = (icm20608c_read_one_reg(dev, ICM20_GYRO_CONFIG) & 0X18) >> 3;*val = 0;*val2 = gyro_scale_icm20608[regdata];mutex_unlock(&dev->lock);return IIO_VAL_INT_PLUS_MICRO; /* 值为val+val2/1000000 */case IIO_ACCEL:mutex_lock(&dev->lock);regdata = (icm20608c_read_one_reg(dev, ICM20_ACCEL_CONFIG) & 0X18) >> 3;*val = 0;*val2 = accel_scale_icm20608[regdata];;mutex_unlock(&dev->lock);return IIO_VAL_INT_PLUS_NANO;/* 值为val+val2/1000000000 */case IIO_TEMP: *val = ICM20608_TEMP_SCALE/ 1000000;*val2 = ICM20608_TEMP_SCALE % 1000000;return IIO_VAL_INT_PLUS_MICRO; /* 值为val+val2/1000000 */default:return -EINVAL;}return ret;case IIO_CHAN_INFO_OFFSET://温度传感器offset值switch(chan->type){case IIO_TEMP:*val = ICM20608_TEMP_OFFSET; break;default:ret = -EINVAL; }return ret; default:break;}printk("icm20608c_read_raw\r\n");return ret;}
static int icm20608c_write_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int val, int val2, long mask)
{int ret = 0;printk("icm20608c_write_raw\r\n");return ret;
}
static int icm20608c_write_raw_get_fmt(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,long mask)
{int ret = 0;return ret;
}/*文件操作函数*/
static struct iio_info icm20608c_info = {.read_raw = icm20608c_read_raw,.write_raw = icm20608c_write_raw,.write_raw_get_fmt = &icm20608c_write_raw_get_fmt,
};/*寄存器初始化*/
static void icm20608c_reg_init( struct icm20608c_dev *dev)
{u8 value = 0,ret = 0;icm20608c_write_one_reg66(dev, ICM20_PWR_MGMT_1,0x80);mdelay(50);icm20608c_write_one_reg66(dev, ICM20_PWR_MGMT_1,0x01);mdelay(50);ret = icm20608c_read_one_reg(dev, ICM20_PWR_MGMT_1);printk("ICM20_PWR_MGMT_1 = %X\r\n", ret);value = icm20608c_read_one_reg(dev, ICM20_WHO_AM_I);printk("ICM20608 ID = %X\r\n", value);icm20608c_write_one_reg66(dev, ICM20_SMPLRT_DIV, 0x00); icm20608c_write_one_reg66(dev, ICM20_GYRO_CONFIG, 0x18); icm20608c_write_one_reg66(dev, ICM20_ACCEL_CONFIG, 0x18); icm20608c_write_one_reg66(dev, ICM20_CONFIG, 0x04); icm20608c_write_one_reg66(dev, ICM20_ACCEL_CONFIG2, 0x04);icm20608c_write_one_reg66(dev, ICM20_PWR_MGMT_2, 0x00); icm20608c_write_one_reg66(dev, ICM20_LP_MODE_CFG, 0x00); icm20608c_write_one_reg66(dev, ICM20_FIFO_EN, 0x00);
};
const struct of_device_id of_icm20608c_table[]={{.compatible = "wyt,icm20608"},{}
};
const struct spi_device_id icm20608c_table[]={{"icm20608c",0},{ }
};
/*完成设备号注册 节点注册 spi设备初始化*/
static int icm20608c_probe(struct spi_device *spi)
{int ret = 0;struct icm20608c_dev *dev;struct iio_dev *indio_dev;/*1.iio_dev申请内存*/indio_dev = devm_iio_device_alloc(&spi->dev,sizeof(*dev));if(!indio_dev){return ret;}/*2.获取icm20608c_dev结构体地址*/dev = iio_priv(indio_dev);dev->spi = spi;spi_set_drvdata(spi,indio_dev);mutex_init(&dev->lock);/*3.初始化iio_dev成员变量*/indio_dev->dev.parent = &spi->dev;indio_dev->info = &icm20608c_info;indio_dev->name = ICMC20608C_NAME;indio_dev->modes = INDIO_DIRECT_MODE;indio_dev->channels = icm20608_channels;indio_dev->num_channels = ARRAY_SIZE(icm20608_channels);/*4.注册iio_dev*/ret = iio_device_register(indio_dev);if(ret<0){return ret;}/*5.初始化regmap_config并且注册*/dev->regmap_config.reg_bits = 8;//寄存器大小为8bitdev->regmap_config.val_bits = 8;//寄存器数据值8bit//dev.regmap_config.read_flag_mask = 0x80;//读掩码dev->regmap = regmap_init_spi(spi,&dev->regmap_config);if(IS_ERR(dev->regmap)){goto regmap_error;return PTR_ERR(dev->regmap);}/*6.初始化SPI设备*/spi->mode = SPI_MODE_0;//时钟模式spi_setup(spi);/*7.寄存器初始化内容*/icm20608c_reg_init(dev);printk("probe success \r\n");return 0;
regmap_error:iio_device_unregister(indio_dev); return ret;
}
static int icm20608c_remove(struct spi_device *spi)
{/*这段代码,为了获取icm20608c_dev的地址和iio_dev地址,以便进行解除*/struct icm20608c_dev *dev;struct iio_dev *indio_dev;indio_dev = spi_get_drvdata(spi);dev = iio_priv(indio_dev);/*删除regmap,注销iio_dev*/regmap_exit(dev->regmap);iio_device_unregister(indio_dev); printk("remove success \r\n");return 0;
}
static struct spi_driver icm20608c_driver={.probe = icm20608c_probe,.remove = icm20608c_remove,.driver = {.name = "icm20608c_spi_driver",.of_match_table = of_icm20608c_table,},.id_table = icm20608c_table,};
static int __init icm20608c_init(void)
{return spi_register_driver(&icm20608c_driver);
}
static void __exit icm20608c_exit(void)
{spi_unregister_driver(&icm20608c_driver);
}
/*驱动入口*/
module_init(icm20608c_init);
module_exit(icm20608c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("WYT");
over
驱动部分总体上都结束了,基本上是字符设备的驱动编写,块设备写一个RAM的,网络设备简单看了看框架,还是要干点项目,后面从100节视频往后,就开始不想学了,后面的章节都是搭建一些环境和软件,也不用动手敲了,这两天把这个会议整完,把操作系统学完。五月开始,找点项目干干,感觉还是没学到什么东西啊!!!!