SPI驅動之主控制器驅動程序

嵌入式微處理器訪問SPI設備有兩種方式:使用GPIO模擬SPI接口的工作時序或者使用SPI控制器。使用GPIO模擬SPI接口的工作時序是非常容易實現的,但是會導致大量的時間耗費在模擬SPI接口的時序上,訪問效率比較低,容易成爲系統瓶頸。這裏主要分析使用SPI控制器的情況。

在內核的drivers/spi/目錄下有兩個spi主控制器驅動程序:spi_s3c24xx.c和spi_s3c24xx_gpio.c其中spi_s3c24xx.c是基於s3c24xx下相應的spi接口的驅動程序,spi_s3c24xx_gpio.c運行用戶指定3個gpio口分別充當spi_clk、spi_mosi和spi_miso接口,模擬標準的spi總線。UT4412BV01開發板預留了兩路的spi接口(spi0和spi1),對於UT4412BV01開發板而言,使用的是spi_s3c64xx.c,也就是硬件SPI,不是軟件SPI。注:下面是基於硬件SPI的spi1分析。

1. 定義platform device

kernel3.0.15/arch/arm/mach-exynos/dev-spi.c

static struct resource exynos_spi1_resource[] = {
	[0] = {
		.start = EXYNOS_PA_SPI1,
		.end   = EXYNOS_PA_SPI1 + 0x100 - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = DMACH_SPI1_TX,
		.end   = DMACH_SPI1_TX,
		.flags = IORESOURCE_DMA,
	},
	[2] = {
		.start = DMACH_SPI1_RX,
		.end   = DMACH_SPI1_RX,
		.flags = IORESOURCE_DMA,
	},
	[3] = {
		.start = IRQ_SPI1,
		.end   = IRQ_SPI1,
		.flags = IORESOURCE_IRQ,
	},
};

static struct s3c64xx_spi_info exynos_spi1_pdata = {
	.cfg_gpio = exynos_spi_cfg_gpio,
	.fifo_lvl_mask = 0x7f,
	.rx_lvl_offset = 15,
	.high_speed = 1,
	.clk_from_cmu = true,
	.tx_st_done = 25,
};

struct platform_device exynos_device_spi1 = {
	.name		  = "s3c64xx-spi",
	.id		  = 1,
	.num_resources	  = ARRAY_SIZE(exynos_spi1_resource),
	.resource	  = exynos_spi1_resource,
	.dev = {
		.dma_mask		= &spi_dmamask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
		.platform_data = &exynos_spi1_pdata,
	},
};
exynos4412總共定義了三個spi控制器平臺設備,實際上UT4412BV01開發板只預留了兩個spi控制器(spi0和spi1)。platform設備給出了spi1接口的寄存器地址資源及IRQ資源。注意其設備名爲s3c64xx-spi

2. 定義platform driver

kernel3.0.15/drivers/spi/spi_s3c64xx.c

static struct platform_driver s3c64xx_spi_driver = {
	.driver = {
		.name	= "s3c64xx-spi",
		.owner = THIS_MODULE,
	},
	.remove = s3c64xx_spi_remove,
	.suspend = s3c64xx_spi_suspend,
	.resume = s3c64xx_spi_resume,
};
MODULE_ALIAS("platform:s3c64xx-spi");

static int __init s3c64xx_spi_init(void)
{
	//設備不可熱插拔,所以使用該函數,而不是platform_driver_register
	return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
}
subsys_initcall(s3c64xx_spi_init);

static void __exit s3c64xx_spi_exit(void)
{
	platform_driver_unregister(&s3c64xx_spi_driver);
}
module_exit(s3c64xx_spi_exit);

MODULE_AUTHOR("Jaswinder Singh <[email protected]>");
MODULE_DESCRIPTION("S3C64XX SPI Controller Driver");
MODULE_LICENSE("GPL");
調用了platform_driver_probe註冊platform驅動,註冊完成以後將會調用platform的s3c64xx_spi_probe函數。注意:platform驅動的name和platform device的name是相同的

3. s3c64xx_spi_probe函數

kernel3.0.15/drivers/spi/spi_s3c64xx.c

當exynos_device_spi1中的name與s3c64xx_spi_driver中的name相同時,也就是是設備名字跟驅動名字可以匹配,s3c64xx_spi_probe驅動探測函數被調用,該函數代碼如下所示:

static int __init s3c64xx_spi_probe(struct platform_device *pdev)
{
	struct resource	*mem_res, *dmatx_res, *dmarx_res;
	struct s3c64xx_spi_driver_data *sdd;
	struct s3c64xx_spi_info *sci;
	struct spi_master *master;
	int ret;

	if (pdev->id < 0) { //pdev->id = 1
		dev_err(&pdev->dev,
				"Invalid platform device id-%d\n", pdev->id);
		return -ENODEV;
	}

	if (pdev->dev.platform_data == NULL) { //pdev->dev.platform_data = &exynos_spi1_pdata
		dev_err(&pdev->dev, "platform_data missing!\n");
		return -ENODEV;
	}

	sci = pdev->dev.platform_data;
	if (!sci->src_clk_name) { //在板級文件中通過調用s3c64xx_spi_set_info()來初始化
		dev_err(&pdev->dev,
			"Board init must call s3c64xx_spi_set_info()\n");
		return -EINVAL;
	}

	/* Check for availability of necessary resource */

	//獲取DMA0資源
	dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
	if (dmatx_res == NULL) {
		dev_err(&pdev->dev, "Unable to get SPI-Tx dma resource\n");
		return -ENXIO;
	}

	//獲取DMA1資源
	dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
	if (dmarx_res == NULL) {
		dev_err(&pdev->dev, "Unable to get SPI-Rx dma resource\n");
		return -ENXIO;
	}

	//獲取IO內存資源
	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (mem_res == NULL) {
		dev_err(&pdev->dev, "Unable to get SPI MEM resource\n");
		return -ENXIO;
	}

	/** 
	 * 通過跟蹤spi_alloc_master相關源碼可知,
	 * 此處分配struct spi_master + struct s3c64xx_spi_driver_data大小的數據,
	 * 把s3c64xx_spi_driver_data設爲spi_master的私有數據 
	 */
	master = spi_alloc_master(&pdev->dev,
				sizeof(struct s3c64xx_spi_driver_data));
	if (master == NULL) {
		dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
		return -ENOMEM;
	}

	/**
	 * platform_set_drvdata 和 platform_get_drvdata
	 * probe函數中定義的局部變量,如果我想在其他地方使用它怎麼辦呢?
	 * 這就需要把它保存起來。內核提供了這個方法,
	 * 使用函數platform_set_drvdata()可以將master保存成平臺總線設備的私有數據。
	 * 以後再要使用它時只需調用platform_get_drvdata()就可以了。
	 */
	platform_set_drvdata(pdev, master);

	//從master中獲得s3c64xx_spi_driver_data,並初始化相關成員
	sdd = spi_master_get_devdata(master);
	sdd->master = master;
	sdd->cntrlr_info = sci;
	sdd->pdev = pdev;
	sdd->sfr_start = mem_res->start;
	sdd->tx_dmach = dmatx_res->start;
	sdd->rx_dmach = dmarx_res->start;

	sdd->cur_bpw = 8;

	//master相關成員的初始化
	master->bus_num = pdev->id; //總線號
	master->setup = s3c64xx_spi_setup;
	master->transfer = s3c64xx_spi_transfer;
	master->num_chipselect = sci->num_cs; //該總線上的設備數
	master->dma_alignment = 8;
	/* the spi->mode bits understood by this driver: */
	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; //mode_3

	//申請IO內存
	if (request_mem_region(mem_res->start,
			resource_size(mem_res), pdev->name) == NULL) {
		dev_err(&pdev->dev, "Req mem region failed\n");
		ret = -ENXIO;
		goto err0;
	}

	//建立映射
	sdd->regs = ioremap(mem_res->start, resource_size(mem_res));
	if (sdd->regs == NULL) {
		dev_err(&pdev->dev, "Unable to remap IO\n");
		ret = -ENXIO;
		goto err1;
	}

	//SPI的IO管腳配置,將相應的IO管腳設置爲SPI功能
	if (sci->cfg_gpio == NULL || sci->cfg_gpio(pdev)) {
		dev_err(&pdev->dev, "Unable to config gpio\n");
		ret = -EBUSY;
		goto err2;
	}

	//使能時鐘
	sdd->clk = clk_get(&pdev->dev, "spi");
	if (IS_ERR(sdd->clk)) {
		dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n");
		ret = PTR_ERR(sdd->clk);
		goto err3;
	}

	if (clk_enable(sdd->clk)) {
		dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n");
		ret = -EBUSY;
		goto err4;
	}

	sdd->src_clk = clk_get(&pdev->dev, sci->src_clk_name);
	if (IS_ERR(sdd->src_clk)) {
		dev_err(&pdev->dev,
			"Unable to acquire clock '%s'\n", sci->src_clk_name);
		ret = PTR_ERR(sdd->src_clk);
		goto err5;
	}

	if (clk_enable(sdd->src_clk)) {
		dev_err(&pdev->dev, "Couldn't enable clock '%s'\n",
							sci->src_clk_name);
		ret = -EBUSY;
		goto err6;
	}

	//創建單個線程的工作隊列,用於數據收發操作
	sdd->workqueue = create_singlethread_workqueue(
						dev_name(master->dev.parent));
	if (sdd->workqueue == NULL) {
		dev_err(&pdev->dev, "Unable to create workqueue\n");
		ret = -ENOMEM;
		goto err7;
	}

	//硬件初始化,初始化設置寄存器,包括對SPIMOSI、SPIMISO、SPICLK引腳的設置
	s3c64xx_spi_hwinit(sdd, pdev->id);

	//鎖、工作隊列等初始化
	spin_lock_init(&sdd->lock);
	init_completion(&sdd->xfer_completion);
	INIT_WORK(&sdd->work, s3c64xx_spi_work);
	INIT_LIST_HEAD(&sdd->queue);

	if (spi_register_master(master)) {
		dev_err(&pdev->dev, "cannot register SPI master\n");
		ret = -EBUSY;
		goto err8;
	}

	dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d "
					"with %d Slaves attached\n",
					pdev->id, master->num_chipselect);
	dev_dbg(&pdev->dev, "\tIOmem=[0x%x-0x%x]\tDMA=[Rx-%d, Tx-%d]\n",
					mem_res->end, mem_res->start,
					sdd->rx_dmach, sdd->tx_dmach);

	return 0;

err8:
	destroy_workqueue(sdd->workqueue);
err7:
	clk_disable(sdd->src_clk);
err6:
	clk_put(sdd->src_clk);
err5:
	clk_disable(sdd->clk);
err4:
	clk_put(sdd->clk);
err3:
err2:
	iounmap((void *) sdd->regs);
err1:
	release_mem_region(mem_res->start, resource_size(mem_res));
err0:
	platform_set_drvdata(pdev, NULL);
	spi_master_put(master);

	return ret;
}
s3c64xx_spi_probe函數很長,但做的事情卻很簡單,從上面代碼的註釋可以基本理清整個探測流程。其中用到幾個比較重要的函數,下面來一一解釋。

spi_alloc_master(kernel3.0.15/drivers/spi/spi.c)

spi_alloc_master函數用於請求分配一個spi_master。

struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
{
	struct spi_master	*master;

	if (!dev)
		return NULL;

	/* 分配內存,分配的內存大小是*master + size,包含了兩部分內存 */
	master = kzalloc(size + sizeof *master, GFP_KERNEL);
	if (!master)
		return NULL;

	device_initialize(&master->dev); //設備模型中的初始設備函數
	master->dev.class = &spi_master_class; //spi_master_class在SPI子系統初始化的時候就已經註冊好了
	master->dev.parent = get_device(dev); //設備當前設備的父設備,這與設備模型相關
	spi_master_set_devdata(master, &master[1]); //&master[1]就是master之後的另一部分內存的起始地址

	return master;
}

spi_master_register(kernel3.0.15/drivers/spi/spi.c)

spi_master_register函數用於向內核註冊一個spi_master。

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;

	/* even if it's just one always-selected device, there must
	 * be at least one chipselect
	 */
	if (master->num_chipselect == 0) //一個SPI控制器至少有一個片選,因此片選數爲0則出錯
		return -EINVAL;

	/* convention:  dynamically assigned bus IDs count down from the max */
	if (master->bus_num < 0) { //如果總線號小於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;
	}

	spin_lock_init(&master->bus_lock_spinlock);
	mutex_init(&master->bus_lock_mutex);
	master->bus_lock_flag = 0;

	/* 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); //把master加入到設備模型中
	status = device_add(&master->dev);
	if (status < 0)
		goto done;
	dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
			dynamic ? " (dynamic)" : "");

	mutex_lock(&board_lock);
	list_add_tail(&master->list, &spi_master_list);
	list_for_each_entry(bi, &board_list, list) //遍歷board_list這個鏈表
		spi_match_master_to_boardinfo(master, &bi->board_info);
	mutex_unlock(&board_lock);

	status = 0;

	/* Register devices from the device tree */
	of_register_spi_devices(master);
done:
	return status;
}

spi_match_master_to_boardinfo(kernel3.0.15/drivers/spi/spi.c)

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) //每找到一個成員就將它的總線號與master的總線號進行比較,如果相等則調用spi_new_device函數創建一個spi設備
		return;

	dev = spi_new_device(master, bi);
	if (!dev)
		dev_err(master->dev.parent, "can't create new device for %s\n",
			bi->modalias);
}

spi_new_device(kernel3.0.15/drivers/spi/spi.c)

struct spi_device *spi_new_device(struct spi_master *master,
				  struct spi_board_info *chip)
{
	struct spi_device	*proxy;
	int			status;

	/* NOTE:  caller did any chip->bus_num checks necessary.
	 *
	 * Also, unless we change the return value convention to use
	 * error-or-pointer (not NULL-or-pointer), troubleshootability
	 * suggests syslogged diagnostics are best here (ugh).
	 */

	proxy = spi_alloc_device(master);
	if (!proxy)
		return NULL;

	WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));

	proxy->chip_select = chip->chip_select;
	proxy->max_speed_hz = chip->max_speed_hz;
	proxy->mode = chip->mode;
	proxy->irq = chip->irq;
	strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias)); //此處比較關鍵,設備名字拷貝
	proxy->dev.platform_data = (void *) chip->platform_data;
	proxy->controller_data = chip->controller_data;
	proxy->controller_state = NULL;

	status = spi_add_device(proxy);
	if (status < 0) {
		spi_dev_put(proxy);
		return NULL;
	}

	return proxy;
}
spi_alloc_device(kernel3.0.15/drivers/spi/spi.c)
struct spi_device *spi_alloc_device(struct spi_master *master)
{
	struct spi_device	*spi;
	struct device		*dev = master->dev.parent;

	if (!spi_master_get(master)) //錯誤檢測
		return NULL;

	spi = kzalloc(sizeof *spi, GFP_KERNEL); //分配內存
	if (!spi) {
		dev_err(dev, "cannot alloc spi_device\n");
		spi_master_put(master);
		return NULL;
	}

	spi->master = master;
	spi->dev.parent = dev;
	spi->dev.bus = &spi_bus_type; //該spi設備屬於SPI子系統初始化時註冊的叫“spi”的總線
	spi->dev.release = spidev_release;
	device_initialize(&spi->dev); //設備模型方面的初始化
	return spi;
}
spi_add_device(kernel3.0.15/drivers/spi/spi.c)
int spi_add_device(struct spi_device *spi)
{
	static DEFINE_MUTEX(spi_add_lock);
	struct device *dev = spi->master->dev.parent;
	struct device *d;
	int status;

	/* Chipselects are numbered 0..max; validate. */
	if (spi->chip_select >= spi->master->num_chipselect) { //片選號是從0開始的,如果大於或者等於片選數的話則返回出錯
		dev_err(dev, "cs%d >= max %d\n",
			spi->chip_select,
			spi->master->num_chipselect);
		return -EINVAL;
	}

	/* Set the bus ID string */
	dev_set_name(&spi->dev, "%s.%u", dev_name(&spi->master->dev), 
			spi->chip_select);


	/* We need to make sure there's no other device with this
	 * chipselect **BEFORE** we call setup(), else we'll trash
	 * its configuration.  Lock against concurrent add() calls.
	 */
	mutex_lock(&spi_add_lock);

	d = bus_find_device_by_name(&spi_bus_type, NULL, dev_name(&spi->dev)); //遍歷spi總線,看是否已經註冊過該設備
	if (d != NULL) {
		dev_err(dev, "chipselect %d already in use\n",
				spi->chip_select);
		put_device(d);
		status = -EBUSY;
		goto done;
	}

	/* Drivers may modify this initial i/o setup, but will
	 * normally rely on the device being setup.  Devices
	 * using SPI_CS_HIGH can't coexist well otherwise...
	 */
	status = spi_setup(spi);
	if (status < 0) {
		dev_err(dev, "can't setup %s, status %d\n",
				dev_name(&spi->dev), status);
		goto done;
	}

	/* Device may be bound to an active driver when this returns */
	status = device_add(&spi->dev);
	if (status < 0)
		dev_err(dev, "can't add %s, status %d\n",
				dev_name(&spi->dev), status);
	else
		dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev));

done:
	mutex_unlock(&spi_add_lock);
	return status;
}
spi_setup(kernel3.0.15/drivers/spi/spi.c)
int spi_setup(struct spi_device *spi)
{
	unsigned	bad_bits;
	int		status;

	/* help drivers fail *cleanly* when they need options
	 * that aren't supported with their current master
	 */
	bad_bits = spi->mode & ~spi->master->mode_bits; //如果驅動不支持該設備的工作模式則返回出錯
	if (bad_bits) {
		dev_err(&spi->dev, "setup: unsupported mode bits %x\n",
			bad_bits);
		return -EINVAL;
	}

	if (!spi->bits_per_word)
		spi->bits_per_word = 8;

	status = spi->master->setup(spi); //調用控制器驅動裏的s3c64xx_spi_setup函數

	dev_dbg(&spi->dev, "setup mode %d, %s%s%s%s"
				"%u bits/w, %u Hz max --> %d\n",
			(int) (spi->mode & (SPI_CPOL | SPI_CPHA)),
			(spi->mode & SPI_CS_HIGH) ? "cs_high, " : "",
			(spi->mode & SPI_LSB_FIRST) ? "lsb, " : "",
			(spi->mode & SPI_3WIRE) ? "3wire, " : "",
			(spi->mode & SPI_LOOP) ? "loopback, " : "",
			spi->bits_per_word, spi->max_speed_hz,
			status);

	return status;
}

s3c64xx_spi_setup(kernel3.0.15/drivers/spi/spi_s3c64xx.c)

static int s3c64xx_spi_setup(struct spi_device *spi)
{
	//由此可知在實例化struct spi_board_info時,其controller_data成員就應該指向struct s3c64xx_spi_csinfo的對象
	struct s3c64xx_spi_csinfo *cs = spi->controller_data; 
	struct s3c64xx_spi_driver_data *sdd;
	struct s3c64xx_spi_info *sci;
	struct spi_message *msg;
	unsigned long flags;
	int err = 0;

	if (cs == NULL || cs->set_level == NULL) {
		dev_err(&spi->dev, "No CS for SPI(%d)\n", spi->chip_select);
		return -ENODEV;
	}

	sdd = spi_master_get_devdata(spi->master);
	sci = sdd->cntrlr_info;

	spin_lock_irqsave(&sdd->lock, flags);

	list_for_each_entry(msg, &sdd->queue, queue) {
		/* Is some mssg is already queued for this device */
		if (msg->spi == spi) {
			dev_err(&spi->dev,
				"setup: attempt while mssg in queue!\n");
			spin_unlock_irqrestore(&sdd->lock, flags);
			return -EBUSY;
		}
	}

	if (sdd->state & SUSPND) {
		spin_unlock_irqrestore(&sdd->lock, flags);
		dev_err(&spi->dev,
			"setup: SPI-%d not active!\n", spi->master->bus_num);
		return -ESHUTDOWN;
	}

	spin_unlock_irqrestore(&sdd->lock, flags);

	if (spi->bits_per_word != 8
			&& spi->bits_per_word != 16
			&& spi->bits_per_word != 32) {
		dev_err(&spi->dev, "setup: %dbits/wrd not supported!\n",
							spi->bits_per_word);
		err = -EINVAL;
		goto setup_exit;
	}

	/* Check if we can provide the requested rate */
	if (!sci->clk_from_cmu) {
		u32 psr, speed;

		/* Max possible */
		speed = clk_get_rate(sdd->src_clk) / 2 / (0 + 1);

		if (spi->max_speed_hz > speed)
			spi->max_speed_hz = speed;

		psr = clk_get_rate(sdd->src_clk) / 2 / spi->max_speed_hz - 1;
		psr &= S3C64XX_SPI_PSR_MASK;
		if (psr == S3C64XX_SPI_PSR_MASK)
			psr--;

		speed = clk_get_rate(sdd->src_clk) / 2 / (psr + 1);
		if (spi->max_speed_hz < speed) {
			if (psr+1 < S3C64XX_SPI_PSR_MASK) {
				psr++;
			} else {
				err = -EINVAL;
				goto setup_exit;
			}
		}

		speed = clk_get_rate(sdd->src_clk) / 2 / (psr + 1);
		if (spi->max_speed_hz >= speed)
			spi->max_speed_hz = speed;
		else
			err = -EINVAL;
	}

setup_exit:

	/* setup() returns with device de-selected */
	disable_cs(sdd, spi);

	return err;
}

發佈了12 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章