ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Linux 驱动开发(三)SPI

2021-11-20 21:59:41  阅读:114  来源: 互联网

标签:struct spi icm20608dev dev SPI Linux device 驱动 icm20608


spi 驱动框架和 iic 驱动框架类似,都分为主机控制器驱动和设备驱动。

1 SPI 主机驱动

SOC 的spi外设驱动是半导体产商写好的,SPI 主机驱动器采用了 platfom 驱动框架。我们可以从内核中文件中找到 spi_imx_driver 结构体:

static struct platform_driver spi_imx_driver = {
	.driver = {
		   .name = DRIVER_NAME,
		   .of_match_table = spi_imx_dt_ids,
		   .pm = IMX_SPI_PM,
	},
	.id_table = spi_imx_devtype,
	.probe = spi_imx_probe,
	.remove = spi_imx_remove,
};

当控制器的设备和驱动匹配以后,spi_imx_probe 函数就会执行,spi_imx_probe 函数会从设备树中读取相应的节点属性值,申请并初始化 spi_master,最后调用 spi_bitbang_start 函数(spi_bitbang_start 会调用 spi_register_master 函数)向 Linux 内核注册
spi_master。spi 控制器驱动的核心就是 spi_master 结构体。

struct spi_master {
	struct device	dev;
	struct list_head list;
	s16			bus_num;
	u16			num_chipselect, dma_alignment, mode_bits;
	u32			bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))
	u32			min_speed_hz, max_speed_hz;
	u16			flags;
#define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* can't do full duplex */
#define SPI_MASTER_NO_RX	BIT(1)		/* can't do buffer read */
#define SPI_MASTER_NO_TX	BIT(2)		/* can't do buffer write */
#define SPI_MASTER_MUST_RX      BIT(3)		/* requires rx */
#define SPI_MASTER_MUST_TX      BIT(4)		/* requires tx */
	spinlock_t		bus_lock_spinlock;
	struct mutex	bus_lock_mutex;
	bool			bus_lock_flag;
	int			(*setup)(struct spi_device *spi);
	int			(*transfer)(struct spi_device *spi, struct spi_message *mesg);   
	void		(*cleanup)(struct spi_device *spi);
	bool		(*can_dma)(struct spi_master *master, struct spi_device *spi,struct spi_transfer *xfer);
	bool				queued;
	struct kthread_worker		kworker;
	struct task_struct		*kworker_task;
	struct kthread_work		pump_messages;
	spinlock_t			queue_lock;
	struct list_head		queue;
	struct spi_message		*cur_msg;
	bool	idling,busy,running,rt,auto_runtime_pm, cur_msg_prepared,cur_msg_mapped;
	struct completion   xfer_completion;
	size_t				max_dma_len;
	int (*prepare_transfer_hardware)(struct spi_master *master);
	int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
	int (*unprepare_transfer_hardware)(struct spi_master *master);
	int (*prepare_message)(struct spi_master *master,struct spi_message *message);
	int (*unprepare_message)(struct spi_master *master,struct spi_message *message);
	void (*set_cs)(struct spi_device *spi, bool enable);
	int (*transfer_one)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer);
	void (*handle_err)(struct spi_master *master, struct spi_message *message);
	int			*cs_gpios;
	struct dma_chan		*dma_tx;
	struct dma_chan		*dma_rx;
	void			*dummy_rx;
	void			*dummy_tx;
};

这里的内容有很多,我们重点关注几个函数
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。
int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去。

也就是 SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。
SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。

2 SPI 设备驱动

spi设备驱动的核心内容就是 spi_driver 结构体,它和 i2c_driver、 platform_driver 基本一样,内容包括 probe、remove函数等,当 spi 设备和驱动匹配成功以后 probe 函数就会执行。spi_driver 初始化完成以后需要向 Linux 内核注册, spi_driver 注册函数为
spi_register_driver,注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函数完成 spi_driver 的注销

struct spi_driver {
	const struct spi_device_id *id_table;
	int			(*probe)(struct spi_device *spi);
	int			(*remove)(struct spi_device *spi);
	void		(*shutdown)(struct spi_device *spi);
	struct device_driver	driver;
};

一个基础的 spi_driver 驱动框架

/* probe function */
static int xxx_probe(struct spi_driver *spi)
{
	/* specific content */
	return 0;
}

/* remove function */
static void xxx_remove(struct spi_driver *spi)
{
	/* specific content */
	return 0;
}

/* traditional match table */
static const struct spi_driver_id xxx_id[] = {
	{"xxx",0},
	{}
};

/* device_tree match table */
static const struct spi_driver_id xxx_of_match[] = {
	{.compatible = "xxx"},
	{}
};

/* spi_driver structure */
static struct spi_driver xxx_driver = {
	.probe = xx_probe,
	.remove = xxx_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "xxx",
		.of_match_table = xxx_of_match,
	},
	.id_table = xxx_id,
};

/* module entrance */
static int __init xxx_init(void)
{
	return spi_register_driver(&xxx_driver);
}

/* module exit */
static void __exit xxx_exit(void)
{
	spi_unregister_driver(&xxx_driver);
}

module_init(xx_init);
module_exit(xxx_exit);

3 spi 设备和驱动匹配过程

SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,SPI 总线为 spi_bus_type

struct bus_type spi_bus_type = {
	.name		= "spi",
	.dev_groups	= spi_dev_groups,
	.match		= spi_match_device,
	.uevent		= spi_uevent,
};

可以看出, SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
	const struct spi_device	*spi = to_spi_device(dev);
	const struct spi_driver	*sdrv = to_spi_driver(drv);
	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;
	/* Then try ACPI */
	if (acpi_driver_match_device(dev, drv))
		return 1;
	if (sdrv->id_table)
		return !!spi_match_id(sdrv->id_table, spi);

	return strcmp(spi->modalias, drv->name) == 0;
}

of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配。spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI设备名字和 spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配。

4 spi 数据收发流程

spi 的收发关键是两个结构体,spi_transfer 和 spi_message

struct spi_transfer {
	const void	*tx_buf;
	void		*rx_buf;
	unsigned	len;
	dma_addr_t	tx_dma;
	dma_addr_t	rx_dma;
	struct sg_table tx_sg;
	struct sg_table rx_sg;
	unsigned	cs_change:1;
	unsigned	tx_nbits:3;
	unsigned	rx_nbits:3;
#define	SPI_NBITS_SINGLE	0x01 /* 1bit transfer */
#define	SPI_NBITS_DUAL		0x02 /* 2bits transfer */
#define	SPI_NBITS_QUAD		0x04 /* 4bits transfer */
	u8		bits_per_word;
	u16		delay_usecs;
	u32		speed_hz;
	struct list_head transfer_list;
};

tx_buf 保存着要发送的数据, rx_buf 用于保存接收到的数据, len 是要进行传输的数据长度

truct spi_message {
	struct list_head	transfers;
	struct spi_device	*spi;
	unsigned		is_dma_mapped:1;
	void			(*complete)(void *context);
	void			*context;
	unsigned		frame_length;
	unsigned		actual_length;
	int			status;
	struct list_head	queue;
	void			*state;
};

在使用spi_message之前需要对其进行初始化, spi_message 初始化函数为spi_message_init
spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,要用到 spi_message_add_tail 函数
spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync
异步传输不会阻塞的等到 SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete 成员变量, complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。 SPI 异步传输函数为 ```spi_async``

5 以 icm20608 为例构建一个 Linux 下 SPI 驱动框架

  • 修改设备树
    在 iomuxc 节点中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚,子节点名字为 pinctrl_ecspi3,内容如下:
pinctrl_ecspi3: icm20608grp {
			fsl,pins = <
				MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20		0x10b0	/* CS */
				MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK	0x10b1	/* SCLK */
				MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO		0x10b1	/* MISO */
				MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI		0x10b1	/* MOSI */
			>;
		};

接着在 ecspi3 节点追加 icm20608 子节点

&ecspi3 {
	fsl,spi-num-chipselects = <1>;
	cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; 
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_ecspi3>;
	status = "okay";

	spidev0: icm20608@0 {  /* @0中的0表示icm20608连接在ECSPI3的第0个通道上 */
		compatible = "alientek,icm20608";
		spi-max-frequency = <8000000>; 
		reg = <0>;
	};	
};

接着开始编写驱动程序

  • 创建一个 icm20608 设备结构体
/* icm20608 device struct*/
struct icm20608_dev {
    int major;
    int minor;
    dev_t devid;
    struct cdev cdev;
    struct device *device;
    struct class *class;
    void *private_data;
    struct device_node *nd;
    int cs_gpio;				/* gpio num of cs */
	signed int gyro_x_adc;		/* initial value of gyro_x */
	signed int gyro_y_adc;		/* initial value of gyro_y */
	signed int gyro_z_adc;		/* initial value of gyro_z */
	signed int accel_x_adc;		/* initial value of accel_x */
	signed int accel_y_adc;		/* initial value of accel_y */
	signed int accel_z_adc;		/* initial value of accel_z*/
	signed int temp_adc;		/* initial tempture value */
};
struct icm20608_dev icm20608dev;
  • icm20608 的 spi_driver 注册与注销
/* no device tree match*/
struct spi_device_id icm20608_id [] = {
    {"alientek,icm20608", 0},
    {}
}; 
/*  device-tree match*/
static const struct of_device_id icm20608_of_match [] = {
    {.compatible = "alientek,icm20608"},
    {}
};
/* spi driver*/ 
static struct spi_driver icm20608_driver = {
    .probe = icm20608_probe,
    .remove = icm20608_remove,
    .driver = {
        .name = "icm20608",
        .owner = THIS_MODULE,
        .of_match_table = icm20608_of_match,

    },
    .id_table = icm20608_id,
};
static int __init icm20608_init(void)
{
    return spi_register_driver(&icm20608_driver);
}
static void __exit icm20608_exit(void)
{
    spi_unregister_driver(&icm20608_driver);
}
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jimmy");
  • 由于匹配成功后开始执行 probe 函数。接着编写 probe 和 remove 函数
/* probe function */
static int icm20608_probe(struct spi_device *spi)
{
    int ret =0;
    /* create character device agriculture */
    /* device ID */
    icm20608dev.major = 0;
    if(icm20608dev.major){     /* given major*/
        icm20608dev.devid = MKDEV(icm20608dev.major, 0);
        register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
    } else {                        /* without major*/
        ret = alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
        icm20608dev.major = MAJOR(icm20608dev.devid);
        icm20608dev.minor = MINOR(icm20608dev.devid);
    }
    if(ret < 0){
        printk("icm20608 chrdev_region error!!!\r\n");
        goto fail_devid;
    }
    printk("major=%d, minor=%d\r\n",icm20608dev.major, icm20608dev.minor);
    /* create cdev */
    icm20608dev.cdev.owner = THIS_MODULE;
    cdev_init(&icm20608dev.cdev, &icm20608_fops);
    ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
    if(ret < 0){
        printk("icm20608 cdev_add error!!\r\n");
        goto fail_cdev;
    }
    /* create device node */
    icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
    if(IS_ERR(icm20608dev.class)){
        ret = PTR_ERR(icm20608dev.class);
        printk("icm20608 class_create error!!\r\n");
        goto fail_class;
    }
    icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
    if(IS_ERR(icm20608dev.device)){
        ret = PTR_ERR(icm20608dev.device);
        printk("icm20608 device_create error!!\r\n");
        goto fail_device;
    }
    /* require shipselect pin */
    icm20608dev.nd = of_get_parent(spi->dev.of_node);
    icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs_gpio", 0);
    if(icm20608dev.cs_gpio < 0){
        printk("cannot get cs_gpio!\r\n");
        goto fail_gpio;
    }
    /* request gpio */
    ret = gpio_request(icm20608dev.cs_gpio, "cs");
    if(ret < 0){
        printk("gpio requset fail!\r\n");
        goto fail_gpio;
    }
    /* set the output, high level, invalid */
    ret = gpio_direction_output(icm20608dev.cs_gpio, 1); 
    if(ret < 0){
        printk("unable to set output!\r\n");
        goto fail_set_output;
    }
    /* initialize spi_device */
    spi->mode = SPI_MODE_0; /* MODE0, CPOL=0, CPHA=0 */
    spi_setup(spi);
    /* set private data */
    icm20608dev.private_data = spi;
    /* initialize icm20608 */
    icm20608_initialize(&icm20608dev);

    return 0;

fail_set_output:
    gpio_free(icm20608dev.cs_gpio);
fail_gpio:
    device_destroy(icm20608dev.class, icm20608dev.devid);
fail_device:
    class_destroy(icm20608dev.class);
fail_class:
    cdev_del(&icm20608dev.cdev);
fail_cdev:
    unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
fail_devid:
    return ret;
}

/* remove function */
static int icm20608_remove(struct spi_device *spi)
{
    cdev_del(&icm20608dev.cdev);
    unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
    device_destroy(icm20608dev.class, icm20608dev.devid);
    class_destroy(icm20608dev.class);
    gpio_free(icm20608dev.cs_gpio);
    return 0;
}
  • 字符设备操作集
static int icm20608_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &icm20608dev;
    return 0;
}

static int icm20608_release(struct inode *inode, struct file *filp){
    return 0;
}

static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offset)
{
    signed int data[7];
    long err = 0;
    struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
    icm20608_readdata(dev);
    data[0] = dev->gyro_x_adc;
    data[1] = dev->gyro_y_adc;
    data[2] = dev->gyro_z_adc;
    data[3] = dev->accel_x_adc;
    data[4] = dev->accel_y_adc;
    data[5] = dev->accel_z_adc;
    data[6] = dev->temp_adc;
    err = copy_to_user(buf, data, sizeof(data));
    if(err < 0){
        printk("copy to user error!!\r\n");
        return err;
    }
    return 0;
}

/* device operations struct */
static struct file_operations icm20608_fops = {
    .owner = THIS_MODULE,
    .read = icm20608_read,
    .open = icm20608_open,
    .release = icm20608_release,
};
  • icm20608 初始化以及读写寄存器函数编写
/* icm20608 initial function */
void icm20608_initialize(struct icm20608_dev *dev)
{
    u8 value = 0;

    icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);    /* reset, sleep mode */
    mdelay(50);
    icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);    /* close sleep mode, select clock automatically */ 
    mdelay(50);

    value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);
    printk("ICM20608 ID = %#x\r\n", value);

    value = icm20608_read_onereg(dev, ICM20_PWR_MGMT_1);
    printk("ICM20_PWR_MGMT_1 = %#x\r\n", value);

    icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); 	/* 输出速率是内部采样率	*/
	icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); 	/* 陀螺仪±2000dps量程 */
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 */
	icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz */
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); 	/* 打开加速度计和陀螺仪所有轴 */
	icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); 	/* 关闭低功耗 */
	icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);		/* 关闭FIFO	*/
}
/* read data of icm20608 */
void icm20608_readdata(struct icm20608_dev *dev)
{
    unsigned char data[14];
    icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
    dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
    dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
    dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
    dev->temp_adc    = (signed short)((data[6] << 8) | data[7]); 
	dev->gyro_x_adc  = (signed short)((data[8] << 8) | data[9]); 
	dev->gyro_y_adc  = (signed short)((data[10] << 8) | data[11]);
	dev->gyro_z_adc  = (signed short)((data[12] << 8) | data[13]);
}
/* icm20608 read a register */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
    u8 data = 0;
    icm20608_read_regs(dev, reg, &data, 1);
    return data;
}

/* icm20608 write a register */
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
    u8 buf = value;
    icm20608_write_regs(dev, reg, &buf, 1);
}

/* spi write register function */
static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len)
{
    int ret = 0;
    unsigned char txdata[len];    
    struct spi_message msg;
    struct spi_transfer *t;
    struct spi_device *spi = (struct spi_device *)dev->private_data;
    /* shipselect set low, select icm20608 */
    gpio_set_value(dev->cs_gpio, 0);
    /* create spi_transfer*/
    t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
    /* step 1: send address of register */
    txdata[0] = reg & ~ 0x80;           // when write data, bit 7 need to be cleared
    t->tx_buf = txdata;                 // data needs to be sent
    t->len = 1;                         // len is one byte
    spi_message_init(&msg);             // initialize spi_message
    spi_message_add_tail(t, &msg);      // add spi_trnasfer to spi_message
    ret = spi_sync(spi, &msg);          // set send method as sync

    /* step2: send data needs to be written */
    t->tx_buf = buf;                    // data needs to be written
    t->len = len;
    spi_message_init(&msg);
    spi_message_add_tail(t, &msg);
    ret = spi_sync(spi, &msg);
    kfree(t);
     /* shipselect set high */
    gpio_set_value(icm20608dev.cs_gpio, 1);
    return ret;

}

/* spi read register function */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
    int ret = 0;
    unsigned char txdata[len]; 
    struct spi_message msg;
    struct spi_transfer *t;
    struct spi_device *spi = (struct spi_device *)dev->private_data;
    /* shipselect set low, select icm20608 */
    gpio_set_value(icm20608dev.cs_gpio, 0);
    /* create spi_transfer*/
    t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
    /* step 1: send address of register */
    txdata[0] = reg | 0x80;             // if you use icm20608 by spi, set the highest bit of reg's add
    t->tx_buf = txdata;                 // data needs to be sent
    t->len = 1;                         // len is one byte
    spi_message_init(&msg);             // initialize spi_message
    spi_message_add_tail(t, &msg);      // add spi_trnasfer to spi_message
    ret = spi_sync(spi, &msg);          // set send method as sync
    /* step2: read data */
    txdata[0] = 0xff;                   // random number, no meaning
    t->tx_buf = txdata;
    t->len = len;
    spi_message_init(&msg);
    spi_message_add_tail(t, &msg);
    ret = spi_sync(spi, &msg);    
    kfree(t); 
     /* shipselect set high */
    gpio_set_value(icm20608dev.cs_gpio, 1);
    return ret;
}

我们可以看到读写寄存器的操作是很繁琐的,有没有高级的API函数呢显然是有得,上面的两个函数我们可以使用内核中的其他函数替换。

/* spi read register function using linux kernel function */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
    u8 data = 0;
    struct spi_device *spi = (struct spi_device *)dev->private_data;
    gpio_set_value(dev->cs_gpio, 0);
    data = reg | 0x80;
    spi_write(spi, &data, 1);   /* send the address of reg you want to read */
    spi_read(spi, buf, len);    /* read data */
    gpio_set_value(dev->cs_gpio, 1);
    return data;
}

/* spi write register function using linux kernel function */
static void icm20608_write_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
    u8 data = 0;
    struct spi_device *spi = (struct spi_device *)dev->private_data;
    gpio_set_value(dev->cs_gpio, 0);
    data = reg & ~ 0x80;
    spi_write(spi, &data, 1);   /* send the address of reg you want to write*/
    spi_read(spi, buf, len);    /* data you want to write */
    gpio_set_value(dev->cs_gpio, 1);
}

标签:struct,spi,icm20608dev,dev,SPI,Linux,device,驱动,icm20608
来源: https://blog.csdn.net/qq_38773342/article/details/121446417

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有