天天看點

基于S3C2440的嵌入式Linux驅動——SPI子系統解讀(二)

該系列文章将分為四個部分:

   第一部分,将對SPI子系統整體進行描述,同時給出SPI的相關資料結構,最後描述SPI總線的注冊。基于S3C2440的嵌入式Linux驅動——SPI子系統解讀(一)

   第二部分,即本篇文章,該文将對SPI的主要制器(master)驅動進行描述。

   第三部分,該文将對SPI裝置驅動,也稱protocol 驅動,進行講解。基于S3C2440的嵌入式Linux驅動——SPI子系統解讀(三)

   第四部分,通過SPI裝置驅動留給使用者層的API,我們将從上到下描述資料是如何通過SPI的protocol 驅動,由bitbang中轉,最後由master驅動将資料傳輸出

                   去。 基于S3C2440的嵌入式Linux驅動——SPI子系統解讀(四)

本文屬于第二部分。

4. 主要制器驅動程式

4.1 定義 platform device

    下列資料結構位于arch/arm/plat-s3c24XX/devs.c

/* SPI (0) */

static struct resource s3c_spi0_resource[] = {
	[0] = {
		.start = S3C24XX_PA_SPI,
		.end   = S3C24XX_PA_SPI + 0x1f,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_SPI0,
		.end   = IRQ_SPI0,
		.flags = IORESOURCE_IRQ,
	}

};

static u64 s3c_device_spi0_dmamask = 0xffffffffUL;

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 = &s3c_device_spi0_dmamask,
                .coherent_dma_mask = 0xffffffffUL
        }
};
           

   platform裝置給出了spi0接口的寄存器位址資源以及IRQ資源。注意其裝置名為s3c2410-spi。

4.2 定義platform driver

 下列函數位于deivers/spi/s3c24xx.c。

MODULE_ALIAS("platform:s3c2410-spi");
static struct platform_driver s3c24xx_spi_driver = {
    .remove        = __exit_p(s3c24xx_spi_remove),
    .suspend    = s3c24xx_spi_suspend,
    .resume        = s3c24xx_spi_resume,
    .driver        = {
        .name    = "s3c2410-spi",
        .owner    = THIS_MODULE,
    },
};

static int __init s3c24xx_spi_init(void)
{
        return platform_driver_probe(&s3c24xx_spi_driver, s3c24xx_spi_probe);//裝置不可熱插拔,是以使用該函數,而不是platform_driver_register
}

static void __exit s3c24xx_spi_exit(void)
{
        platform_driver_unregister(&s3c24xx_spi_driver);
}

module_init(s3c24xx_spi_init);
module_exit(s3c24xx_spi_exit);
           

  調用了platform_driver_probe注冊platform驅動,注冊完成以後将會調用platform的s3c24xx_spi_probe函數。

  NOTE:platform驅動的name和platform device的name是相同的。

4.2.1  s3c24xx_spi_probe函數

下列函數位于deivers/spi/s3c24xx.c。

static int __init 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;

	/*配置設定master結構體,其中包括s3c24xx_spi結構的記憶體空間,使用master.dev.driver_data指向它*/
	master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));
	if (master == NULL) {
		dev_err(&pdev->dev, "No memory for spi_master\n");
		err = -ENOMEM;
		goto err_nomem;
	}
	/*獲得s3c24xx_spi結構,并清0該結構*/
	hw = spi_master_get_devdata(master);
	memset(hw, 0, sizeof(struct s3c24xx_spi));
	
	hw->master = spi_master_get(master);	/*儲存master結構體,同時增加引用計數*/
	hw->pdata = pdata = pdev->dev.platform_data;	/*擷取s3c2410_spi_info結構體指針*/
	hw->dev = &pdev->dev;					/*儲存platform裝置的dev*/

	if (pdata == NULL) {
		dev_err(&pdev->dev, "No platform data supplied\n");
		err = -ENOENT;
		goto err_no_pdata;
	}

	platform_set_drvdata(pdev, hw);	/*讓platform_devuce.dev.driver_data 指向 s3c24xx_spi*/
	init_completion(&hw->done);		/*初始化completion*/

	/* setup the master state. */ /*填充master結構體的兩個字段*/
	
	master->num_chipselect = hw->pdata->num_cs;
	master->bus_num = pdata->bus_num;

	/* setup the state for the bitbang driver */	/*填充bitbang字段*/

	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->bitbang.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);	/*擷取IO資源*/
	if (res == NULL) {
		dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
		err = -ENOENT;
		goto err_no_iores;
	}

	hw->ioarea = request_mem_region(res->start, (res->end - res->start)+1,	/*申請IO記憶體*/
					pdev->name);

	if (hw->ioarea == NULL) {
		dev_err(&pdev->dev, "Cannot reserve region\n");
		err = -ENXIO;
		goto err_no_iores;
	}

	hw->regs = ioremap(res->start, (res->end - res->start)+1);		/*建立映射*/
	if (hw->regs == NULL) {
		dev_err(&pdev->dev, "Cannot map IO\n");
		err = -ENXIO;
		goto err_no_iomap;
	}

	hw->irq = platform_get_irq(pdev, 0);			/*擷取irq号*/
	if (hw->irq < 0) {
		dev_err(&pdev->dev, "No IRQ specified\n");
		err = -ENOENT;
		goto err_no_irq;
	}

	err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw);	/*申請spi中斷,ISR為 s3c24xx_spi_irq*/
	if (err) {
		dev_err(&pdev->dev, "Cannot claim IRQ\n");
		goto err_no_irq;
	}

	hw->clk = clk_get(&pdev->dev, "spi");	/*擷取spi時鐘*/
	if (IS_ERR(hw->clk)) {
		dev_err(&pdev->dev, "No clock for device\n");
		err = PTR_ERR(hw->clk);
		goto err_no_clk;
	}

	/* setup any gpio we can */

	if (!pdata->set_cs) {		/*沒有定義配置設定CS管腳的函數*/
		if (pdata->pin_cs < 0) {	/*pin_cs為cs管腳*/
			dev_err(&pdev->dev, "No chipselect pin\n");
			goto err_register;
		}

		err = gpio_request(pdata->pin_cs, dev_name(&pdev->dev));/*申請IO位址*/
		if (err) {
			dev_err(&pdev->dev, "Failed to get gpio for cs\n");
			goto err_register;
		}

		hw->set_cs = s3c24xx_spi_gpiocs; /*給出配置設定cs管腳函數*/
		gpio_direction_output(pdata->pin_cs, 1);	/*設定該管腳為輸出,貌似多次一舉,在probe已經調用過該函數*/  
	} else
		hw->set_cs = pdata->set_cs;

	s3c24xx_spi_initialsetup(hw);	/*spi控制器初始化*/

	/* 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:
	if (hw->set_cs == s3c24xx_spi_gpiocs)
		gpio_free(pdata->pin_cs);

	clk_disable(hw->clk);
	clk_put(hw->clk);

 err_no_clk:
	free_irq(hw->irq, hw);

 err_no_irq:
	iounmap(hw->regs);

 err_no_iomap:
	release_resource(hw->ioarea);	/*先釋放資源*/
	kfree(hw->ioarea);				/*再釋放空間*/

 err_no_iores:
 err_no_pdata:
	spi_master_put(hw->master);;	/*減少引用計數*/

 err_nomem:
	return err;
}
           
/**
 * spi_alloc_master - allocate SPI master controller
 * @dev: the controller, possibly using the platform_bus
 * @size: how much zeroed driver-private data to allocate; the pointer to this
 *	memory is in the driver_data field of the returned device,
 *	accessible with spi_master_get_devdata().
 * Context: can sleep
 *
 * This call is used only by SPI master controller drivers, which are the
 * only ones directly touching chip registers.  It's how they allocate
 * an spi_master structure, prior to calling spi_register_master().
 *
 * This must be called from context that can sleep.  It returns the SPI
 * master structure on success, else NULL.
 *
 * The caller is responsible for assigning the bus number and initializing
 * the master's methods before calling spi_register_master(); and (after errors
 * adding the device) calling spi_master_put() to prevent a memory leak.
 */
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
{
	struct spi_master	*master;

	if (!dev)
		return NULL;

	master = kzalloc(size + sizeof *master, GFP_KERNEL);
	if (!master)
		return NULL;

	device_initialize(&master->dev);
	master->dev.class = &spi_master_class;
	master->dev.parent = get_device(dev);
	spi_master_set_devdata(master, &master[1]);

	return master;
}
EXPORT_SYMBOL_GPL(spi_alloc_master);
           

   該函數首先為spi_master結構體以及s3c24xx_spi結構體配置設定了空間,同時,spi_master.dev.driver_data指向了s3c24xx_spi。

 s3c24xx_spi結構如下:

struct s3c24xx_spi {
	/* bitbang has to be first */
	struct spi_bitbang	 bitbang;
	struct completion	 done;

	void __iomem		*regs;
	int			 irq;
	int			 len;
	int			 count;

	void			(*set_cs)(struct s3c2410_spi_info *spi,
					  int cs, int pol);

	/* data buffers */
	const unsigned char	*tx;
	unsigned char		*rx;

	struct clk		*clk;
	struct resource		*ioarea;
	struct spi_master	*master;
	struct spi_device	*curdev;
	struct device		*dev;
	struct s3c2410_spi_info *pdata;
};
           

   接着執行了該條語句:

       hw->pdata = pdata = pdev->dev.platform_data;    

  NOTE:在這裡擷取platform_device.dev.platform_data,也就是平台裝置的相關資料,而在4.1小結中的arch/arm/plat-s3c24XX/devs.c檔案中并沒有發現platform_data的身影,是以這正式需要我們移植的地方。

  随後初始化了completion,這個東東将用于實作同步I/O,詳見下文。之後,為master定義了setup方法,為bitbang定義了3個方法。

  接着擷取了一系列的資源,同時注冊了中斷服務程式。接着調用s3c24xx_spi_initialsetup初始化控制器。我們來看下該函數。

  該函數位于下列函數位于deivers/spi/s3c24xx.c。

static void s3c24xx_spi_initialsetup(struct s3c24xx_spi *hw)
{
	/* for the moment, permanently enable the clock */

	clk_enable(hw->clk);		/*使能時鐘*/

	/* program defaults into the registers */

	writeb(0xff, hw->regs + S3C2410_SPPRE);	/*設定預分頻系數,baudrate=pclk/2/(prescaler value+1)*/
	writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN);/*使能master out keep*/
	writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON);/*master, interrupt mode*/

	if (hw->pdata) {
		if (hw->set_cs == s3c24xx_spi_gpiocs)      /*set_cs 在probe方法中設定為s3c24xx_spi_gpiocs*/
			gpio_direction_output(hw->pdata->pin_cs, 1);	/*設定該管腳為輸出,貌似多次一舉,在probe已經調用過該函數*/    
*/
		if (hw->pdata->gpio_setup)
			hw->pdata->gpio_setup(hw->pdata, 1);		
	}
}
           

注意,這裡設定了SPI0主要制器工作在master方式,使用中斷模式。

最後調用了spi_bitbang_start函數,該函數非常重要,在下一小節中單獨講解。

4.2.2 spi_bitbang_start函數

  下列函數位于drivers/spi/spi_bitbang.c

/**
 * spi_bitbang_start - start up a polled/bitbanging SPI master driver
 * @bitbang: driver handle
 *
 * Caller should have zero-initialized all parts of the structure, and then
 * provided callbacks for chip selection and I/O loops.  If the master has
 * a transfer method, its final step should call spi_bitbang_transfer; or,
 * that's the default if the transfer routine is not initialized.  It should
 * also set up the bus number and number of chipselects.
 *
 * For i/o loops, provide callbacks either per-word (for bitbanging, or for
 * hardware that basically exposes a shift register) or per-spi_transfer
 * (which takes better advantage of hardware like fifos or DMA engines).
 *
 * Drivers using per-word I/O loops should use (or call) spi_bitbang_setup,
 * spi_bitbang_cleanup and spi_bitbang_setup_transfer to handle those spi
 * master methods.  Those methods are the defaults if the bitbang->txrx_bufs
 * routine isn't initialized.
 *
 * This routine registers the spi_master, which will process requests in a
 * dedicated task, keeping IRQs unblocked most of the time.  To stop
 * processing those requests, call spi_bitbang_stop().
 */
int spi_bitbang_start(struct spi_bitbang *bitbang)
{
	int	status;

	if (!bitbang->master || !bitbang->chipselect)
		return -EINVAL;

	INIT_WORK(&bitbang->work, bitbang_work);	/*初始化工作,工作為bitbang_work*/
	spin_lock_init(&bitbang->lock);				/*初始化自旋鎖*/
	INIT_LIST_HEAD(&bitbang->queue);			/*初始化連結清單頭,連結清單為雙向循環連結清單*/

	if (!bitbang->master->transfer)	/*master的transfer方法沒有定義過*/
		bitbang->master->transfer = spi_bitbang_transfer;	/*使用spi_bitbang_transfe方法*/
	if (!bitbang->txrx_bufs) {		/*如果bitbang沒有txrx_bufs方法,在probe函數中定義過該方法*/
		bitbang->use_dma = 0;
		bitbang->txrx_bufs = spi_bitbang_bufs;
		if (!bitbang->master->setup) {
			if (!bitbang->setup_transfer)
				bitbang->setup_transfer =
					 spi_bitbang_setup_transfer;
			bitbang->master->setup = spi_bitbang_setup;
			bitbang->master->cleanup = spi_bitbang_cleanup;
		}
	} else if (!bitbang->master->setup)	/*setup方法在probe函數中有定義*/
		return -EINVAL;

	/* this task is the only thing to touch the SPI bits */
	bitbang->busy = 0;
	bitbang->workqueue = create_singlethread_workqueue(		/*建立工作隊列*/
			dev_name(bitbang->master->dev.parent));
	if (bitbang->workqueue == NULL) {
		status = -EBUSY;
		goto err1;
	}

	/* driver may get busy before register() returns, especially
	 * if someone registered boardinfo for devices
	 */
	status = spi_register_master(bitbang->master);	/*注冊spi控制器*/
	if (status < 0)
		goto err2;

	return status;

err2:
	destroy_workqueue(bitbang->workqueue);
err1:
	return status;
}
EXPORT_SYMBOL_GPL(spi_bitbang_start);
           

  在這裡,定義了控制器的transfer方法為spi_bitbang_transfer。建立了一個工作隊列和一個工作bitbang_work,同時建立了一個連結清單。這些東東的作用将在後面介紹。

  最後,調用了spi_register_master函數,該函數将完成SPI控制器的注冊,其中還牽涉到spi_device的注冊。是以該函數也非常重要。我們來看看這個函數

  下列函數位于drivers/spi/spi_bitbang.c

/**
 * spi_register_master - register SPI master controller
 * @master: initialized master, originally from spi_alloc_master()
 * Context: can sleep
 *
 * SPI master controllers connect to their drivers using some non-SPI bus,
 * such as the platform bus.  The final stage of probe() in that code
 * includes calling spi_register_master() to hook up to this SPI bus glue.
 *
 * SPI controllers use board specific (often SOC specific) bus numbers,
 * and board-specific addressing for SPI devices combines those numbers
 * with chip select numbers.  Since SPI does not directly support dynamic
 * device identification, boards need configuration tables telling which
 * chip is at which address.
 *
 * This must be called from context that can sleep.  It returns zero on
 * success, else a negative error code (dropping the master's refcount).
 * After a successful return, the caller is responsible for calling
 * spi_unregister_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;
	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)
		return -EINVAL;

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

	/* 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)" : "");

	/* populate children from any spi device tables */
	scan_boardinfo(master);
	status = 0;
done:
	return status;
}
EXPORT_SYMBOL_GPL(spi_register_master);
           

   該函數中,執行了相關的檢查,然後注冊了master裝置,随後調用了scan_boardinfo。函數如下:

   下列函數位于drivers/spi/spi.c

/* FIXME someone should add support for a __setup("spi", ...) that
 * creates board info from kernel command lines
 */

static void scan_boardinfo(struct spi_master *master)
{
    struct boardinfo    *bi;

    mutex_lock(&board_lock);
    /*以board_list為連結清單頭,周遊所有的boardinfo結構,連結清單由spi_register_board_info添加*/
    list_for_each_entry(bi, &board_list, list) {    
        struct spi_board_info    *chip = bi->board_info;
        unsigned        n;
        /*周遊該boardinfo指向的spi_board_info數組*/
        for (n = bi->n_board_info; n > 0; n--, chip++) {
            if (chip->bus_num != master->bus_num) /*通過bus_num對spi裝置和master進行比對*/
                continue;
            /* NOTE: this relies on spi_new_device to
             * issue diagnostics when given bogus inputs
             */
             /*執行到此,表示比對完成,SPI裝置由該SPI接口來控制,開始建立spi_device*/
            (void) spi_new_device(master, chip);
        }
    }
    mutex_unlock(&board_lock);
}
           

  NOTE:這個函數通過boardinfo周遊的spi_board_info數組,而spi_board_info是在核心初始化過程中由spi_register_board_info進行注冊的,在

               linux/arch/arm/mach-s3c2440/mach-smdk2440.c中并沒有調用過該函數,是以這也是需要移植的地方。

  S3C2440共有兩個接口:spi0和spi1。chip->bus_num表示該裝置使用哪個spi接口,而master->bus_num正好表示了目前的接口。

  該函數中,周遊spi_board_info,通過bus_num完成SPI裝置和SPI控制器的比對,比對成功則開始建立spi_device裝置,該過程通過調用spi_new_device實作。我們接着看下這個函數。

  下列函數位于drivers/spi/spi.c

/**
 * spi_new_device - instantiate one new SPI device
 * @master: Controller to which device is connected
 * @chip: Describes the SPI device
 * Context: can sleep
 *
 * On typical mainboards, this is purely internal; and it's not needed
 * after board init creates the hard-wired devices.  Some development
 * platforms may not be able to use spi_register_board_info though, and
 * this is exported so that for example a USB or parport based adapter
 * driver could add devices (which it would learn about out-of-band).
 *
 * Returns the new device, or NULL.
 */
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);	/*配置設定spi_device結構,并初始化一些字段*/
	if (!proxy)
		return NULL;

	WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));
	/*從spi_board_info擷取SPI從裝置的參數*/
	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;
}
EXPORT_SYMBOL_GPL(spi_new_device);
 
           

   首先,建立了spi_device結構,讓後通過闆級資訊spi_board_info将SPI從裝置的相關資訊複制給spi_device結構,進而完成了spi_device結構的定義,最後調用spi_add_device,完成spi_device的注冊。

   看下spi_add_device函數,該函數位于drivers/spi/spi.c

/**
 * spi_add_device - Add spi_device allocated with spi_alloc_device
 * @spi: spi_device to register
 *
 * Companion function to spi_alloc_device.  Devices allocated with
 * spi_alloc_device can be added onto the spi bus with this function.
 *
 * Returns 0 on success; negative errno on failure
 */
int spi_add_device(struct spi_device *spi)
{
	static DEFINE_MUTEX(spi_add_lock);
	struct device *dev = spi->master->dev.parent;
	int status;

	/* Chipselects are numbered 0..max; validate. */
	if (spi->chip_select >= spi->master->num_chipselect) {
		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);

	if (bus_find_device_by_name(&spi_bus_type, NULL, dev_name(&spi->dev))
			!= NULL) {
		dev_err(dev, "chipselect %d already in use\n",
				spi->chip_select);
		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->master->setup(spi);	/*調用setup方法,即s3c24xx_spi_setup函數*/
	if (status < 0) {
		dev_err(dev, "can't %s %s, status %d\n",
				"setup", dev_name(&spi->dev), status);
		goto done;
	}

	/* Device may be bound to an active driver when this returns */
	status = device_add(&spi->dev);        /*注冊SPI_device*/
	if (status < 0)
		dev_err(dev, "can't %s %s, status %d\n",
				"add", 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;
}
EXPORT_SYMBOL_GPL(spi_add_device);
           

  在注冊spi_device之前,調用了master的setup方法,該方法又将調用s3c24xx_spi_setupxfer和s3c24xx_spi_chipsel函數。

  s3c24xx_spi_setupxfer函數計算預分頻系數并寫入寄存器。

  s3c24xx_spi_chipsel函數用于禁止或使能CS信号。當使能CS信号時,要設定控制寄存器。這裡調用是禁止CS信号。

  下列代碼位于 deivers/spi/s3c24xx.c。

#define SPCON_DEFAULT (S3C2410_SPCON_MSTR | S3C2410_SPCON_SMOD_INT)
#define SPPIN_DEFAULT (S3C2410_SPPIN_KEEP)

static inline struct s3c24xx_spi *to_hw(struct spi_device *sdev)
{
	return spi_master_get_devdata(sdev->master);
}

static void s3c24xx_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol)
{
	gpio_set_value(spi->pin_cs, pol);
}

static void s3c24xx_spi_chipsel(struct spi_device *spi, int value)
{
	struct s3c24xx_spi *hw = to_hw(spi);
	unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0;
	unsigned int spcon;

	switch (value) {
	case BITBANG_CS_INACTIVE:	/*CS無效時*/
		hw->set_cs(hw->pdata, spi->chip_select, cspol^1);/*即調用s3c24xx_spi_gpiocs使CS無效*/
		break;

	case BITBANG_CS_ACTIVE:		/*CS有效時*/
		spcon = readb(hw->regs + S3C2410_SPCON); /*擷取目前SPCON寄存器的值*/

		/*開始設定工作模式*/
		if (spi->mode & SPI_CPHA)	
			spcon |= S3C2410_SPCON_CPHA_FMTB;
		else
			spcon &= ~S3C2410_SPCON_CPHA_FMTB;

		if (spi->mode & SPI_CPOL)
			spcon |= S3C2410_SPCON_CPOL_HIGH;
		else
			spcon &= ~S3C2410_SPCON_CPOL_HIGH;
		/*激活時鐘sck輸出*/
		spcon |= S3C2410_SPCON_ENSCK;

		/* write new configration */

		writeb(spcon, hw->regs + S3C2410_SPCON); /*儲存新的配置*/
		hw->set_cs(hw->pdata, spi->chip_select, cspol);/*即調用s3c24xx_spi_gpiocs使CS有效*/

		break;
	}
}

static int s3c24xx_spi_setupxfer(struct spi_device *spi,
				 struct spi_transfer *t)
{
	struct s3c24xx_spi *hw = to_hw(spi);
	unsigned int bpw;
	unsigned int hz;
	unsigned int div;

	/*沒有transfer,則使用spi_device進行配置*/
	bpw = t ? t->bits_per_word : spi->bits_per_word;
	hz  = t ? t->speed_hz : spi->max_speed_hz;

	if (bpw != 8) {
		dev_err(&spi->dev, "invalid bits-per-word (%d)\n", bpw);
		return -EINVAL;
	}

	div = clk_get_rate(hw->clk) / hz;

	/* is clk = pclk / (2 * (pre+1)), or is it
	 *    clk = (pclk * 2) / ( pre + 1) */
	/*計算預分頻系數*/
	div /= 2;

	if (div > 0)
		div -= 1;

	if (div > 255)	/*隻有8位*/
		div = 255;

	dev_dbg(&spi->dev, "setting pre-scaler to %d (hz %d)\n", div, hz);
	writeb(div, hw->regs + S3C2410_SPPRE);	/*設定預分頻系數*/

	spin_lock(&hw->bitbang.lock); /*自旋鎖加鎖*/
	if (!hw->bitbang.busy) {		/*如果不忙*/
		hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);/*即調用s3c24xx_spi_chipsel使CS無效*/
		/* need to ndelay for 0.5 clocktick ? */
	}
	spin_unlock(&hw->bitbang.lock);

	return 0;
}

/* the spi->mode bits understood by this driver: */
#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)

static int s3c24xx_spi_setup(struct spi_device *spi) /*maser.setup方法*/
{
	int ret;

	if (!spi->bits_per_word)
		spi->bits_per_word = 8;	/*沒有設定則使用8位*/

	if (spi->mode & ~MODEBITS) {	/*檢查mode是否有錯*/
		dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",
			spi->mode & ~MODEBITS);
		return -EINVAL;
	}

	ret = s3c24xx_spi_setupxfer(spi, NULL);
	if (ret < 0) {
		dev_err(&spi->dev, "setupxfer returned %d\n", ret);
		return ret;
	}

	dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n",
		__func__, spi->mode, spi->bits_per_word,
		spi->max_speed_hz);

	return 0;
}
           

至此,在probe函數中, 由spi_bitbang_start調用所引起的一系列函數調用都已講解完畢。下面總結下整個調用過程:

基于S3C2440的嵌入式Linux驅動——SPI子系統解讀(二)

  可以看到,調用spi_bitbang_start以後,spi_master和spi_device都将被注冊到核心中。

  下面來看下platform driver的其他幾個方法。

4.2.3 remove,suspend以及resume方法

static int __exit s3c24xx_spi_remove(struct platform_device *dev)
{
	struct s3c24xx_spi *hw = platform_get_drvdata(dev);

	platform_set_drvdata(dev, NULL);

	spi_unregister_master(hw->master);	/*登出spi主要制器*/

	clk_disable(hw->clk);			/*禁止時鐘*/
	clk_put(hw->clk);			/*釋放CLK*/

	free_irq(hw->irq, hw);			/*登出IRQ*/
	iounmap(hw->regs);			/*解除映射*/

	if (hw->set_cs == s3c24xx_spi_gpiocs)
		gpio_free(hw->pdata->pin_cs);	/*釋放用于cs的gpio*/

	release_resource(hw->ioarea);
	kfree(hw->ioarea);					

	spi_master_put(hw->master);		/*減少master引用計數*/
	return 0;
}


#ifdef CONFIG_PM	/*如果定義了電源管理*/

static int s3c24xx_spi_suspend(struct platform_device *pdev, pm_message_t msg)
{
	struct s3c24xx_spi *hw = platform_get_drvdata(pdev);

	if (hw->pdata && hw->pdata->gpio_setup)
		hw->pdata->gpio_setup(hw->pdata, 0);

	clk_disable(hw->clk);
	return 0;
}

static int s3c24xx_spi_resume(struct platform_device *pdev)
{
	struct s3c24xx_spi *hw = platform_get_drvdata(pdev);

	s3c24xx_spi_initialsetup(hw);
	return 0;
}

#else
#define s3c24xx_spi_suspend NULL
#define s3c24xx_spi_resume  NULL
#endif
           

  至此,master 驅動的大體結構都已分析完畢,随後第三篇文章将介紹spi裝置驅動。

繼續閱讀