linux随笔记 - SPI驱动

spi驱动总线和I2C驱动差不多。只不过对应的API不一样,使用的大致流程都是一样的。

 linux下的spi驱动不支持热插拔(i2c同样不支持,usb、hdmi支持热插拔)

SPI总线一般四根线:

SCK:串行时钟,由SPI总线的主机提供,用于同步通信。

CS:一个spi总线上cs可以有多个,主机通过cs引脚控制不同的从机,cs为低电平的时候表示从机被选中,从而主机和被选中的从机通信。同一时刻一般只有从机的cs引脚被置低。

MOSI:主机输出、从机输入。数据由主机向从机发送。

MISO:主机输入、从机输出。数据由从机向主机发送。

时钟极性和时钟相位:

在SPI中,主机可以选择时钟极性和时钟相位。

时钟极性CPOL(Clock Polarity):是用来配置SCLK的电平处于哪种状态时有效。

CPOL=0:表示高电平有效,低电平处于空闲态。

CPOL=1:表示低电平有效,高电平处于空闲态。

时钟相位CPHA(Clock Phase):是用来配置数据采样是在第几个边沿,0表示第一个边沿(前沿Leading edge),1表示第二个边沿(后沿Trailing edge)。

CPHA=0:表示数据采样是在第1个边沿,数据发送在第2个边沿。

CPHA=1:表示数据采样是在第2个边沿,数据发送在第1个边沿。

 主机需要根据从机的要求选择时钟极性和时钟相位,也即从机的传输模式决定了主机的传输模式。故先要了解从机的SPI是何种模式,然后再将主机的SPI的模式设置成同样的模式,即可正常通讯。根据CPOL和CPHA位的选择,有四种SPI模式可用。

SPI_SOC驱动

同样的,这部分不同的SOC的SPI控制寄存器不一样,驱动实现的方式也不同,这一般是由原厂去实现。如果只是使用SPI驱动其他外设,只需要知道会用几个API即可,此部分一般和SOC使用无关。

//spi_master申请与释放
struct spi_master *spi_alloc_master(struct device *dev,unsigned size);
void spi_master_put(struct spi_master *master);
//spi_master的注册与注销
int spi_register_master(struct spi_master *master);
void spi_unregister_master(struct spi_master *master);

SPI  设备驱动

该部分才是使用SOC的spi驱动的重点。

/* probe 函数 */
static int xxx_probe(struct spi_device *spi)
{
	/* 具体函数内容 */
	return 0;
}
/* remove 函数 */
static int xxx_remove(struct spi_device *spi)
{
	/* 具体函数内容 */
	return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct spi_device_id xxx_id[] = {
	{"xxx", 0},
	{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "xxx" },
	{ /* Sentinel */ }
};
/* SPI 驱动结构体 */
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,
};

/* 驱动入口函数 */
static int __init xxx_init(void)
{
	return spi_register_driver(&xxx_driver);
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
	spi_unregister_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

 I.MX6U 来讲,SPI 主机的最终数据收发函数为 spi_imx_transfer

再偷个懒,复制一段正点原子的代码。可以如果需要自己写spi读写函数时可以仿照写。

/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{

	int ret = -1;
	unsigned char txdata[1];
	unsigned char * rxdata;
	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);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}

	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);	/* 申请内存 */
	if(!rxdata) {
		goto out1;
	}
	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,一共要读取len个字节长度的数据,*/
	txdata[0] = reg | 0x80;		/* 写数据的时候首寄存器地址bit8要置1 */			
	t->tx_buf = txdata;			/* 要发送的数据 */
    t->rx_buf = rxdata;			/* 要读取的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
	if(ret) {
		goto out2;
	}
	
    memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */

out2:
	kfree(rxdata);					/* 释放内存 */
out1:	
	kfree(t);						/* 释放内存 */
	
	return ret;
}

/*
 * @description	: 向icm20608多个寄存器写入数据
 * @param - dev:  icm20608设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
	int ret = -1;
	unsigned char *txdata;
	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);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}
	
	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
	if(!txdata) {
		goto out1;
	}
	
	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,len为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80;	/* 写数据的时候首寄存器地址bit8要清零 */
    memcpy(txdata+1, buf, len);	/* 把len个寄存器拷贝到txdata里,等待发送 */
	t->tx_buf = txdata;			/* 要发送的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
    if(ret) {
        goto out2;
    }
	
out2:
	kfree(txdata);				/* 释放内存 */
out1:
	kfree(t);					/* 释放内存 */
	return ret;
}

另外补充一点,在linux/spi/spi.h文件中,存在封装好的读写函数。如下:

/**
 * spi_write - SPI synchronous write
 * @spi: device to which data will be written
 * @buf: data buffer
 * @len: data buffer size
 * Context: can sleep
 *
 * This writes the buffer and returns zero or a negative error code.
 * Callable only from contexts that can sleep.
 */
static inline int spi_write(struct spi_device *spi, const void *buf, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= buf,
			.len		= len,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spi_sync(spi, &m);
}

/**
 * spi_read - SPI synchronous read
 * @spi: device from which data will be read
 * @buf: data buffer
 * @len: data buffer size
 * Context: can sleep
 *
 * This reads the buffer and returns zero or a negative error code.
 * Callable only from contexts that can sleep.
 */
static inline int spi_read(struct spi_device *spi, void *buf, size_t len)
{
	struct spi_transfer	t = {
			.rx_buf		= buf,
			.len		= len,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spi_sync(spi, &m);
}

/* this copies txbuf and rxbuf data; for small transfers only! */
extern int spi_write_then_read(struct spi_device *spi,
		const void *txbuf, unsigned n_tx,
		void *rxbuf, unsigned n_rx);

另外注意读写还可以使用spi_write_then_read,但是上面的注释写的是用于数据量较少时的传输,这一点我也没有去注意具体多大的数据量会出问题,如果有人知道还往指点一下。

所以最后可以简化读写函数为

目录

SPI_SOC驱动

SPI  设备驱动


/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
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;
 
	data = reg | 0x80;
 
	spi_write_then_read(spi, &data, 1, buf, len);	//读寄存器数据,先写入地址再读
 
	return 0;
}
 
/*
 * @description	: 向icm20608多个寄存器写入数据
 * @param - dev:  icm20608设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 *txdata;
	struct spi_device *spi = (struct spi_device *)dev->private_data;
 
	txdata = kzalloc(len+1, GFP_KERNEL);	//申请内存
	txdata[0] = reg & ~0x80;	//起始地址保存要写的寄存器地址
	memcpy(&txdata[1], buf, len);	//要发送的数据拷贝
 
	spi_write(spi, txdata, len+1);
	kfree(txdata);
	return 0;
}

 另外补充一点。对于imx6ul来说,原厂编写驱动用得是软件片选,也就是将片选引脚配置未gpio,在原厂得驱动里面去控制得。所以要注意设备树里面不能把片选引脚配置未SS0,配置为gpio功能即可。

在读写函数时会拉住片选信号。

[OK]  spi_write_then_read(spi, &data, 1, buf, len);    //读寄存器数据,先写入地址再读

[ERR] 下面这样实现读函数会有问题,因为在write完了之后,片选会释放,在读得时候会出错。

我们应该要做的是,写之前拉住,直到读完之后才释放才正确

//spi_write(spi, .....);

//spi_read(spi,.....);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值