linux驅動(第二十七課,綜合實例,platform, cdev,spi, gpio)

來看一個具體的實例OLED驅動
首先我們確定設備的主類型,這裏爲CDEV,
然後,我們確定設備的其他類型,這裏爲PDEV,
然後,我們確定設備所需要的伺服設備的類型,這裏爲SPIDEV.
然後,我們設置設備所需要的資源。
我們需要一個BUFFER,所以這裏設置了BUF,
我們需要GPIO,這裏設置了GPIONUM。
我們需要進程互斥,這裏設置了MUTEX。
我們需要關聯到PDEV,這裏設置了PDEV的句柄。
我們需要關聯到SPIDEV,這裏設置了SPIDEV的句柄。
我們需要索引到SPIDEV,這裏設置了SPIMASTERID。
我們需要索引到CDEV,這裏設置了CDEVID。

struct gpio_pmodoled_device {
	char *name;
	/* R/W Mutex Lock */
	struct mutex mutex;
	/* Display Buffers */
	uint8_t disp_on;
	uint8_t *disp_buf;
	/* Pin Assignment */
	unsigned long iVBAT;
	unsigned long iVDD;
	unsigned long iRES;
	unsigned long iDC;
	unsigned long iSCLK;
	unsigned long iSDIN;
	unsigned long iCS;
	/* SPI Info */
	uint32_t spi_id;
	/* platform device structures */
	struct platform_device *pdev;
	/* Char Device */
	struct cdev cdev;
	struct spi_device *spi;
	dev_t dev_id;
};

首先考慮整個驅動模塊的機制性問題。
我們採用OFSTYLE,所以,platform_driver_probe是驅動加載的起點。
首先配置platform_driver,以及of_match_table。

static const struct of_device_id gpio_pmodoled_of_match[] __devinitconst = {
	{ .compatible = "dglnt,pmodoled-gpio", },
	{},
};
MODULE_DEVICE_TABLE(of, gpio_pmodoled_of_match);
static struct platform_driver gpio_pmodoled_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = gpio_pmodoled_of_match,
	},
	.probe = gpio_pmodoled_of_probe,
	.remove = __devexit_p(gpio_pmodoled_of_remove),
};
module_platform_driver(gpio_pmodoled_driver);

來看看probe.

static int __devinit gpio_pmodoled_of_probe(struct platform_device *pdev)
{
	struct gpio_pmodoled_device *gpio_pmodoled_dev;
	struct platform_device *gpio_pmodoled_pdev;
	struct spi_gpio_platform_data *gpio_pmodoled_pdata;

	struct device_node *np = pdev->dev.of_node;

	const u32* tree_info;
	int status = 0;

	/* Alloc Space for platform device structure */
	gpio_pmodoled_dev = (struct gpio_pmodoled_device*) kzalloc(sizeof(*gpio_pmodoled_dev), GFP_KERNEL);
	
	/* Alloc Graphic Buffer for device */
	gpio_pmodoled_dev->disp_buf = (uint8_t*) kmalloc(DISPLAY_BUF_SZ, GFP_KERNEL);
	
	/* Get the GPIO Pins */
	gpio_pmodoled_dev->iVBAT = of_get_named_gpio(np, "vbat-gpio", 0);
	gpio_pmodoled_dev->iVDD = of_get_named_gpio(np, "vdd-gpio", 0);
	gpio_pmodoled_dev->iRES = of_get_named_gpio(np, "res-gpio", 0); 
	gpio_pmodoled_dev->iDC = of_get_named_gpio(np, "dc-gpio", 0); 
	gpio_pmodoled_dev->iSCLK = of_get_named_gpio(np, "spi-sclk-gpio", 0); 
	gpio_pmodoled_dev->iSDIN = of_get_named_gpio(np, "spi-sdin-gpio", 0); 
	status = of_get_named_gpio(np, "spi-cs-gpio", 0);
	gpio_pmodoled_dev->iCS = (status < 0) ? SPI_GPIO_NO_CHIPSELECT : status;

	/* Get SPI Related Params */
	tree_info = of_get_property(np, "spi-bus-num", NULL);
	if(tree_info) {
		gpio_pmodoled_dev->spi_id = be32_to_cpup((tree_info));
	}

	/* Alloc Space for platform data structure */
	gpio_pmodoled_pdata = (struct spi_gpio_platform_data*) kzalloc(sizeof(*gpio_pmodoled_pdata), GFP_KERNEL);

	/* Fill up Platform Data Structure */
	gpio_pmodoled_pdata->sck = gpio_pmodoled_dev->iSCLK;
	gpio_pmodoled_pdata->miso = SPI_GPIO_NO_MISO;
	gpio_pmodoled_pdata->mosi = gpio_pmodoled_dev->iSDIN;
	gpio_pmodoled_pdata->num_chipselect = 1;
	
	/* Alloc Space for platform data structure */
	gpio_pmodoled_pdev = (struct platform_device*) kzalloc(sizeof(*gpio_pmodoled_pdev), GFP_KERNEL);
	
	/* Fill up Platform Device Structure */	
	gpio_pmodoled_pdev->name = "spi_gpio";
	gpio_pmodoled_pdev->id = gpio_pmodoled_dev->spi_id;
	gpio_pmodoled_pdev->dev.platform_data = gpio_pmodoled_pdata;
	gpio_pmodoled_dev->pdev = gpio_pmodoled_pdev;
	/* Register spi_gpio master */
	status = platform_device_register(gpio_pmodoled_dev->pdev);
	
	gpio_pmodoled_dev->name = np->name;	
	
	/* Fill up Board Info for SPI device */
	status = add_gpio_pmodoled_device_to_bus(gpio_pmodoled_dev);
	
	/* Point device node data to gpio_pmodoled_device structure */
	if(np->data == NULL)
		np->data = gpio_pmodoled_dev;
	
	if(gpio_pmodoled_dev_id == 0) {
		/* Alloc Major & Minor number for char device */
		status = alloc_chrdev_region(&gpio_pmodoled_dev_id, 0, MAX_PMODOLED_GPIO_DEV_NUM, DRIVER_NAME);
	}
	
	if(gpio_pmodoled_class == NULL) {
		/* Create Pmodoled-gpio Device Class */
		gpio_pmodoled_class = class_create(THIS_MODULE, DRIVER_NAME);
	}
	
	if(spi_drv_registered == 0) {
		/* Register SPI Driver for Pmodoled Device */
		status = spi_register_driver(&gpio_pmodoled_spi_driver);
		spi_drv_registered = 1;
	}
	
	device_num ++;
	return status;	
}

static int __init add_gpio_pmodoled_device_to_bus(struct gpio_pmodoled_device* dev) {
	struct spi_master *spi_master;
	struct spi_device *spi_device;
	int status = 0;

	spi_master = spi_busnum_to_master(dev->spi_id);
	spi_device = spi_alloc_device(spi_master);

	spi_device->chip_select = 0;
	spi_device->max_speed_hz = 4000000;
	spi_device->mode = SPI_MODE_0;
	spi_device->bits_per_word = 8;
	spi_device->controller_data = (void *) dev->iCS;
	spi_device->dev.platform_data = dev;
	strlcpy(spi_device->modalias, SPI_DRIVER_NAME, sizeof(SPI_DRIVER_NAME));

	status = spi_add_device(spi_device);
	dev->spi = spi_device;

	put_device(&spi_master->dev);
	return status;
}

從中可以看出,
pdev->dev.of_node,是用來索引到DEVICE_NODE的方法。
of_get_named_gpio(np, “vbat-gpio”, 0),是從DEVNODE中獲取GPIO的方法。
函數中創建了PMODDEV的對象,
函數中接着創建了PDEV的對象,並關聯到PMODDEV中。
函數中接着創建了PDATA的對象,並關聯到PDEV中。如下:
gpio_pmodoled_pdev->dev.platform_data = gpio_pmodoled_pdata;
然後,將PDEV註冊到了內核中。

然後,將SPIDEV註冊到內核中。使用了add_gpio_pmodoled_device_to_bus函數。
來看看這個add_gpio_pmodoled_device_to_bus函數。
這個函數中,首先通過SPIMASTERID獲取了SPIMASTER的句柄。然後通過SPIMASTER分配了SPIDEV。填充了SPIDEV對象後,使用spi_add_device函數,將SPIDEV註冊到了內核中。
注意其中的幾個關鍵的句柄配置。

spi_device->dev.platform_data = dev;
dev->spi = spi_device;

這兩句設置了回溯引用,並構成了環回。

回到probe函數中,繼續往下看。
在註冊了SPIDEV並關聯到PMODDEV之後,
將PMODDEV關聯到DEVNODE之中,

np->data = gpio_pmodoled_dev;

然後申請了CDEVID,創建了CLASS。
然後向內核註冊了SPIDRIVER。類似於platform_driver的註冊,SPIDRIVER的註冊,也是實例化一個結構體對象。
來看看SPIDRIVER的probe函數。

static struct spi_driver gpio_pmodoled_spi_driver = {
	.driver = {
		.name = SPI_DRIVER_NAME,
		.bus = &spi_bus_type,
		.owner = THIS_MODULE,
	},
	.probe = gpio_pmodoled_spi_probe,
	.remove = __devexit_p(gpio_pmodoled_spi_remove),
};

static int gpio_pmodoled_spi_probe(struct spi_device *spi) {
	int status = 0;
	struct gpio_pmodoled_device *gpio_pmodoled_dev;
	
	/* We must use SPI_MODE_0 */
	spi->mode = SPI_MODE_0;
	spi->bits_per_word = 8;

	status = spi_setup(spi);

	/* Get gpio_pmodoled_device structure */
	gpio_pmodoled_dev = (struct gpio_pmodoled_device*) spi->dev.platform_data;
	
	/* Setup char driver */
	status = gpio_pmodoled_setup_cdev(gpio_pmodoled_dev, &(gpio_pmodoled_dev->dev_id), spi);	
	
	/* Initialize Mutex */
	mutex_init(&gpio_pmodoled_dev->mutex);
	
	status = gpio_pmodoled_init_gpio(gpio_pmodoled_dev);
	
	gpio_pmodoled_disp_init(gpio_pmodoled_dev);

	return status;

}

static int gpio_pmodoled_setup_cdev(struct gpio_pmodoled_device *dev, dev_t *dev_id, struct spi_device *spi) 
{
	int status = 0;
	struct device *device;
	
	cdev_init(&dev->cdev, &gpio_pmodoled_cdev_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &gpio_pmodoled_cdev_fops;
	
	*dev_id = MKDEV(MAJOR(gpio_pmodoled_dev_id), cur_minor++);
	status = cdev_add(&dev->cdev, *dev_id, 1);
	
	/* Add Device node in system */
	device = device_create(gpio_pmodoled_class, NULL,
					*dev_id, NULL,
					"%s", dev->name);
	
	return status;
}

static int gpio_pmodoled_init_gpio(struct gpio_pmodoled_device *dev) 
{
	struct gpio gpio_pmodoled_ctrl[] = {
		{dev->iVBAT, GPIOF_OUT_INIT_HIGH, "OLED VBat"},
		{dev->iVDD, GPIOF_OUT_INIT_HIGH, "OLED VDD"},
		{dev->iRES, GPIOF_OUT_INIT_HIGH, "OLED_RESET"},
		{dev->iDC, GPIOF_OUT_INIT_HIGH, "OLED_D/C"},
	};
	int status;
	int i;

	for (i = 0; i < ARRAY_SIZE(gpio_pmodoled_ctrl); i++) {
		status = gpio_is_valid(gpio_pmodoled_ctrl[i].gpio);
	}

	status = gpio_request_array(gpio_pmodoled_ctrl, ARRAY_SIZE(gpio_pmodoled_ctrl));
	return status;
}

static void gpio_pmodoled_disp_init(struct gpio_pmodoled_device *dev) 
{
	int status;
	uint8_t wr_buf[20];
	
	gpio_set_value(dev->iDC, OLED_CONTROLLER_CMD);
	gpio_set_value(dev->iVDD, 0);
	msleep(1);
	wr_buf[0] = OLED_DISPLAY_OFF;
	status = spi_write(dev->spi, wr_buf, 1);
	gpio_set_value(dev->iRES, 1);
	msleep(1);
	gpio_set_value(dev->iRES, 0);
	msleep(1);
	gpio_set_value(dev->iRES, 1);
	wr_buf[0] = 0x8D;
	wr_buf[1] = 0x14;
	wr_buf[2] = OLED_SET_PRECHARGE_PERIOD;
	wr_buf[3] = 0xF1;
	status = spi_write(dev->spi, wr_buf, 4);
	gpio_set_value(dev->iVBAT, 0);
	msleep(100);
	wr_buf[0] = OLED_CONTRAST_CTRL;
	wr_buf[1] = 0x0F;
	wr_buf[2] = OLED_SET_SEGMENT_REMAP; // Remap Columns
	wr_buf[3] = OLED_SET_COM_DIR;	// Remap Rows
	wr_buf[4] = OLED_SET_COM_PINS;
	wr_buf[5] = 0x00;
	wr_buf[6] = 0xC0;
	wr_buf[7] = 0x20;
	wr_buf[8] = 0x00;
	wr_buf[9] = OLED_DISPLAY_ON;	
	status = spi_write(dev->spi, wr_buf, 10);
}

struct file_operations gpio_pmodoled_cdev_fops = {
	.owner = THIS_MODULE,
	.write = gpio_pmodoled_write,
	.read = gpio_pmodoled_read,
	.open = gpio_pmodoled_open,
	.release = gpio_pmodoled_close,
};

由於之前已經在內核中註冊了SPIDEV,並且和現在註冊的SPIDRIVER的名字是匹配的,所以將啓動probe,內核傳入一個SPIDEV的句柄給SPIDRIVER的probe。
由於我們之前已經配置了回溯引用,所以可以索引到PMODDEV。

//spi_device->dev.platform_data = dev;
gpio_pmodoled_dev = (struct gpio_pmodoled_device*) spi->dev.platform_data;

接着,使用gpio_pmodoled_setup_cdev函數,進一步部署CDEV。
來看看gpio_pmodoled_setup_cdev函數。
函數中,註冊了CDEV,並創建了DEVNODE。

回到SPI的Probe,繼續往下看。
在CDEV被註冊之後,繼續初始化其他資源。
接着配置了MUTEX。

然後申請GPIO。使用了gpio_pmodoled_init_gpio函數。
來看看gpio_pmodoled_init_gpio函數。它向內核申請了PMODDEV所需要的GPIO。

回到SPI的Probe,繼續往下看。
在申請了GPIO後,使用了gpio_pmodoled_disp_init函數進行IO。
在gpio_pmodoled_disp_init函數中,
使用了spi_write來產生操作時序,
同時也使用了gpio_set_value來直接控制雜散的GPIO。

回到SPI的probe,之後函數返回。

回到PDEV的probe,之後函數返回。

整個模塊的部署,在兩個probe函數中完成,也就是說,分爲兩級探測部署。
在PDEV的探測時,部署了SPIDEV,和SPIDRIVER,而在SPIDEV的探測時,又部署了CDEV,並初始化了硬件。
這樣的分級探測,是符合邏輯關係的。
UADEV的使用,需要依賴於SVDEV所提供的服務,所以在SVDEV的probe中,部署UADEV。
而SVDEV的部署,又依賴於內核啓動時,對OFDEV的創建,所以在PDEV的probe中,部署SVDEV。

我們注意到,這其中涉及多處回溯引用的配置。

np = pdev->dev.of_node;
np->data = gpio_pmodoled_dev;

dev->spi = spi_device;
spi_device->dev.platform_data = dev;
//dev->spi->dev.platform_data = dev;

gpio_pmodoled_dev->pdev = gpio_pmodoled_pdev;
gpio_pmodoled_pdev->dev.platform_data = gpio_pmodoled_pdata;
//	gpio_pmodoled_dev->pdev->dev.platform_data = gpio_pmodoled_pdata;

可以看到,回溯引用的原則,就是將句柄配置爲索引能力最強的那個對象。
這個最具體的對象類型,通常是我們設計的衍生對象。

部署好了UADEV後,用戶程序中就可以使用UADEV了。
這裏是CDEV,所以內核提供服務時,所需要的就是FOPS中的具體函數了。

來看看這些FOPS。

static int 	gpio_pmodoled_open(struct inode *inode, struct file *fp) 
{
	struct gpio_pmodoled_device *dev;

	dev = container_of(inode->i_cdev, struct gpio_pmodoled_device, cdev);
	fp->private_data = dev;

	return 0;
}

static ssize_t	gpio_pmodoled_write(struct file *fp, const char __user *buffer, size_t length, loff_t *offset) 
{
	ssize_t retval = 0;
	struct gpio_pmodoled_device *dev;
	unsigned int minor_id;
	int cnt;
	int status;

	dev = fp->private_data;
	minor_id = MINOR(dev->dev_id);

	ret = mutex_lock_interruptible(&dev->mutex));
	ret = copy_from_user(dev->disp_buf, buffer, cnt);
	status = screen_buf_to_display(dev, dev->disp_buf);
	
	ret = mutex_unlock(&dev->mutex);
	return retval;
}

static int screen_buf_to_display(struct gpio_pmodoled_device *dev,uint8_t *screen_buf) 
{
	uint32_t pg;
	int status;
	uint8_t lower_start_column = 0x00;
	uint8_t upper_start_column = 0x10;
	uint8_t wr_buf[10];

	for(pg = 0; pg < OLED_MAX_PG_CNT; pg++) {
		wr_buf[0] = OLED_SET_PG_ADDR;
		wr_buf[1] = pg;
		wr_buf[2] = lower_start_column;
		wr_buf[3] = upper_start_column;
		gpio_set_value(dev->iDC, OLED_CONTROLLER_CMD);
		status = spi_write(dev->spi, wr_buf, 4);
		
		gpio_set_value(dev->iDC, OLED_CONTROLLER_DATA);
		status = spi_write(dev->spi, (uint8_t *) (screen_buf + 
						(pg*OLED_CONTROLLER_PG_SZ)), OLED_CONTROLLER_PG_SZ);
	}
	return status;
}

在open中將FILE和PMODDEV關聯起來後,
write中就可以利用FILE索引到PMODDEV了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章