Linux driver development | SPI driver

SPI drive

SPI is a very common serial communication interface, which is used to connect various peripherals, sensors and other devices Linux bare metal development | SPI experiment SPI interface has been introduced in detail in this paper. This paper mainly introduces the SPI driver framework under Linux, and writes SPI device drivers according to the framework

1, SPI drive frame

SPI driver framework is very similar to I2C. It is divided into host controller driver and device driver. Host controller is the SPI controller interface of SOC. Like I2C adapter driver, SPI host driver is generally written by SOC manufacturer

1.1 SPI host driver

SPI host driver is the SPI controller driver of SOC, and the kernel uses spi_master means SPI host driver, spi_master is a structure defined in include / Linux / SPI / SPI H file

struct spi_master { 
	struct device dev; 
	struct list_head list; 
	...... 
	s16 bus_num; 
	u16 num_chipselect; 
	u16 dma_alignment; 
	u16 mode_bits; 
	u32 bits_per_word_mask; 
	...... 
	u32 min_speed_hz; 
	u32 max_speed_hz; 
	u16 flags; 
	......
	spinlock_t bus_lock_spinlock; 
	struct mutex bus_lock_mutex; 
	bool bus_lock_flag; 
	...... 
	int (*setup)(struct spi_device *spi); 
	...... 
	/* Controller data transfer function */
	int (*transfer)(struct spi_device *spi, struct spi_message *mesg); 
	...... 
	/* It is used for SPI data transmission, and the transmitted data will be packaged into spi_message, which is sent in a queue */
	int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg); 
	...... 
};

The core of SPI host driver is to apply for spi_master, and then initialize spi_master, and finally register SPI with the kernel_ master

  • spi_master application and release
/************************* spi_master Apply for***************************/
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
//dev: device, usually platform_ dev member variable in device
//Size: private data size, which can be through SPI_ master_ get_ The devdata function gets the data
//Return value: applied spi_master
/************************* spi_master Release***************************/
void spi_master_put(struct spi_master *master)
//Master: SPI to release_ master
  • spi_ Registration and cancellation of Master: SPI_ After the master is initialized, it needs to be registered with the kernel
/************************* spi_master Register***************************/
int spi_register_master(struct spi_master *master)
//Master: SPI to register_ master
//Return value: 0, successful; Negative value, failed
/************************* spi_master Write off***************************/
void spi_unregister_master(struct spi_master *master)
//Master: SPI to log off_ master
1.2 SPI device drive

The Linux kernel uses spi_driver structure to represent SPI device driver. When writing SPI device driver, we need to implement spi_driver. spi_ The driver structure is defined in include / Linux / SPI / SPI H file

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_ After the driver initialization is completed, it needs to register with the Linux kernel

  • spi_driver registration function
int spi_register_driver(struct spi_driver *sdrv)
//sdrv: SPI to register_ driver
//Return value: 0, registration succeeded; Assignment, registration failed
  • spi_driver logoff function
void spi_unregister_driver(struct spi_driver *sdrv)
//sdrv: SPI to log off_ driver

spi_ The driver registration example program is as follows:

/* probe function */
static int xxx_probe(struct spi_device *spi){
	/* Specific function content */
	return 0;
}
/* remove function */
static int xxx_remove(struct spi_device *spi){
	/* Specific function content */
	return 0;
}
/* Traditional matching method ID list */
static const struct spi_device_id xxx_id[] = {
	{"xxx", 0},
	{}
};
/* Device tree matching list */
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "xxx" },
	{ /* Sentinel */ }
};
/* SPI Drive structure */
static struct spi_driver xxx_driver = {
	.probe = xxx_probe,
	.remove = xxx_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "xxx",
		.of_match_table = xxx_of_match,
	},
	.id_table = xxx_id,
};
/* Drive entry function */
static int __init xxx_init(void){
	return spi_register_driver(&xxx_driver);
}
/* Drive exit function */
static void __exit xxx_exit(void){
	spi_unregister_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);
1.3 SPI equipment and drive matching process

The matching process between SPI device and driver is completed by SPI bus, which is spi_bus_type, defined in drivers / SPI / SPI C in the document

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

It can be seen from the above that the matching function between SPI device and driver is spi_match_device, whose function contents are as follows:

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);

	/* Used to complete device tree device and driver matching */
	if (of_driver_match_device(dev, drv))
		return 1;
	/* For ACPI form matching */
	if (acpi_driver_match_device(dev, drv))
		return 1;
	/* For traditional device tree free device and driver matching */
	if (sdrv->id_table)
		return !!spi_match_id(sdrv->id_table, spi);
	/* Compare whether the modalias member variable and the name member variable are equal */
	return strcmp(spi->modalias, drv->name) == 0;
}
1.4 SPI equipment data transceiver processing

When the SPI is successfully registered with the Linux kernel_ After the driver, you can use the API functions provided by the SPI core layer to read and write to the device. The first is spi_transfer structure, which is used to describe SPI transmission information. The structure is as follows:

struct spi_transfer {
	const void *tx_buf;	//Save the data to be sent
	void *rx_buf;		//Used to save received data
	unsigned len;		//Length of data to be transferred
	
	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;
};

spi_transfer needs to be organized into spi_message, spi_message is also a structure, as follows:

struct spi_message {
	struct list_head transfers;	
	struct spi_device *spi;	
	unsigned is_dma_mapped:1;
	......
	/* completion is reported through a callback */
	void (*complete)(void *context);
	void *context;
	unsigned frame_length;
	unsigned actual_length;
	int status;	

	struct list_head queue;
	void *state;
};

Using spi_message needs to be initialized before it

void spi_message_init(struct spi_message *m)
//m: SPI to initialize_ message

spi_ After message initialization, SPI needs to be initialized_ Add transfer to spi_message queue

void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
//t: SPI to add to queue_ transfer
//m: spi_transfer the SPI to be added_ message

spi_ After message is ready, data transmission can be carried out. Data transmission is divided into synchronous transmission and asynchronous transmission

/***Synchronous transmission will be blocked and wait for SPI data transmission to complete***/
int spi_sync(struct spi_device *spi, struct spi_message *message)
//spi: spi for data transmission_ device
//Message: SPI to transfer_ message
/***Asynchronous transmission will not block waiting, so SPI needs to be set_ In message
complete Callback function, which will be called after the asynchronous transmission is completed***/
int spi_async(struct spi_device *spi, struct spi_message *message)
//spi: spi for data transmission_ device
//Message: SPI to transfer_ message

To sum up, the SPI data transmission example code is as follows:

/********** SPI Multibyte transmission**********/
static int spi_send(struct spi_device *spi, u8 *buf, int len){
	int ret;
	struct spi_message m;
	struct spi_transfer t = {	  //1. Define an spi_transfer structure variable and set member variable
		.tx_buf = buf,
		.len = len,
	};
	spi_message_init(&m); 		  //2. Initialize spi_message
	spi_message_add_tail(t, &m);  //3. SPI_ Add transfer to spi_message queue
	ret = spi_sync(spi, &m); 	  //4. Synchronous transmission
	return ret;
}
/********** SPI Multibyte reception**********/
static int spi_receive(struct spi_device *spi, u8 *buf, int len){
	int ret;
	struct spi_message m;
	struct spi_transfer t = {	  //1. Define an spi_transfer structure variable and set member variable
		.rx_buf = buf,
		.len = len,
	};
	spi_message_init(&m); 		  //2. Initialize spi_message
	spi_message_add_tail(t, &m);  //3. SPI_ Add transfer to spi_message queue
	ret = spi_sync(spi, &m);	  //4. Synchronous transmission
	return ret;
}

2, SPI drive experiment

This chapter introduces how to drive the six axis sensor of the SPI interface ICM-20608 on the I.MX6U-ALPHA development board, and read the original sensor data of ICM-20608 in the application program. For the introduction of ICM-20608 six axis sensor, please refer to Linux bare metal development | SPI experiment One article

2.1 modifying the equipment tree
  • Modify or add the pinctrl node: add a child node in the iomuxc node to describe the SPI pin used by ICM20608. The child node name is pinctrl_ecspi3
pinctrl_ecspi3: icm20608 { 
	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 */ 
	>; 
};
//UART2_TX_DATA is a chip selection signal. Because you want to control the chip selection signal yourself, it is multiplexed into ordinary GPIO
  • Add child node: add icm20608 child node under ecspi3 node
&ecspi3 { 
	fsl,spi-num-chipselects = <1>; 	//Set the current number of film selections to 1
	/* If the "CS gpios" attribute is used, the SPI host driver will control the chip selection pin */
	cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; 	/* cant't use cs-gpios! */ 
	pinctrl-names = "default"; 
	pinctrl-0 = <&pinctrl_ecspi3>; 
	status = "okay"; 
 
	spidev: icm20608@0 { 
		compatible = "alientek,icm20608"; 
		spi-max-frequency = <8000000>; 	//Set the SPI maximum clock frequency to 8MHz
		reg = <0>; 
	}; 
};
  • Check whether the pins conflict: check whether the pins set in pinctrl and specified in the device node are used by other peripherals

After saving the modifications, compile the device tree with the "make dtbs" command in the kernel home directory, and start the Linux system with the new device tree file

2.2 driver programming
  • New icm20608reg H register header file
#ifndef ICM20608_H
#define ICM20608_H

#define ICM20608G_ID 			 0XAF 	/*  ID value*/
#define ICM20608D_ID 			 0XAE 	/*  ID value*/
/* Gyroscope and acceleration self-test (set during production, used to compare with the user's self-test output value) */
#define	ICM20_SELF_TEST_X_GYRO		0x00
#define	ICM20_SELF_TEST_Y_GYRO		0x01
#define	ICM20_SELF_TEST_Z_GYRO		0x02
#define	ICM20_SELF_TEST_X_ACCEL		0x0D
#define	ICM20_SELF_TEST_Y_ACCEL		0x0E
#define	ICM20_SELF_TEST_Z_ACCEL		0x0F
/* Gyro static offset */
#define	ICM20_XG_OFFS_USRH			0x13
#define	ICM20_XG_OFFS_USRL			0x14
#define	ICM20_YG_OFFS_USRH			0x15
#define	ICM20_YG_OFFS_USRL			0x16
#define	ICM20_ZG_OFFS_USRH			0x17
#define	ICM20_ZG_OFFS_USRL			0x18
#define	ICM20_SMPLRT_DIV			0x19
#define	ICM20_CONFIG				0x1A
#define	ICM20_GYRO_CONFIG			0x1B
#define	ICM20_ACCEL_CONFIG			0x1C
#define	ICM20_ACCEL_CONFIG2			0x1D
#define	ICM20_LP_MODE_CFG			0x1E
#define	ICM20_ACCEL_WOM_THR			0x1F
#define	ICM20_FIFO_EN				0x23
#define	ICM20_FSYNC_INT				0x36
#define	ICM20_INT_PIN_CFG			0x37
#define	ICM20_INT_ENABLE			0x38
#define	ICM20_INT_STATUS			0x3A
/* Acceleration output */
#define	ICM20_ACCEL_XOUT_H			0x3B
#define	ICM20_ACCEL_XOUT_L			0x3C
#define	ICM20_ACCEL_YOUT_H			0x3D
#define	ICM20_ACCEL_YOUT_L			0x3E
#define	ICM20_ACCEL_ZOUT_H			0x3F
#define	ICM20_ACCEL_ZOUT_L			0x40
/* Temperature output */
#define	ICM20_TEMP_OUT_H			0x41
#define	ICM20_TEMP_OUT_L			0x42
/* Gyroscope output */
#define	ICM20_GYRO_XOUT_H			0x43
#define	ICM20_GYRO_XOUT_L			0x44
#define	ICM20_GYRO_YOUT_H			0x45
#define	ICM20_GYRO_YOUT_L			0x46
#define	ICM20_GYRO_ZOUT_H			0x47
#define	ICM20_GYRO_ZOUT_L			0x48
#define	ICM20_SIGNAL_PATH_RESET		0x68
#define	ICM20_ACCEL_INTEL_CTRL 		0x69
#define	ICM20_USER_CTRL				0x6A
#define	ICM20_PWR_MGMT_1			0x6B
#define	ICM20_PWR_MGMT_2			0x6C
#define	ICM20_FIFO_COUNTH			0x72
#define	ICM20_FIFO_COUNTL			0x73
#define	ICM20_FIFO_R_W				0x74
#define	ICM20_WHO_AM_I 				0x75

/* Acceleration static offset */
#define	ICM20_XA_OFFSET_H			0x77
#define	ICM20_XA_OFFSET_L			0x78
#define	ICM20_YA_OFFSET_H			0x7A
#define	ICM20_YA_OFFSET_L			0x7B
#define	ICM20_ZA_OFFSET_H			0x7D
#define	ICM20_ZA_OFFSET_L 			0x7E

#endif
  • New icm20608 C driver file
#define ICM20608_CNT	1
#define ICM20608_NAME	"icm20608"

struct icm20608_dev {
	dev_t devid;				/* Equipment number */
	struct cdev cdev;			/* cdev */
	struct class *class;		/* class */
	struct device *device;		/* equipment */
	struct device_node	*nd; 	/* Device node */
	int major;					/* Main equipment No */
	void *private_data;			/* Private data */
	int cs_gpio;				/* GPIO number used for chip selection */
	signed int gyro_x_adc;		/* Gyro X-axis original value */
	signed int gyro_y_adc;		/* Gyro Y-axis original value */
	signed int gyro_z_adc;		/* Gyro Z-axis original value */
	signed int accel_x_adc;		/* Original value of accelerometer X axis */
	signed int accel_y_adc;		/* Accelerometer Y-axis original value */
	signed int accel_z_adc;		/* Original value of accelerometer Z axis */
	signed int temp_adc;		/* Original temperature value */
};
static struct icm20608_dev icm20608dev;
/* Reading multiple register data from icm20608 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len){
	int ret;
	unsigned char txdata[len];
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->private_data;

	gpio_set_value(dev->cs_gpio, 0);				/* Lower the film selection and select ICM20608 */
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* Request memory */
	/* For the first time, send the deposit address to be read */
	txdata[0] = reg | 0x80;		/* When writing data, the register address bit8 should be set to 1 */
	t->tx_buf = txdata;			/* Data to send */
	t->len = 1;					/* 1 Bytes */
	spi_message_init(&m);		/* Initialize spi_message */
	spi_message_add_tail(t, &m);/* SPI_ Add transfer to spi_message queue */
	ret = spi_sync(spi, &m);	/* Synchronous transmission */
	/* The second time, read the data */
	txdata[0] = 0xff;			/* Any value is meaningless here */
	t->rx_buf = buf;			/* Read data */
	t->len = len;				/* Length of data to read */
	spi_message_init(&m);		/* Initialize spi_message */
	spi_message_add_tail(t, &m);/* SPI_ Add transfer to spi_message queue */
	ret = spi_sync(spi, &m);	/* Synchronous transmission */

	kfree(t);									/* Free memory */
	gpio_set_value(dev->cs_gpio, 1);			/* The chip selection is pulled up and ICM20608 is released */

	return ret;
}
/* Write data to icm20608 multiple registers */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len){
	int ret;
	unsigned char txdata[len];
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->private_data;

	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* Request memory */
	gpio_set_value(dev->cs_gpio, 0);			/* Film selection pull down */
	/* For the first time, send the deposit address to be read */
	txdata[0] = reg & ~0x80;	/* When writing data, the register address bit8 should be cleared */
	t->tx_buf = txdata;			/* Data to send */
	t->len = 1;					/* 1 Bytes */
	spi_message_init(&m);		/* Initialize spi_message */
	spi_message_add_tail(t, &m);/* SPI_ Add transfer to spi_message queue */
	ret = spi_sync(spi, &m);	/* Synchronous transmission */
	/* The second time, send the data to be written */
	t->tx_buf = buf;			/* Data to write */
	t->len = len;				/* Bytes written */
	spi_message_init(&m);		/* Initialize spi_message */
	spi_message_add_tail(t, &m);/* SPI_ Add transfer to spi_message queue */
	ret = spi_sync(spi, &m);	/* Synchronous transmission */

	kfree(t);					/* Free memory */
	gpio_set_value(dev->cs_gpio, 1);/* The chip selection is pulled up and ICM20608 is released */
	return ret;
}
/* Read the specified register value of icm20608 and 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;
}
/* Writes the specified value to the icm20608 specified register */	
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value){
	u8 buf = value;
	icm20608_write_regs(dev, reg, &buf, 1);
}
/* Read the data of ICM20608 and read the original data */
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]);
}
/* open device */
static int icm20608_open(struct inode *inode, struct file *filp){
	filp->private_data = &icm20608dev; /* Set private data */
	return 0;
}
/* Read data from device */
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off){
	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));
	return 0;
}
/* Turn off / release the device */
static int icm20608_release(struct inode *inode, struct file *filp){
	return 0;
}
/* icm20608 Operation function */
static const struct file_operations icm20608_ops = {
	.owner = THIS_MODULE,
	.open = icm20608_open,
	.read = icm20608_read,
	.release = icm20608_release,
};
/* ICM20608 Internal register initialization function */
void icm20608_reginit(void){
	u8 value = 0;
	
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
	mdelay(50);
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
	mdelay(50);

	value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
	printk("ICM20608 ID = %#X\r\n", value);	

	icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); 	/* The output rate is the internal sampling rate					*/
	icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); 	/* Gyroscope ± 2000dps range 				*/
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); 	/* Accelerometer ± 16G range 					*/
	icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); 		/* Gyro low pass filter BW=20Hz 				*/
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* Accelerometer low pass filter BW=21.2Hz 			*/
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); 	/* Turn on all axes of accelerometer and gyroscope 				*/
	icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); 	/* Turn off low power consumption 						*/
	icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);		/* Close FIFO						*/
}
/* spi The probe function of the driver. This function will be executed after the driver matches the device */	
static int icm20608_probe(struct spi_device *spi){
	int ret = 0;

	/* 1,Build equipment number */
	if (icm20608dev.major) {
		icm20608dev.devid = MKDEV(icm20608dev.major, 0);
		register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
	} else {
		alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
		icm20608dev.major = MAJOR(icm20608dev.devid);
	}
	/* 2,Register device */
	cdev_init(&icm20608dev.cdev, &icm20608_ops);
	cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
	/* 3,Create class */
	icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
	if (IS_ERR(icm20608dev.class)) {
		return PTR_ERR(icm20608dev.class);
	}
	/* 4,Create device */
	icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
	if (IS_ERR(icm20608dev.device)) {
		return PTR_ERR(icm20608dev.device);
	}

	/* Obtain the cs chip selection signal in the device tree */
	icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
	if(icm20608dev.nd == NULL) {
		printk("ecspi3 node not find!\r\n");
		return -EINVAL;
	} 

	/* 2, Get the gpio attribute in the device tree and get the BEEP number used by BEEP */
	icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
	if(icm20608dev.cs_gpio < 0) {
		printk("can't get cs-gpio");
		return -EINVAL;
	}

	/* 3,Set GPIO1_IO20 is an output and outputs a high level */
	ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	/*Initialize spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	icm20608dev.private_data = spi; /* Set private data */

	/* Initialize ICM20608 internal register */
	icm20608_reginit();		
	return 0;
}
/* spi The remove function of the spi driver. This function will be executed when the spi driver is removed */
static int icm20608_remove(struct spi_device *spi){
	/* Delete device */
	cdev_del(&icm20608dev.cdev);
	unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);

	/* Unregister classes and devices */
	device_destroy(icm20608dev.class, icm20608dev.devid);
	class_destroy(icm20608dev.class);
	return 0;
}
/* Traditional matching method ID list */
static const struct spi_device_id icm20608_id[] = {
	{"alientek,icm20608", 0},  
	{}
};
/* Device tree matching list */
static const struct of_device_id icm20608_of_match[] = {
	{ .compatible = "alientek,icm20608" },
	{ /* Sentinel */ }
};
/* SPI Drive structure */	
static struct spi_driver icm20608_driver = {
	.probe = icm20608_probe,
	.remove = icm20608_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "icm20608",
		   	.of_match_table = icm20608_of_match, 
		   },
	.id_table = icm20608_id,
};		   
/* Drive entry function */
static int __init icm20608_init(void){
	return spi_register_driver(&icm20608_driver);
}
/* Drive exit function */
static void __exit icm20608_exit(void){
	spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
2.3 test program preparation

Create a new icm20608app C) test documents

int main(int argc, char *argv[])
{
	int fd;
	char *filename;
	signed int databuf[7];
	unsigned char data[14];
	signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
	signed int accel_x_adc, accel_y_adc, accel_z_adc;
	signed int temp_adc;

	float gyro_x_act, gyro_y_act, gyro_z_act;
	float accel_x_act, accel_y_act, accel_z_act;
	float temp_act;

	int ret = 0;

	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if(fd < 0) {
		printf("can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
		ret = read(fd, databuf, sizeof(databuf));
		if(ret == 0) { 			/* Data read successfully */
			gyro_x_adc = databuf[0];
			gyro_y_adc = databuf[1];
			gyro_z_adc = databuf[2];
			accel_x_adc = databuf[3];
			accel_y_adc = databuf[4];
			accel_z_adc = databuf[5];
			temp_adc = databuf[6];

			/* Calculate actual value */
			gyro_x_act = (float)(gyro_x_adc)  / 16.4;
			gyro_y_act = (float)(gyro_y_adc)  / 16.4;
			gyro_z_act = (float)(gyro_z_adc)  / 16.4;
			accel_x_act = (float)(accel_x_adc) / 2048;
			accel_y_act = (float)(accel_y_adc) / 2048;
			accel_z_act = (float)(accel_z_adc) / 2048;
			temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;


			printf("\r\n Original value:\r\n");
			printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
			printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
			printf("temp = %d\r\n", temp_adc);
			printf("actual value:");
			printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
			printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
			printf("act temp = %.2f°C\r\n", temp_act);
		}
		usleep(100000); /*100ms */
	}
	close(fd);	/* Close file */	
	return 0;
}
2.4 operation test
  • Modify Makefile compilation target variable
obj-m := icm20608.o
  • Use "make -j32" to compile the driver module file
make -j32

icm20608App. Floating point calculation is used in C, and I.MX6U supports hardware floating point. Therefore, the following parameters can be added during compilation to enable hardware floating point
-march=armv7-a -mfpu=neon -mfloat=hard

  • Compile the test APP with the command "arm linux gnueabihf GCC - march-armv7-a - mfpu neon - mfload = hard"
arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat=hard icm20608App.c -o  icm20608App
  • Copy the driver file and APP executable file to "rootfs/lib/modules/4.1.15"

  • Use the "modprobe" command to load the driver. After loading successfully, the bus will be matched

depmod  #When loading the driver for the first time, use the "depmod" command
modprobe icm20608.ko
  • After loading successfully, use the following command to test. APP continuously reads data from icm20608 and outputs it to the terminal
./icm20608App  /dev/icm20608

Keywords: Linux SPI

Added by perrohunter on Thu, 20 Jan 2022 02:33:13 +0200