來看一個具體的實例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了。