Linux驱动学习笔记:spi设备驱动

目录

一、spi协议概述

二、spi驱动框架

三、spi设备树

四、分析spidev.c


一、spi协议概述

       1、硬件原理:

        SPI接口共有4根信号线,分别是:设备选择线(CS)、时钟线(SCLK)、串行输出数据线(MOSI)、串行输入数据线(MISO);spi控制器可以同时连接多个spi设备,但同一时刻只能有一个 SPI 设备处于工作状态,即多个 CS 信号中某时间只能有一个有效。

2、通讯方式

        通过 CS 片选信号使能外部 SPI 设备后,SCLK 同步数据传输。MOSI 和 MISO 信号在 SCLK 上升沿变化,在下降沿锁存数据。

 

3.工作模式

        spi有四种工作模式,具体由CPOL,CPHA决定;

        CPOL:时钟极性,SCLK信号初始电平;CPHA:数据是在第一个上升沿采集数据还是第二个上升沿采集数据;

CPOLSCLK模式含义
001初始电平为低电平,在第一个时钟沿采样数据
012初始电平为低电平,在第二个时钟沿采样数据
103初始电平为高电平,在第一个时钟沿采样数据
114初始电平为高电平,在第二个时钟沿采样数据

        常用的是模式 0 和模式 3,因为它们都是 在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据 就行。

二、spi驱动框架

         spi驱动框架大致分为spi核心层,spi控制器驱动和spi设备驱动。

        spi核心层向控制器驱动层提供注册SPI控制器驱动的接口,并提供一些需要控制器驱动实现的回调函数。核心层向上,对SPI设备驱动,提供标准的SPI收发API,以及设备注册函数。

        spi控制器驱动会根据设备树里面描述的spi控制器的硬件信息注册一个spi_master,还会解析设备树中该spi控制器的子节点,创建spi_device结构体。驱动实现在Linux/drivers/spi/spi.c。

        spi设备驱动框架主要是注册具体设备,实现硬件相关的读写接口,参考内核代码Linuc/drivers/spi/spidev.c 。

三、spi设备树

        在设备树里,使用一个节点来表示SPI Master,使用子节点来表示挂在下面的SPI设备。设备树节点参考如下代码:

 spi {
        /*spi控制器*/
        #address-cells = <1>;
        #size-cells = <0>;
        compatible = "fsl,spi";
        reg = <0xf00 0x20>;
        interrupts = <2 13 0 2 14 0>;
        interrupt-parent = <&mpc5200_pic>;

        /*spi设备节点1*/
        ethernet-switch@0 {
            compatible = "micrel,ks8995m";
            spi-max-frequency = <1000000>;
            reg = <0>;
        };

      /*spi设备节点2*/
        codec@1 {
            compatible = "ti,tlv320aic26";
            spi-max-frequency = <100000>;
            reg = <1>;
        };
    };

        对于spi master,必须的属性如下:

  • #address-cells:这个spi master的SPI设备,需要多少个cell来表述它的片选引脚。
  • #size-cells:必须设置为0。
  • compatible:根据它找到spi master驱动。

        可选的属性如下:

  • cs-gpios:spi master可以使用多个GPIO当做片选,可以在这个属性列出那些GPIO。

        对于spi设备,必选的属性如下:

  • compatible:根据它找到SPI Device驱动。
  • reg:用来表示它使用哪个片选引脚。
  • spi-max-frequency:必选,该SPI设备支持的最大SPI时钟。

        可选的属性如下:

  • spi-cpol:这是一个空属性(没有值),表示CPOL为1,即平时SPI时钟为低电平。
  • spi-cpha:这是一个空属性(没有值),表示CPHA为1),即在时钟的第2个边沿采样数据。

   四、分析spidev.c

        先从入口函数看起,在入口函数中主要注册了一个字符设备spidev_fops,和spidev_spi_driver;

         spidev.c是一个通用的spi设备驱动程序,设备树某个spi节点下的子设备的compatible属性为下边spidev_dt_ids的某个,就会跟spidev匹配,既然是通用的spi驱动,那么肯定不止支持下面的几个,根据匹配关系发现,只要是设备树的compatible属性为"spidev",就能跟spidev匹配成功。

         匹配之后,spidev.c的spidev_probe会被调用,在probe里面首先分配一个spidev_data结构体,用来记录对应的spi_device, spidev_data会被记录在一个链表里;分配一个次设备号,以后可以根据这个次设备号在链表里找到spidev_data;生成一个设备节点 /dev/spidevB.D ,B表示总线号,D表示它是这个SPI Master下第几个设备,以后,我们就可以通过/dev/spidevB.D来访问spidev驱动程序。

static int spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;
	int			status;
	unsigned long		minor;

	/*
	 * spidev should never be referenced in DT without a specific
	 * compatible string, it is a Linux implementation thing
	 * rather than a description of the hardware.
	 */
	WARN(spi->dev.of_node &&
	     of_device_is_compatible(spi->dev.of_node, "spidev"),
	     "%pOF: buggy DT: spidev listed directly in DT\n", spi->dev.of_node);

	spidev_probe_acpi(spi);

	/* Allocate driver data */
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);/*给spidev申请私有内存空间*/
	if (!spidev)
		return -ENOMEM;

	/* Initialize the driver data */
	spidev->spi = spi;  /*记录spi*/
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);

	INIT_LIST_HEAD(&spidev->device_entry);

	/* If we can allocate a minor number, hook up this device.
	 * Reusing minors is fine so long as udev or mdev is working.
	 */
	mutex_lock(&device_list_lock);
	minor = find_first_zero_bit(minors, N_SPI_MINORS);
	if (minor < N_SPI_MINORS) {
		struct device *dev;

		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);

        /*生成一个设备节点 /dev/spidevB.D */
		dev = device_create(spidev_class, &spi->dev, spidev->devt,
				    spidev, "spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);
		status = PTR_ERR_OR_ZERO(dev);
	} else {
		dev_dbg(&spi->dev, "no minor number available!\n");
		status = -ENODEV;
	}
	if (status == 0) {
		set_bit(minor, minors);
		list_add(&spidev->device_entry, &device_list);/*把spidev_data放入链表*/
	}
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz;/*记录时钟频率*/

	if (status == 0)
		spi_set_drvdata(spi, spidev);
	else
		kfree(spidev);

	return status;
}

        然后看看spidev_fops,它提供了open,read,write,ioctl等读写API。

  spidev_read函数 

/* Read-only message with current device setup */
static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;

	/* chipselect only toggles at start or end of operation */
	if (count > bufsiz)
		return -EMSGSIZE;

	spidev = filp->private_data;

	mutex_lock(&spidev->buf_lock);
	status = spidev_sync_read(spidev, count);
	if (status > 0) {
		unsigned long	missing;

		missing = copy_to_user(buf, spidev->rx_buffer, status);
		if (missing == status)
			status = -EFAULT;
		else
			status = status - missing;
	}
	mutex_unlock(&spidev->buf_lock);

	return status;
}

static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
	struct spi_transfer	t = {
			.rx_buf		= spidev->rx_buffer,
			.len		= len,
			.speed_hz	= spidev->speed_hz,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spidev_sync(spidev, &m);
}

/*-------------------------------------------------------------------------*/
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
	int status;
	struct spi_device *spi;

	spin_lock_irq(&spidev->spi_lock);
	spi = spidev->spi;
	spin_unlock_irq(&spidev->spi_lock);

	if (spi == NULL)
		status = -ESHUTDOWN;
	else
		status = spi_sync(spi, message);

	if (status == 0)
		status = message->actual_length;

	return status;
}


        在spidev_read函数中,调用 spidev_sync_read,在spidev_sync_read中定义spi_transfer和spi_message,然后通过spidev_sync发送,spidev_sync最终会调用内核提供的spi_sync来实现读数据,得到数据后使用copy_to_user把内核空间的数据发给应用程序;

  spidev_write函数 

static ssize_t
spidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;
	unsigned long		missing;

	if (count > bufsiz)
		return -EMSGSIZE;
	spidev = filp->private_data;
	
	mutex_lock(&spidev->buf_lock);
	missing = copy_from_user(spidev->tx_buffer, buf, count);		
	if (missing == 0)
		status = spidev_sync_write(spidev, count);					
		status = -EFAULT;
	mutex_unlock(&spidev->buf_lock);

	return status;
}

static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= spidev->tx_buffer,
			.len		= len,
			.speed_hz	= spidev->speed_hz,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spidev_sync(spidev, &m);									
}

static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
	int status;
	struct spi_device *spi;
	spin_lock_irq(&spidev->spi_lock);
	spi = spidev->spi;
	spin_unlock_irq(&spidev->spi_lock);
	if (spi == NULL)
		status = -ESHUTDOWN;
	else
		status = spi_sync(spi, message);							
	if (status == 0)
		status = message->actual_length;
	return status;
}

        在spi_write函数中,首先要把写的数据拷贝到内核空间,然后把数据传到spi_sync_write函数,在spi_sync_write定义spi_transfer和spi_message,然后通过spidev_sync发送,spidev_sync最终会调用内核提供的spi_sync来实现写数据。

        spi_write和spi_read只能读、写,这是半双工方式,要实现全双工模式的话要使用spi_ioctl。

spi_ioctl函数

spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int			retval = 0;
	struct spidev_data	*spidev;
	struct spi_device	*spi;
	u32			tmp;
	unsigned		n_ioc;
	struct spi_ioc_transfer	*ioc;

	/* Check type and command number */
	if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
		return -ENOTTY;

	/* guard against device removal before, or while,
	 * we issue this ioctl.
	 */
	spidev = filp->private_data;
	spin_lock_irq(&spidev->spi_lock);
	spi = spi_dev_get(spidev->spi);
	spin_unlock_irq(&spidev->spi_lock);

	if (spi == NULL)
		return -ESHUTDOWN;

	/* use the buffer lock here for triple duty:
	 *  - prevent I/O (from us) so calling spi_setup() is safe;
	 *  - prevent concurrent SPI_IOC_WR_* from morphing
	 *    data fields while SPI_IOC_RD_* reads them;
	 *  - SPI_IOC_MESSAGE needs the buffer locked "normally".
	 */
	mutex_lock(&spidev->buf_lock);

	switch (cmd) {
	/* read requests */
	case SPI_IOC_RD_MODE:
		retval = put_user(spi->mode & SPI_MODE_MASK,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MODE32:
		retval = put_user(spi->mode & SPI_MODE_MASK,
					(__u32 __user *)arg);
		break;
	case SPI_IOC_RD_LSB_FIRST:
		retval = put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_BITS_PER_WORD:
		retval = put_user(spi->bits_per_word, (__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MAX_SPEED_HZ:
		retval = put_user(spidev->speed_hz, (__u32 __user *)arg);
		break;

	/* write requests */
	case SPI_IOC_WR_MODE:
	case SPI_IOC_WR_MODE32:
		if (cmd == SPI_IOC_WR_MODE)
			retval = get_user(tmp, (u8 __user *)arg);
		else
			retval = get_user(tmp, (u32 __user *)arg);
		if (retval == 0) {
			struct spi_controller *ctlr = spi->controller;
			u32	save = spi->mode;

			if (tmp & ~SPI_MODE_MASK) {
				retval = -EINVAL;
				break;
			}

			if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&
			    ctlr->cs_gpiods[spi->chip_select])
				tmp |= SPI_CS_HIGH;

			tmp |= spi->mode & ~SPI_MODE_MASK;
			spi->mode = (u16)tmp;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->mode = save;
			else
				dev_dbg(&spi->dev, "spi mode %x\n", tmp);
		}
		break;
	case SPI_IOC_WR_LSB_FIRST:
		retval = get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u32	save = spi->mode;

			if (tmp)
				spi->mode |= SPI_LSB_FIRST;
			else
				spi->mode &= ~SPI_LSB_FIRST;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->mode = save;
			else
				dev_dbg(&spi->dev, "%csb first\n",
						tmp ? 'l' : 'm');
		}
		break;
	case SPI_IOC_WR_BITS_PER_WORD:
		retval = get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u8	save = spi->bits_per_word;

			spi->bits_per_word = tmp;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->bits_per_word = save;
			else
				dev_dbg(&spi->dev, "%d bits per word\n", tmp);
		}
		break;
	case SPI_IOC_WR_MAX_SPEED_HZ:
		retval = get_user(tmp, (__u32 __user *)arg);
		if (retval == 0) {
			u32	save = spi->max_speed_hz;

			spi->max_speed_hz = tmp;
			retval = spi_setup(spi);
			if (retval >= 0)
				spidev->speed_hz = tmp;
			else
				dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
			spi->max_speed_hz = save;
		}
		break;

	default:
		/* segmented and/or full-duplex I/O request */
		/* Check message and copy into scratch area */
		ioc = spidev_get_ioc_message(cmd,
				(struct spi_ioc_transfer __user *)arg, &n_ioc);
		if (IS_ERR(ioc)) {
			retval = PTR_ERR(ioc);
			break;
		}
		if (!ioc)
			break;	/* n_ioc is also 0 */

		/* translate to spi_message, execute */
		retval = spidev_message(spidev, ioc, n_ioc);
		kfree(ioc);
		break;
	}

	mutex_unlock(&spidev->buf_lock);
	spi_dev_put(spi);
	return retval;
}

        

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值