前几天学习了Linux的I2C驱动子系统,工作需要,今天再看下spi子系统,学习这些子系统,最重要的是一定要掌握到两个思想:
1.面相对象的思想,即驱动和设备的分离,便于驱动的移植,通过设备的硬件信息来实例化驱动。
2.软件开发的分层思想,比如I2C子系统将I2C适配器的功能与I2C具体设备的驱动分成两个层次来实现。这样的好处不言而喻,非常便于驱动的移植。降低了I2C设备开发人员的工作量。
当然,内核的代码还有需要非常好的思想,暂时没有深入的体会到。。。
回到正题,spi子系统其实和I2C子系统一毛一样的套路,大致了解了I2C之后,基本上SPI的看下代码实现也就明白了。关于I2C的子系统,可以看下宋宝华的Linux 设备驱动开发详解,讲的挺好的,反复琢磨,掌握基本架构后,在看一些内核关于I2C适配器和I2C设备的驱动即可了解个大概,对于相关的驱动移植、BSP驱动开发大致上可以入门了。
对于内核的各个子系统,基本是都是基于总线、驱动、设备的模型来实现的。SPI的总线信息如下:
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
所有的SPI控制器、SPI控制器对应的驱动、SPI控制器上挂载的SPI从设备、SPI从设备的驱动都是注册到这条总线上的。 实际上SPI从设备实际上必须要挂载在SPI控制上的(这样才能通信),但从总线的角度看,SPI控制器和SPI从设备都是注册到SPI总线上的设备,这点和I2C子系统一样的。
当设备和总线匹配成功之后,即调用驱动的probe函数(各个子系统应该都是这样的套路)。
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);
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0;
}
匹配的函数,和I2C总线、平台总线platform_bus_type一样的套路,比较特定字符串是否一致。网上这个函数的解析比较多,不说了。
postcore_initcall(spi_init);
static int __init spi_init(void)
{
int status;
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
if (!buf) {
status = -ENOMEM;
goto err0;
}
status = bus_register(&spi_bus_type);
if (status < 0)
goto err1;
status = class_register(&spi_master_class);
if (status < 0)
goto err2;
if (IS_ENABLED(CONFIG_OF_DYNAMIC))
WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));
if (IS_ENABLED(CONFIG_ACPI))
WARN_ON(acpi_reconfig_notifier_register(&spi_acpi_notifier));
return 0;
err2:
bus_unregister(&spi_bus_type);
err1:
kfree(buf);
buf = NULL;
err0:
return status;
}
status = bus_register(&spi_bus_type); 主要就是这一行代码,向系统注册了SPI总线。其他的无需太多关心。
每个SPI控制器在子系统中对应一个spi_master结构体。比较大的结构体,可以去内核看下,各个成员的含义内核有注释,网上文档也比较多,不介绍了。重点是spi_master结构体里面的几个回调函数,这几个回调函数定义了SPI控制器发送消息、接收消息等的实现方式。当SPI从设备挂载到相应的控制器上,收发消息都是通过这几个回调函数实现的。这样其实也就实现了驱动开发者不需要再重新写SPI控制器的驱动的目的,只需要拿来用就可以了(软件分层的好处之一)。
具体细节不讲了,看一个SPI驱动的实现代码。
内核目录drivers\spi\ 中实现了许多的控制器驱动。基本上每个.c对应一个控制器驱动。下面看下spi-s3c24xx.c中的控制器驱动实现。
static struct platform_driver s3c24xx_spi_driver = {
.probe = s3c24xx_spi_probe,
.remove = s3c24xx_spi_remove,
.driver = {
.name = "s3c2410-spi",
.pm = S3C24XX_SPI_PMOPS,
},
};
当设备和上面的驱动匹配后,s3c24xx_spi_driver中的probe函数被调用,进行初始化、注册spi控制器等等。如下:
static int s3c24xx_spi_probe(struct platform_device *pdev)
{
struct s3c2410_spi_info *pdata;
struct s3c24xx_spi *hw;
struct spi_master *master;
struct resource *res;
int err = 0;
/*申请一个spi_master控制器结构体*/
master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));
if (master == NULL) {
dev_err(&pdev->dev, "No memory for spi_master\n");
return -ENOMEM;
}
/*获取设备信息指针,并填充相关信息*/
hw = spi_master_get_devdata(master);
hw->master = master;
hw->pdata = pdata = dev_get_platdata(&pdev->dev); /*设备的平台信息*/
hw->dev = &pdev->dev;
if (pdata == NULL) {
dev_err(&pdev->dev, "No platform data supplied\n");
err = -ENOENT;
goto err_no_pdata;
}
platform_set_drvdata(pdev, hw);
init_completion(&hw->done);
/* initialise fiq handler */
s3c24xx_spi_initfiq(hw);
/* setup the master state. */
/* the spi->mode bits understood by this driver: */
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
master->num_chipselect = hw->pdata->num_cs; /*控制器支持的片选数量,即最多可以的SPI从设备个数*/
master->bus_num = pdata->bus_num;
master->bits_per_word_mask = SPI_BPW_MASK(8);
/* setup the state for the bitbang driver */
hw->bitbang.master = hw->master;
hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;
hw->bitbang.chipselect = s3c24xx_spi_chipsel;
hw->bitbang.txrx_bufs = s3c24xx_spi_txrx;
hw->master->setup = s3c24xx_spi_setup;
dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);
/* find and map our resources */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hw->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(hw->regs)) {
err = PTR_ERR(hw->regs);
goto err_no_pdata;
}
hw->irq = platform_get_irq(pdev, 0);
if (hw->irq < 0) {
dev_err(&pdev->dev, "No IRQ specified\n");
err = -ENOENT;
goto err_no_pdata;
}
err = devm_request_irq(&pdev->dev, hw->irq, s3c24xx_spi_irq, 0,
pdev->name, hw);
if (err) {
dev_err(&pdev->dev, "Cannot claim IRQ\n");
goto err_no_pdata;
}
hw->clk = devm_clk_get(&pdev->dev, "spi");
if (IS_ERR(hw->clk)) {
dev_err(&pdev->dev, "No clock for device\n");
err = PTR_ERR(hw->clk);
goto err_no_pdata;
}
/* setup any gpio we can */
if (!pdata->set_cs) {
if (pdata->pin_cs < 0) {
dev_err(&pdev->dev, "No chipselect pin\n");
err = -EINVAL;
goto err_register;
}
err = devm_gpio_request(&pdev->dev, pdata->pin_cs,
dev_name(&pdev->dev));
if (err) {
dev_err(&pdev->dev, "Failed to get gpio for cs\n");
goto err_register;
}
hw->set_cs = s3c24xx_spi_gpiocs;
gpio_direction_output(pdata->pin_cs, 1);
} else
hw->set_cs = pdata->set_cs;
s3c24xx_spi_initialsetup(hw);
/* register our spi controller */
err = spi_bitbang_start(&hw->bitbang);
if (err) {
dev_err(&pdev->dev, "Failed to register SPI master\n");
goto err_register;
}
return 0;
err_register:
clk_disable(hw->clk);
err_no_pdata:
spi_master_put(hw->master);
return err;
}
上面这个函数的大多数代码,不做深入研究,细看需要结合SPI控制器芯片手册一起看才能明白各个含义。此处主要大概看下SPI子系统的框架,不细细研究驱动器实现。
下面看下SPI控制器的注册函数,spi_bitbang_start中调用了spi_register_master函数,注册驱动器。
int spi_bitbang_start(struct spi_bitbang *bitbang)
{
struct spi_master *master = bitbang->master;
int ret;
if (!master || !bitbang->chipselect)
return -EINVAL;
mutex_init(&bitbang->lock);
if (!master->mode_bits)
master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;
if (master->transfer || master->transfer_one_message)
return -EINVAL;
master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;
master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;
master->transfer_one = spi_bitbang_transfer_one;
master->set_cs = spi_bitbang_set_cs;
if (!bitbang->txrx_bufs) {
bitbang->use_dma = 0;
bitbang->txrx_bufs = spi_bitbang_bufs;
if (!master->setup) {
if (!bitbang->setup_transfer)
bitbang->setup_transfer =
spi_bitbang_setup_transfer;
master->setup = spi_bitbang_setup;
master->cleanup = spi_bitbang_cleanup;
}
}
/* driver may get busy before register() returns, especially
* if someone registered boardinfo for devices
*/
ret = spi_register_master(spi_master_get(master));
if (ret)
spi_master_put(master);
return 0;
}
注册SPI控制器:大体上和I2C一样的。
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);
mutex_init(&master->io_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);
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);
acpi_register_spi_devices(master);
done:
return status;
}
这里需要关注的是这个函数的后面几行,将会向SPI控制器注册SPI从设备,并将从设备加入到总线上,然后会匹配对应的驱动,从而调用到从设备的probe函数。主要方式是
1.从链表中读取出注册的SPI从设备,加入到对应的控制器中。
2.从设备树中读取出SPI控制器下的SPI从设备,并注册到SPI控制器。
3.acpi的暂时没有看过。懒得看了,基本上前两种是主流。
list_add_tail(&master->list, &spi_master_list); /*将SPI控制器加入相应的链表中*/
/*遍历board_list链表中的SPI从设备*/
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);
acpi_register_spi_devices(master);
static void spi_match_master_to_boardinfo(struct spi_master *master,
struct spi_board_info *bi)
{
struct spi_device *dev;
if (master->bus_num != bi->bus_num)
return;
dev = spi_new_device(master, bi);
if (!dev)
dev_err(master->dev.parent, "can't create new device for %s\n",
bi->modalias);
}
dev = spi_new_device(master, bi); 会实例化一个从设备对应的结构体spi_device,将从设备和控制器联系在一起。其实这里和I2C的也是一个套路的。
上面说的是spi控制器驱动,那么SPI控制器的设备信息在哪里呢?一般来讲设备的信息都会在初始化的时候注册进总线上面。一般会在以下两个地方:
1./arch/arm 对应的板级目录中。
对于本文中的设备信息位于文件:./plat-samsung/devs.c
struct platform_device s3c_device_spi0 = {
.name = "s3c2410-spi",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_spi0_resource),
.resource = s3c_spi0_resource,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
将这个设备信息逐个的地方:platform_add_devices将设备注册到平台的总线上。这里有点奇怪了,居然是注册到了平台总线,按照我的理解,是应该要注册到SPI总线的,暂时忽略过去,日后有空再细看。
static void __init nexcoder_init(void)
{
s3c_i2c0_set_platdata(NULL);
platform_add_devices(nexcoder_devices, ARRAY_SIZE(nexcoder_devices));
};
static struct platform_device *nexcoder_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&s3c_device_rtc,
&s3c_device_camif,
&s3c_device_spi0, --本文的spi控制器设备。
&s3c_device_spi1,
&nexcoder_device_nor,
};
2.设备树文件中。
本例中这个SPI控制器在设备树文件中没有找到,应该是驱动比较老了,没有更新,不支持设备树。随变找一个,表明意思即可,如下:
arch\arm\boot\dts\at91sam9263ek.dts
spi0: spi@fffa4000 {
status = "okay";
cs-gpios = <&pioA 5 0>, <0>, <0>, <0>;
mtd_dataflash@0 {
compatible = "atmel,at45", "atmel,dataflash"; --从设备对应的驱动
spi-max-frequency = <50000000>;
reg = <0>;
};
};
at91sam9263.dtsi
spi0: spi@fffa4000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "atmel,at91rm9200-spi"; --驱动器对应的驱动
reg = <0xfffa4000 0x200>;
interrupts = <14 IRQ_TYPE_LEVEL_HIGH 3>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi0>;
clocks = <&spi0_clk>;
clock-names = "spi_clk";
status = "disabled";
};
spi@fffa4000控制器下挂载了一个从设备mtd_dataflash@0。 设备的信息通过设备树指定,驱动从设备树中读取硬件信息来实例化驱动。
每个从设备均对应于一个spi_device结构体的。该结构体由spi驱动器调用spi_new_device创建,这个函数的第二个入参注意,来自于平台的注册哦。
struct spi_device {
struct device dev;
struct spi_master *master; --指向spi控制器结构体
u32 max_speed_hz;
u8 chip_select; --本从设备的片选信号
u8 bits_per_word;
u16 mode;
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires */
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
int cs_gpio; /* chip select gpio */
/* the statistics */
struct spi_statistics statistics;
/*
* likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
* - memory packing (12 bit samples into low bits, others zeroed)
* - priority
* - drop chipselect after each word
* - chipselect delays
* - ...
*/
};
具体的理论不讨论,大体明白I2C之后,这个也是那个套路的。去看一个从设备的驱动都干了些什么吧。以上面刚才说的设备树spi从设备mtd_dataflash@0为例:
spi0: spi@fffa4000 {
status = "okay";
cs-gpios = <&pioA 5 0>, <0>, <0>, <0>;
mtd_dataflash@0 {
compatible = "atmel,at45", "atmel,dataflash";
spi-max-frequency = <50000000>;
reg = <0>;
};
};
搜索下对应的驱动在哪里:
[zhanged@62a5eb9b140c ~/C600-kernel/CGEL6.x/bsp/SFUL/build/kernel-source/drivers]$ grep -rn "atmel,at45" ./
./mtd/devices/mtd_dataflash.c:101: { .compatible = "atmel,at45", },
具体驱动如下:
static struct spi_driver dataflash_driver = {
.driver = {
.name = "mtd_dataflash",
.of_match_table = of_match_ptr(dataflash_dt_ids),
},
.probe = dataflash_probe,
.remove = dataflash_remove,
/* FIXME: investigate suspend and resume... */
;
probe函数:
static int dataflash_probe(struct spi_device *spi)
{
int status;
struct flash_info *info;
/*
* Try to detect dataflash by JEDEC ID.
* If it succeeds we know we have either a C or D part.
* D will support power of 2 pagesize option.
* Both support the security register, though with different
* write procedures.
*/
info = jedec_probe(spi);
if (IS_ERR(info))
return PTR_ERR(info);
if (info != NULL)
return add_dataflash_otp(spi, info->name, info->nr_pages,
info->pagesize, info->pageoffset,
(info->flags & SUP_POW2PS) ? 'd' : 'c');
/*
* Older chips support only legacy commands, identifing
* capacity using bits in the status byte.
*/
status = dataflash_status(spi);
if (status <= 0 || status == 0xff) {
pr_debug("%s: status error %d\n",
dev_name(&spi->dev), status);
if (status == 0 || status == 0xff)
status = -ENODEV;
return status;
}
/* if there's a device there, assume it's dataflash.
* board setup should have set spi->max_speed_max to
* match f(car) for continuous reads, mode 0 or 3.
*/
switch (status & 0x3c) {
case 0x0c: /* 0 0 1 1 x x */
status = add_dataflash(spi, "AT45DB011B", 512, 264, 9);
break;
case 0x14: /* 0 1 0 1 x x */
status = add_dataflash(spi, "AT45DB021B", 1024, 264, 9);
break;
case 0x1c: /* 0 1 1 1 x x */
status = add_dataflash(spi, "AT45DB041x", 2048, 264, 9);
break;
case 0x24: /* 1 0 0 1 x x */
status = add_dataflash(spi, "AT45DB081B", 4096, 264, 9);
break;
case 0x2c: /* 1 0 1 1 x x */
status = add_dataflash(spi, "AT45DB161x", 4096, 528, 10);
break;
case 0x34: /* 1 1 0 1 x x */
status = add_dataflash(spi, "AT45DB321x", 8192, 528, 10);
break;
case 0x38: /* 1 1 1 x x x */
case 0x3c:
status = add_dataflash(spi, "AT45DB642x", 8192, 1056, 11);
break;
/* obsolete AT45DB1282 not (yet?) supported */
default:
dev_info(&spi->dev, "unsupported device (%x)\n",
status & 0x3c);
status = -ENODEV;
}
if (status < 0)
pr_debug("%s: add_dataflash --> %d\n", dev_name(&spi->dev),
status);
return status;
}
这个设备是一个flash设备,所以驱动中的代码和mtd 子系统有很大的关系,由于对mtd还不懂,所以暂时不谢这部分。仅将和spi控制器相关的总结下:
dataflash_probe调用add_dataflash_otp函数,向mtd子系统注册设备。
static int add_dataflash_otp(struct spi_device *spi, char *name, int nr_pages,
int pagesize, int pageoffset, char revision)
{
struct dataflash *priv;
struct mtd_info *device;
struct flash_platform_data *pdata = dev_get_platdata(&spi->dev);
char *otp_tag = "";
int err = 0;
priv = kzalloc(sizeof *priv, GFP_KERNEL);
if (!priv)
return -ENOMEM;
mutex_init(&priv->lock);
priv->spi = spi;
priv->page_size = pagesize;
priv->page_offset = pageoffset;
/* name must be usable with cmdlinepart */
sprintf(priv->name, "spi%d.%d-%s",
spi->master->bus_num, spi->chip_select,
name);
device = &priv->mtd;
device->name = (pdata && pdata->name) ? pdata->name : priv->name;
device->size = nr_pages * pagesize;
device->erasesize = pagesize;
device->writesize = pagesize;
device->type = MTD_DATAFLASH;
device->flags = MTD_WRITEABLE;
device->_erase = dataflash_erase;
device->_read = dataflash_read;
device->_write = dataflash_write;
device->priv = priv;
device->dev.parent = &spi->dev;
mtd_set_of_node(device, spi->dev.of_node);
if (revision >= 'c')
otp_tag = otp_setup(device, revision);
dev_info(&spi->dev, "%s (%lld KBytes) pagesize %d bytes%s\n",
name, (long long)((device->size + 1023) >> 10),
pagesize, otp_tag);
spi_set_drvdata(spi, priv);
err = mtd_device_register(device,
pdata ? pdata->parts : NULL,
pdata ? pdata->nr_parts : 0);
if (!err)
return 0;
kfree(priv);
return err;
}
我们关注上述代码以下几行:
device->_erase = dataflash_erase; --flash的擦除
device->_read = dataflash_read; --flash的读
device->_write = dataflash_write; --flash的写
这三个函数实现了flash的擦除、读、写。那么读写的方法实现就是调用了该设备所依附的SPI控制器的通信函数,这样呢,也就将从设备和spi控制器联系在一起了。下面看一下read函数:
static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct dataflash *priv = mtd->priv;
struct spi_transfer x[2] = { };
struct spi_message msg;
unsigned int addr;
uint8_t *command;
int status;
pr_debug("%s: read 0x%x..0x%x\n", dev_name(&priv->spi->dev),
(unsigned)from, (unsigned)(from + len));
/* Calculate flash page/byte address */
addr = (((unsigned)from / priv->page_size) << priv->page_offset)
+ ((unsigned)from % priv->page_size);
command = priv->command;
pr_debug("READ: (%x) %x %x %x\n",
command[0], command[1], command[2], command[3]);
spi_message_init(&msg);
x[0].tx_buf = command;
x[0].len = 8;
spi_message_add_tail(&x[0], &msg);
x[1].rx_buf = buf;
x[1].len = len;
spi_message_add_tail(&x[1], &msg);
mutex_lock(&priv->lock);
/* Continuous read, max clock = f(car) which may be less than
* the peak rate available. Some chips support commands with
* fewer "don't care" bytes. Both buffers stay unchanged.
*/
command[0] = OP_READ_CONTINUOUS;
command[1] = (uint8_t)(addr >> 16);
command[2] = (uint8_t)(addr >> 8);
command[3] = (uint8_t)(addr >> 0);
/* plus 4 "don't care" bytes */
status = spi_sync(priv->spi, &msg);
mutex_unlock(&priv->lock);
if (status >= 0) {
*retlen = msg.actual_length - 8;
status = 0;
} else
pr_debug("%s: read %x..%x --> %d\n",
dev_name(&priv->spi->dev),
(unsigned)from, (unsigned)(from + len),
status);
return status;
}
前部分主要是将消息封装、加入到消息链表中,之后调用spi_sync来读取消息。
spi_sync(struct spi_device *spi, struct spi_message *message)
__spi_sync(spi, message);
__spi_pump_messages
之后会逐步调用到spi控制器对应的收发消息函数中。后面内容比较多,也比较复杂,不做深入研究。
和SPI控制器的设备信息同样,一般也分布在两个地方:
1.平台代码目录
/arch/* 下一般存在的都是具体平台设备相关的代码,一般芯片的硬件信息都在这里面的,只是这些代码相对比较多,也比较庞大,所以才有了设备树的概念,将这些垃圾代码全部以设备树的形式传递给内核了。
以文件./mach-omap1/board-nokia770.c为例:
static struct spi_board_info nokia770_spi_board_info[] __initdata = {
[0] = {
.modalias = "lcd_mipid",
.bus_num = 2,
.chip_select = 3,
.max_speed_hz = 12000000,
.platform_data = &nokia770_mipid_platform_data,
},
[1] = {
.modalias = "ads7846",
.bus_num = 2,
.chip_select = 0,
.max_speed_hz = 2500000,
.platform_data = &nokia770_ads7846_platform_data,
},
};
cpu架构相关的初始化:
omap_nokia770_init
spi_register_board_info(nokia770_spi_board_info,
ARRAY_SIZE(nokia770_spi_board_info));
int spi_register_board_info(struct spi_board_info const *info, unsigned n)
{
struct boardinfo *bi;
int i;
if (!n)
return -EINVAL;
bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
if (!bi)
return -ENOMEM;
for (i = 0; i < n; i++, bi++, info++) {
struct spi_master *master;
memcpy(&bi->board_info, info, sizeof(*info));
mutex_lock(&board_lock);
list_add_tail(&bi->list, &board_list);
list_for_each_entry(master, &spi_master_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock);
}
return 0;
}
最后调用 spi_register_board_info 将spi从设备加入到链表board_list中。并且遍历spi控制器的链表,当该从设备和控制器匹配时,调用spi_new_device构造spi_device结构体,从而会将从设备和控制器关联起来,并将会调用 spi_add_device将从设备注册。
这样,当内核注册从设备时,会遍历控制器看是否匹配。当内核注册控制器时,也会遍历设备,看是否匹配。这样,无论是谁前谁后注册,都能保证会匹配,不会遗漏。
2.设备树,如本例的mtd_dataflash设备,硬件信息由设备树指定:
arch\arm\boot\dts\at91sam9263ek.dts
spi0: spi@fffa4000 {
status = "okay";
cs-gpios = <&pioA 5 0>, <0>, <0>, <0>;
mtd_dataflash@0 {
compatible = "atmel,at45", "atmel,dataflash"; --从设备对应的驱动
spi-max-frequency = <50000000>;
reg = <0>;
};
};
at91sam9263.dtsi
spi0: spi@fffa4000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "atmel,at91rm9200-spi"; --驱动器对应的驱动
reg = <0xfffa4000 0x200>;
interrupts = <14 IRQ_TYPE_LEVEL_HIGH 3>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi0>;
clocks = <&spi0_clk>;
clock-names = "spi_clk";
status = "disabled";
};
这种方式的从设备前面写控制器的驱动时,前面已经提到过了,流程简要如下:
spi_register_master
of_register_spi_devices
{
for_each_available_child_of_node(master->dev.of_node, nc)
of_register_spi_device(master, nc)
}
当向内核注册spi控制器时,会遍历该控制器对应的设备树节点下所有的子节点,而每个子节点都是代码挂载在该控制器下的一个从设备。所以,通过for_each_available_child_of_node遍历设备树的子节点,然后调用of_register_spi_device读取各个子节点的信息,然后申请spi_device结构体等。
of_register_spi_device
spi_alloc_device 申请从设备结构体
of_property_read_u32 设备树属性读取函数,多次调用类似函数去读信息,填充spi_device结构体
spi_add_device 注册从设备
spi设备也是字符设备,并且将这部分抽象了出来,所有的spi共用,因此spi子系统将字符设备设备的操作函数file_operations已经写好了。如下:
spidev.c
spidev_init
register_chrdev注册字符设备,并且注册 file_operations 函数集
当我们在用户态使用read write IOCtl时,会调用到下面这几个函数,然后会调用从设备对应的spi控制器的通讯函数,产生硬件信号对从设备进行读写操作。
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.write = spidev_write,
.read = spidev_read,
.unlocked_ioctl = spidev_ioctl,
.compat_ioctl = spidev_compat_ioctl,
.open = spidev_open,
.release = spidev_release,
.llseek = no_llseek,
;
虽然内核态的驱动框架大体明白了,但是用户态怎么使用呢?以下是几个网上的例子。总结下来套路如下:
以下是几个实例:
https://www.emcraft.com/stm32f769i-discovery-board/accessing-spi-devices-in-linux
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
char *name;
int fd;
struct spi_ioc_transfer xfer[2];
unsigned char buf[32], *bp;
int len, status;
name = argv[1];
fd = open(name, O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
memset(xfer, 0, sizeof xfer);
memset(buf, 0, sizeof buf);
len = sizeof buf;
/*
* Send a GetID command
*/
buf[0] = 0x9f;
len = 6;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 1;
xfer[1].rx_buf = (unsigned long) buf;
xfer[1].len = 6;
status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
return -1;
}
printf("response(%d): ", status);
for (bp = buf; len; len--)
printf("%02x ", *bp++);
printf("\n");
return 0;
}
https://e2e.ti.com/support/legacy_forums/embedded/linux/f/354/t/123792
static int spi_write(int fd,unsigned short addr,unsigned short value)
{
uint16_t out_buf[2];
int status;
struct spi_ioc_transfer xfer[1] = {
{
.tx_buf = (unsigned long)out_buf,
.rx_buf = 0,
.len = 2,
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word = bits,
.cs_change = 0
},
};
out_buf[0] = addr;
out_buf[2] = value;
printf("Writing register %04x with value %04x \r\n", addr,value);
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
if (status < 0) {
pabort("SPI_IOC_MESSAGE");
}
return status;
}
https://www.cnblogs.com/xiaojianliu/p/9841677.html
int spi_read()
{
bt_devide_msg msg;
unsigned char ucRegVal;
int ret,i;
unsigned char tx[20];
for(i = 0;i<20;i++)
{
tx[i] = 0xda;
}
unsigned char rx[ARRAY_SIZE(tx)] = {0, };
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = ARRAY_SIZE(tx),
.delay_usecs = udelay,
.speed_hz = speed,
.bits_per_word = bits,
};
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
if (ret < 1)
{
printf("can't read spi message\n");
return -1;
}
if(rx[0] !=0xAA)
{
printf("read spi data: ");
for (ret = 0; ret < ARRAY_SIZE(tx); ret++)
{
printf("%02X ", rx[ret]);
}
printf("\n");
}
ucRegVal = rx[ARRAY_SIZE(tx)-1];
get_data_process(rx);
return 1;
}
tools/spi/spidev_test.c