简述spi内核框架

时序

一个总线在物理层面最重要的就是时序,spi四种时序大家知道,但是并不是每个平台的内核版本都是支持的,比如高通某基带芯片的某个基线版本,就只支持了模式0;当然调试的时候都会去检查时序是否正确,还有就是从设备的时序,这个可能涉及到上电时序,比如要去读这个spi语音芯片的id来检查spi通讯是否正常,这个spi设备可能需要正确的上电时序,或者某个时钟,或者某个引脚的使能才能正常工作给反馈;反正调试过程既需要与soc产商沟通,也需要与设备厂商沟通,这个应该是占据嵌入式开发最多时间的事情了,扯了些没用的,看时序吧

 控制器驱动

框架

控制其驱动所作的最重要的一件事就是按时序来办事,当然并不是所有soc厂商都按照这么来办,就不细说了,下面再看一张图来简述spi控制器驱动干了什么

 probe

match到设备树中的spi控制器节点后,就会执行probe函数来进行接下来的初始化工作,主要是对时钟,dma,操作函就行初始化,这个(transfer_one)是我们重点关注的

static int rockchip_spi_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct rockchip_spi *rs;
	struct spi_master *master;
	struct resource *mem;
	u32 rsd_nsecs;

	master = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi));
	if (!master)
		return -ENOMEM;

	platform_set_drvdata(pdev, master);

	rs = spi_master_get_devdata(master);

	/* Get basic io resource and map it */
	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	rs->regs = devm_ioremap_resource(&pdev->dev, mem);
	if (IS_ERR(rs->regs)) {
		ret =  PTR_ERR(rs->regs);
		goto err_ioremap_resource;
	}

	rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk");
	if (IS_ERR(rs->apb_pclk)) {
		dev_err(&pdev->dev, "Failed to get apb_pclk\n");
		ret = PTR_ERR(rs->apb_pclk);
		goto err_ioremap_resource;
	}

	rs->spiclk = devm_clk_get(&pdev->dev, "spiclk");
	if (IS_ERR(rs->spiclk)) {
		dev_err(&pdev->dev, "Failed to get spi_pclk\n");
		ret = PTR_ERR(rs->spiclk);
		goto err_ioremap_resource;
	}

	ret = clk_prepare_enable(rs->apb_pclk);
	if (ret) {
		dev_err(&pdev->dev, "Failed to enable apb_pclk\n");
		goto err_ioremap_resource;
	}

	ret = clk_prepare_enable(rs->spiclk);
	if (ret) {
		dev_err(&pdev->dev, "Failed to enable spi_clk\n");
		goto err_spiclk_enable;
	}

	spi_enable_chip(rs, 0);

	rs->type = SSI_MOTO_SPI;
	rs->master = master;
	rs->dev = &pdev->dev;
	rs->max_freq = clk_get_rate(rs->spiclk);

	if (!of_property_read_u32(pdev->dev.of_node, "rx-sample-delay-ns",
				  &rsd_nsecs))
		rs->rsd_nsecs = rsd_nsecs;

	rs->fifo_len = get_fifo_len(rs);
	if (!rs->fifo_len) {
		dev_err(&pdev->dev, "Failed to get fifo length\n");
		ret = -EINVAL;
		goto err_get_fifo_len;
	}

	spin_lock_init(&rs->lock);

	pm_runtime_set_active(&pdev->dev);
	pm_runtime_enable(&pdev->dev);

	master->auto_runtime_pm = true;
	master->bus_num = pdev->id;
	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP;
	master->num_chipselect = 2;
	master->dev.of_node = pdev->dev.of_node;
	master->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8);

	master->set_cs = rockchip_spi_set_cs;
	master->prepare_message = rockchip_spi_prepare_message;
	master->unprepare_message = rockchip_spi_unprepare_message;
	master->transfer_one = rockchip_spi_transfer_one;
	master->handle_err = rockchip_spi_handle_err;

	rs->dma_tx.ch = dma_request_slave_channel(rs->dev, "tx");
	if (!rs->dma_tx.ch)
		dev_warn(rs->dev, "Failed to request TX DMA channel\n");

	rs->dma_rx.ch = dma_request_slave_channel(rs->dev, "rx");
	if (!rs->dma_rx.ch) {
		if (rs->dma_tx.ch) {
			dma_release_channel(rs->dma_tx.ch);
			rs->dma_tx.ch = NULL;
		}
		dev_warn(rs->dev, "Failed to request RX DMA channel\n");
	}

	if (rs->dma_tx.ch && rs->dma_rx.ch) {
		rs->dma_tx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_TXDR);
		rs->dma_rx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_RXDR);
		rs->dma_tx.direction = DMA_MEM_TO_DEV;
		rs->dma_rx.direction = DMA_DEV_TO_MEM;

		master->can_dma = rockchip_spi_can_dma;
		master->dma_tx = rs->dma_tx.ch;
		master->dma_rx = rs->dma_rx.ch;
	}

	ret = devm_spi_register_master(&pdev->dev, master);
	if (ret) {
		dev_err(&pdev->dev, "Failed to register master\n");
		goto err_register_master;
	}

	return 0;

err_register_master:
	if (rs->dma_tx.ch)
		dma_release_channel(rs->dma_tx.ch);
	if (rs->dma_rx.ch)
		dma_release_channel(rs->dma_rx.ch);
err_get_fifo_len:
	clk_disable_unprepare(rs->spiclk);
err_spiclk_enable:
	clk_disable_unprepare(rs->apb_pclk);
err_ioremap_resource:
	spi_master_put(master);

	return ret;
}

transfer_one

transfer_one对应的rockchip_spi_transfer_one就是传输函数的具体实现,根据xfer->tx_buf和xfer->rx_buf是否填充了数据来判断当前是发送还是接收

static int rockchip_spi_transfer_one(
		struct spi_master *master,
		struct spi_device *spi,
		struct spi_transfer *xfer)
{
	int ret = 1;
	struct rockchip_spi *rs = spi_master_get_devdata(master);

	WARN_ON(readl_relaxed(rs->regs + ROCKCHIP_SPI_SSIENR) &&
		(readl_relaxed(rs->regs + ROCKCHIP_SPI_SR) & SR_BUSY));

	if (!xfer->tx_buf && !xfer->rx_buf) {
		dev_err(rs->dev, "No buffer for transfer\n");
		return -EINVAL;
	}

	rs->speed = xfer->speed_hz;
	rs->bpw = xfer->bits_per_word;
	rs->n_bytes = rs->bpw >> 3;

	rs->tx = xfer->tx_buf;
	rs->tx_end = rs->tx + xfer->len;
	rs->rx = xfer->rx_buf;
	rs->rx_end = rs->rx + xfer->len;
	rs->len = xfer->len;

	rs->tx_sg = xfer->tx_sg;
	rs->rx_sg = xfer->rx_sg;

	if (rs->tx && rs->rx)
		rs->tmode = CR0_XFM_TR;
	else if (rs->tx)
		rs->tmode = CR0_XFM_TO;
	else if (rs->rx)
		rs->tmode = CR0_XFM_RO;

	/* we need prepare dma before spi was enabled */
	if (master->can_dma && master->can_dma(master, spi, xfer))
		rs->use_dma = 1;
	else
		rs->use_dma = 0;

	rockchip_spi_config(rs);

	if (rs->use_dma) {
		if (rs->tmode == CR0_XFM_RO) {
			/* rx: dma must be prepared first */
			rockchip_spi_prepare_dma(rs);
			spi_enable_chip(rs, 1);
		} else {
			/* tx or tr: spi must be enabled first */
			spi_enable_chip(rs, 1);
			rockchip_spi_prepare_dma(rs);
		}
	} else {
		spi_enable_chip(rs, 1);
		ret = rockchip_spi_pio_transfer(rs);
	}

	return ret;
}

spi_register_master

这个就是注册一个上面把从设备树拿出的信息,和从soc厂商自定义的成员函数都赋给了spi_master,这个函数将把spi_master注册到系统中:主要是把这个spi_master设备通过device_add添加到系统中;通过spi_master_initialize_queue(master)来初始化一个线程,来处理message队列,通过of_register_spi_devices(master)将设备树中spi控制器节点下面的子节点生成spi从设备

int spi_register_master(struct spi_master *master)
{
	static atomic_t		dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
	struct device		*dev = master->dev.parent;
	struct boardinfo	*bi;
	int			status = -ENODEV;
	int			dynamic = 0;

	if (!dev)
		return -ENODEV;

	status = of_spi_register_master(master); //主要拿片选信息
	if (status)
		return status;

	/* even if it's just one always-selected device, there must
	 * be at least one chipselect
	 */
	if (master->num_chipselect == 0)
		return -EINVAL;

	if ((master->bus_num < 0) && master->dev.of_node)
		master->bus_num = of_alias_get_id(master->dev.of_node, "spi");

	/* convention:  dynamically assigned bus IDs count down from the max */
	if (master->bus_num < 0) {
		/* FIXME switch to an IDR based scheme, something like
		 * I2C now uses, so we can't run out of "dynamic" IDs
		 */
		master->bus_num = atomic_dec_return(&dyn_bus_id);
		dynamic = 1;
	}

	INIT_LIST_HEAD(&master->queue);
	spin_lock_init(&master->queue_lock);
	spin_lock_init(&master->bus_lock_spinlock);
	mutex_init(&master->bus_lock_mutex);
	master->bus_lock_flag = 0;
	init_completion(&master->xfer_completion);
	if (!master->max_dma_len)
		master->max_dma_len = INT_MAX;

	/* register the device, then userspace will see it.
	 * registration fails if the bus ID is in use.
	 */
	dev_set_name(&master->dev, "spi%u", master->bus_num);
	status = device_add(&master->dev);
	if (status < 0)
		goto done;
	dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
			dynamic ? " (dynamic)" : "");

	/* If we're using a queued driver, start the queue */
	if (master->transfer)
		dev_info(dev, "master is unqueued, this is deprecated\n");
	else {
		status = spi_master_initialize_queue(master); //初始化一个线程,来处理message队列
		if (status) {
			device_del(&master->dev);
			goto done;
		}
	}
	/* add statistics */
	spin_lock_init(&master->statistics.lock);

	mutex_lock(&board_lock);
	list_add_tail(&master->list, &spi_master_list);
	list_for_each_entry(bi, &board_list, list)
		spi_match_master_to_boardinfo(master, &bi->board_info);
	mutex_unlock(&board_lock);

	/* Register devices from the device tree and ACPI */
	of_register_spi_devices(master); //生成spi从设备
	acpi_register_spi_devices(master);
done:
	return status;
}

 spi_master_initialize_queue

spi_master_initialize_queue主要是spi_init_queue建立起队列来处理message,和spi_start_queue来调用queue_kthread_work(&master->kworker, &master->pump_messages)执行一次工作的调度

static int spi_master_initialize_queue(struct spi_master *master)
{
	int ret;

	master->transfer = spi_queued_transfer;
	if (!master->transfer_one_message)
		master->transfer_one_message = spi_transfer_one_message;

	/* Initialize and start queue */
	ret = spi_init_queue(master); //队列的初始化
	if (ret) {
		dev_err(&master->dev, "problem initializing queue\n");
		goto err_init_queue;
	}
	master->queued = true;
	ret = spi_start_queue(master); //队列开始工作
	if (ret) {
		dev_err(&master->dev, "problem starting queue\n");
		goto err_start_queue;
	}

	return 0;

err_start_queue:
	spi_destroy_queue(master);
err_init_queue:
	return ret;
}

队列的初始化工作如下,主要是创建一个内核线程来处理每次加进来的message队列;spi_sync会调用queue_kthread_work(&master->kworker, &master->pump_messages)来执行一次数据接发

static int spi_init_queue(struct spi_master *master)
{
	struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };

	master->running = false;
	master->busy = false;

	init_kthread_worker(&master->kworker);
	master->kworker_task = kthread_run(kthread_worker_fn,//这个线程不加work进去,就啥也不会干,直接睡眠
					   &master->kworker, "%s",
					   dev_name(&master->dev));
	if (IS_ERR(master->kworker_task)) {
		dev_err(&master->dev, "failed to create message pump task\n");
		return PTR_ERR(master->kworker_task);
	}
	init_kthread_work(&master->pump_messages, spi_pump_messages);//初始化一个work,及其work的工作函数

	/*
	 * Master config will indicate if this controller should run the
	 * message pump with high (realtime) priority to reduce the transfer
	 * latency on the bus by minimising the delay between a transfer
	 * request and the scheduling of the message pump thread. Without this
	 * setting the message pump thread will remain at default priority.
	 */
	if (master->rt) {
		dev_info(&master->dev,
			"will run message pump with realtime priority\n");
		sched_setscheduler(master->kworker_task, SCHED_FIFO, &param);//设置进程调度为先进先出的实时进程类
	}

	return 0;
}

设备驱动

spidev

内核加载spidev.c是一个通用的spi驱动;可以通过这种形式加载insmod spidev.ko busnum=0 chipselect=1 maxspeed=50000000,通过register_chrdev注册一个spi字符设备

static int __init spidev_init(void)
{
    int status;

    /* Claim our 256 reserved device numbers.  Then register a class
     * that will key udev/mdev to add/remove /dev nodes.  Last, register
     * the driver which manages those device numbers.
     */
    BUILD_BUG_ON(N_SPI_MINORS > 256);
    status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
    if (status < 0)
        return status;

    spidev_class = class_create(THIS_MODULE, "spidev");
    if (IS_ERR(spidev_class)) {
        status = PTR_ERR(spidev_class);
        goto error_class;
    }

    status = spi_register_driver(&spidev_spi_driver);
    if (status < 0)
        goto error_register;

    if (busnum != -1 && chipselect != -1) {
        struct spi_board_info chip = {
                    .modalias    = "spidev",
                    .mode        = spimode,
                    .bus_num    = busnum,
                    .chip_select    = chipselect,
                    .max_speed_hz    = maxspeed,
        };

        struct spi_master *master;

        master = spi_busnum_to_master(busnum);
        if (!master) {
            status = -ENODEV;
            goto error_busnum;
        }

        /* We create a virtual device that will sit on the bus */
        spi = spi_new_device(master, &chip);
        if (!spi) {
            status = -EBUSY;
            goto error_mem;
        }
        dev_dbg(&spi->dev, "busnum=%d cs=%d bufsiz=%d maxspeed=%d",
            busnum, chipselect, bufsiz, maxspeed);
    }
    return 0;
error_mem:
error_busnum:
    spi_unregister_driver(&spidev_spi_driver);
error_register:
    class_destroy(spidev_class);
error_class:
    unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
    return status;
}
module_init(spidev_init);

spidev_probe

spidev_spi_driver的probe成员spidev_probe通过device_create在/dev目录下生成一个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.
	 */
	if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) {
		dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n");
		WARN_ON(spi->dev.of_node &&
			!of_match_device(spidev_dt_ids, &spi->dev));
	}

	/* Allocate driver data */
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
	if (!spidev)
		return -ENOMEM;

	/* Initialize the driver data */
	spidev->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 = 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);
	}
	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_write/read

用的时候填充tx_buf/rx_buf到spi_transfer,放入message队列中,并通过spidev_sync调用spi_sync去接发数据

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

transfer_one

跟最上面的transfer_one完美对上,从机驱动跟主机驱动通过spi核心(driverr/spi/spi.c)中的spi_sync建立起完美的合作关系;纵观其他总线,也都是类似从机-核心-主机这样的框架来工作的

spi_sync
    __spi_queued_transfer
        queue_kthread_work(&master->kworker, &master->pump_messages)
            spi_pump_messages
                 __spi_pump_messages
                    master->transfer_one_message(master, master->cur_msg)
                        spi_transfer_one_message
                            master->transfer_one(master, msg->spi, xfer)

GPIO虚拟SPI

gpio模拟spi主机,设备树示例如下;Makefile使能spi-gpio.c/spi-bitbang.c就行了,就会自己生成一个spi_master;直接insmod spidev.ko busnum=0 chipselect=0就会自己生成一个spidev0.0设备,供应用使用;有人说有实际的spi控制器为什么要用这个?这个有两种效果,物理控制器工作异常需要这个来验证,还有就是确实从设备太多了

spi_0: spi@0 {
                compatible = "spi-gpio";
                #address-cells = <0x1>;
                ranges;
                gpio-sck = <&tlmm_pinmux 3 0x1>;
                gpio-miso = <&tlmm_pinmux 1 0x1>;
                gpio-mosi = <&tlmm_pinmux 0 0x1>;
                cs-gpios = <&tlmm_pinmux 2 0x1>;
                num-chipselects = <1>;
                /* clients */
        };

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值