时序
一个总线在物理层面最重要的就是时序,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, ¶m);//设置进程调度为先进先出的实时进程类
}
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 */
};