星星博客 »  > 

基于dts的SPI框架 ADC9009驱动

  1. datesheet中支持spi的任何芯片手册都有应该有传输方式解释
  • sclk 时钟线  sdi sdo 分别表示 slave device input / slave device output  cs片选 表格很清楚不多说了;硬件连线很简单就四个;
  1. 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个钟;

传输最大频率是否超过芯片手册的最大要求;

数据抓取;复位引脚等;

 

 

 

 

 

 

 

 

 

 

相关文章