来看一个具体的实例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了。