Linux Spi驱动子系统学习

前几天学习了Linux的I2C驱动子系统,工作需要,今天再看下spi子系统,学习这些子系统,最重要的是一定要掌握到两个思想:
1.面相对象的思想,即驱动和设备的分离,便于驱动的移植,通过设备的硬件信息来实例化驱动。
2.软件开发的分层思想,比如I2C子系统将I2C适配器的功能与I2C具体设备的驱动分成两个层次来实现。这样的好处不言而喻,非常便于驱动的移植。降低了I2C设备开发人员的工作量。
当然,内核的代码还有需要非常好的思想,暂时没有深入的体会到。。。

回到正题,spi子系统其实和I2C子系统一毛一样的套路,大致了解了I2C之后,基本上SPI的看下代码实现也就明白了。关于I2C的子系统,可以看下宋宝华的Linux 设备驱动开发详解,讲的挺好的,反复琢磨,掌握基本架构后,在看一些内核关于I2C适配器和I2C设备的驱动即可了解个大概,对于相关的驱动移植、BSP驱动开发大致上可以入门了。

SPI驱动子系统

SPI总线

对于内核的各个子系统,基本是都是基于总线、驱动、设备的模型来实现的。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函数(各个子系统应该都是这样的套路)。

SPI总线上设备和驱动的匹配
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一样的套路,比较特定字符串是否一致。网上这个函数的解析比较多,不说了。

SPI总线的注册(drivers/spi/spi.c)
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控制器在子系统中对应一个spi_master结构体。比较大的结构体,可以去内核看下,各个成员的含义内核有注释,网上文档也比较多,不介绍了。重点是spi_master结构体里面的几个回调函数,这几个回调函数定义了SPI控制器发送消息、接收消息等的实现方式。当SPI从设备挂载到相应的控制器上,收发消息都是通过这几个回调函数实现的。这样其实也就实现了驱动开发者不需要再重新写SPI控制器的驱动的目的,只需要拿来用就可以了(软件分层的好处之一)。

具体细节不讲了,看一个SPI驱动的实现代码。
内核目录drivers\spi\ 中实现了许多的控制器驱动。基本上每个.c对应一个控制器驱动。下面看下spi-s3c24xx.c中的控制器驱动实现。

SPI控制器驱动
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控制器驱动,那么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控制器从设备

每个从设备均对应于一个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
	 *  - ...
	 */
};
SPI控制器从设备驱动

具体的理论不讨论,大体明白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控制器从设备信息

和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共用,因此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,
;

用户态读取spi设备

虽然内核态的驱动框架大体明白了,但是用户态怎么使用呢?以下是几个网上的例子。总结下来套路如下:

  1. 封装spi的消息结构体
  2. ioctl、read、write进行读写操作

以下是几个实例:

sample 1

https://www.emcraft.com/stm32f769i-discovery-board/accessing-spi-devices-in-linux

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

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

sample 2

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

sample 3

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

sample4 内核提供的测试spi的函数

tools/spi/spidev_test.c
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值