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了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值