- datesheet中支持spi的任何芯片手册都有应该有传输方式解释
- sclk 时钟线 sdi sdo 分别表示 slave device input / slave device output cs片选 表格很清楚不多说了;硬件连线很简单就四个;
- SPI的dts配置:
&spi0 {
is-decoded-cs = <0>;
num-cs = <1>;
status = "okay";
device@0{
compatible = "adrv9009_1";
//compatible = "xlnx";
reg = <0x0>;
spi-max-frequency = <0x100000>;
reset-gpio = <&gpio 28 GPIO_ACTIVE_HIGH>;
};
};
&spi1 {
is-decoded-cs = <0>;
num-cs = <3>;
status = "okay";
device@0{
compatible = "adrv9009_2";
//compatible = "xlnx";
reg = <0x0>;
spi-max-frequency = <0x100000>;
reset-gpio = <&gpio 29 GPIO_ACTIVE_HIGH>;
};
spidev2: spi@2 {
compatible = "rohm,dh2228fv";
spi-max-frequency = <0x100000>;
reg = <2>;
};
};
驱动代码:
static const struct spi_device_id adrv9009_id[] = {
{"adrv9009_1", ID_ADRV9009_1},
{"adrv9009_2", ID_ADRV9009_2},
{}
};
MODULE_DEVICE_TABLE(spi, adrv9009_id);
static struct spi_driver adrv9009_driver = {
.driver = {
.name = "adi",
.owner = THIS_MODULE,
},
.probe = adrv9009_probe,
.remove = adrv9009_remove,
.id_table = adrv9009_id, //设备驱动匹配条件
};
module_spi_driver(adrv9009_driver);
module_spi_driver(adrv9009_driver); 这个宏展开
#define module_spi_driver(__spi_driver) \
module_driver(__spi_driver, spi_register_driver, spi_unregister_driver)
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
最终得到的就是下面两个注册函数而已: "##"表示字符串连接
static int __init __spi_driver_init(void) {
return spi_register_driver(&(__spi_driver) , ##__VA_ARGS__);
}
module_init(__spi_driver_init);
static void __exit __spi_driver_exit(void) {
spi_unregister_driver(&(__spi_driver) , ##__VA_ARGS__);
}
module_exit(__spi_driver_exit);
字符设备驱动的一般流程: 给结构体分配空间,注册结构体,实现结构体中相应函数指针的函数体;
spi也是一样 静态定义spi结构体:
static struct spi_driver adrv9009_driver ;
只有一个地方需要关注:.id_table = adrv9009_id, 定义了匹配条件使用设备树的话这个应该和设备树中定义一致,不然和设备树匹配不成功;如何匹配的,这个看driver_register 里面有调用match匹配,可以发现使用的就是id_table;
可以看到我们设备树中定位为: compatible = "adrv9009_1";
匹配成功后和平台设备一样进入probe函数,也就是我们定义的adrv9009_probe
可以想象一下probe中应该要做哪些事情?
1、肯定会有读取设备树的代码,获取设备树中定义的最大传输频率;
2、肯定需要读写接口,那么就需要字符设备或者杂项设备的注册还有相应的读写函数完成;
看下我们实现: 为了减小篇幅,把返回值判断都删除了。
static const struct file_operations adrv9009_fops = {
.owner = THIS_MODULE,
.open = adrv9009_open,
.release = adrv9009_release,
.unlocked_ioctl = adrv9009_unlocked_ioctl,
};
struct adrv9009_rf {
struct spi_device *spi; //存放匹配的spi_device 也就是probe中传入的参数
struct device dev;
const struct firmware *fw; //firmware 加载RF相关配置包忽略
const struct firmware *stream;
taliseDevice_t talDevice; //9379 adc芯片配置很多其中的配置项
taliseInit_t talInit;
int16_t txFirCoefs[20];
int16_t rxFirCoefs[72];
int16_t obsrxFirCoefs[24];
struct miscdevice miscdev; //注册杂项设备使用
unsigned char initflag;
};
static int adrv9009_probe(struct spi_device *spi)
{
int iRet;
struct adrv9009_rf *pstPrvData;
int id = spi_get_device_id(spi)->driver_data;
pstPrvData = kzalloc(sizeof(struct adrv9009_rf), GFP_KERNEL);
if (!pstPrvData) {
iRet = -ENOMEM;
return iRet;
}
pstPrvData->spi = spi;
pstPrvData->initflag = 0;
dev_set_drvdata(&pstPrvData->dev, pstPrvData);
iRet = request_firmware(&pstPrvData->fw, "TaliseTDDArmFirmware.bin", &spi->dev);
iRet = request_firmware(&pstPrvData->stream, "TaliseStream.bin", &spi->dev);
pstPrvData->miscdev.minor = MISC_DYNAMIC_MINOR;
pstPrvData->miscdev.fops = &adrv9009_fops;
pstPrvData->miscdev.parent = &spi->dev;
if (id == ID_ADRV9009_1) {
stUser_radio1.spi = pstPrvData->spi; //保存匹配的spi_device
stUser_radio1.logLevel = ADIHAL_LOG_ERR | ADIHAL_LOG_WARN;
stUser_radio1.reset_gpio = devm_gpiod_get(&pstPrvData->spi->dev, "reset", GPIOD_OUT_LOW);
pstPrvData->miscdev.name = CHIP_ONE_NAME;
iRet = misc_register(&pstPrvData->miscdev); //注册杂项设备;
if (iRet < 0) {
return iRet;
}
} else if (id == ID_ADRV9009_2) {
stUser_radio2.spi = pstPrvData->spi;
stUser_radio2.logLevel = ADIHAL_LOG_ERR | ADIHAL_LOG_WARN;
stUser_radio2.reset_gpio = devm_gpiod_get(&pstPrvData->spi->dev, "reset", GPIOD_OUT_LOW);
pstPrvData->miscdev.name = CHIP_TWO_NAME;
iRet = misc_register(&pstPrvData->miscdev);
}
return iRet;
}
这里需要关注的是:
问题1、prboe做了哪些事情需要搞清楚
问题2、怎么使用spi总线读写的
问题3、初始化哪些必要的东西
答1:
步骤a 、如果有多个设备 (在id_table中定义了),同时支持多个设备,却又不同的引脚和频率等首先读取id,用这个区分不同设备的不同初始化 int id = spi_get_device_id(spi)->driver_data;
步骤b、 定义字符设备结构体,注册字符/杂项设备,因为字符设备比较简单,而且满足读写控制要求file_ops,我们在字符/杂项设备的读写控制接口中调用总线的读写操作 spi_write_then_read(devHalData->spi, txbuf, 3, NULL, 0);
步骤a 实质:遍历 spi_driver 链表取出每个节点的id_table 和 probe中传入的spi_device的名字匹配
struct spi_device_id {
char name[SPI_NAME_SIZE];
kernel_ulong_t driver_data; /* Data private to the driver */
};
static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
const struct spi_device *sdev)
{
while (id->name[0]) {
if (!strcmp(sdev->modalias, id->name))
return id;
id++;
}
return NULL;
}
const struct spi_device_id *spi_get_device_id(const struct spi_device *sdev)
{
const struct spi_driver *sdrv = to_spi_driver(sdev->dev.driver);
return spi_match_id(sdrv->id_table, sdev);
}
看下字符设备驱动实现: 我们通过 adrv9009_unlocked_ioctl 中CMD的不同提供不同的操作;
static int adrv9009_open(struct inode *inode, struct file *file){
return 0;
}
static int adrv9009_release(struct inode *inode, struct file *file){
return 0;
}
static long adrv9009_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
int iRet = 0;
struct adrv9009_rf *pstPrvData = file_adrv9009_rf(file);
struct spi_device *spi = ((struct adrv9009_hal *)(pstPrvData->talDevice.devHalInfo))->spi;
switch (cmd) {
case TALISE_PARAM_INIT:
adrv9009_paramget(pstPrvData, arg);
pstPrvData->initflag = 1;
break;
case TALISE_SETARMGPIOPINS:
copy_from_user(&armGpio, (taliseArmGpioConfig_t *)arg, sizeof(taliseArmGpioConfig_t));
iRet = TALISE_setArmGpioPins(&pstPrvData->talDevice, &armGpio);
break;
default:
mutex_lock(&adrv9009_mutex);
iRet = adrv9009_ioctl(pstPrvData, cmd, arg);
mutex_unlock(&adrv9009_mutex);
break;
}
return iRet;
}
总结一下吧:
SPI驱动实际上就是字符设备驱动,利用总线收发不用字节根据时许图写收发接口就是使用spi框架的目的;
如何使用:
1、创建spi_driver 结构体;
2、调用spi_register_driver(&spi_driver);注册我们定义的驱动到总线,所谓注册到总线就是插入到了spi_driver 的链表里;
3、之后就是初始化spi 总线所需的参数 (无论从设备树获取还是平台代码中定义);
4、注册字符设备/杂项设备,应用层通过ioctl控制设备读写等操作;实际就在杂项设备中的fops实现;
5、硬件的操作,根据芯片手册看下设备id 读取成功就通了;
调试方式:
示波器,首先抓取时钟线,看下和芯片手册定义的是否一致,发送16位地址是否是16个钟;
传输最大频率是否超过芯片手册的最大要求;
数据抓取;复位引脚等;