之前在注册SPIDRIVER时,我们注册了SPI的probe函数和remove函数。
我们来编写这两个函数。
static int gpio_pmodoled_spi_probe(struct spi_device *spi) {
int status = 0;
struct gpio_pmodoled_device *gpio_pmodoled_dev;
/* We rely on full duplex transfers, mostly to reduce
* per transfer overheads (by making few transfers).
*/
if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) {
status = -EINVAL;
dev_err(&spi->dev, "SPI settings incorrect: %d\n", status);
goto spi_err;
}
/* We must use SPI_MODE_0 */
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
status = spi_setup(spi);
if(status < 0) {
dev_err(&spi->dev, "needs SPI mode %02x, %d KHz; %d\n",
spi->mode, spi->max_speed_hz / 1000,
status);
goto spi_err;
}
/* Get gpio_pmodoled_device structure */
gpio_pmodoled_dev = (struct gpio_pmodoled_device*) spi->dev.platform_data;
if(gpio_pmodoled_dev == NULL) {
dev_err(&spi->dev, "Cannot get gpio_pmodoled_device.\n");
status = -EINVAL;
goto spi_platform_data_err;
}
printk(KERN_INFO SPI_DRIVER_NAME " [%s] SPI Probing\n", gpio_pmodoled_dev->name);
#ifdef CONFIG_PMODS_DEBUG
printk(KERN_INFO SPI_DRIVER_NAME " [%s] spi_probe: setup char device\n", gpio_pmodoled_dev->name);
#endif
/* Setup char driver */
status = gpio_pmodoled_setup_cdev(gpio_pmodoled_dev, &(gpio_pmodoled_dev->dev_id), spi);
if (status) {
dev_err(&spi->dev, "spi_probe: Error adding %s device: %d\n", SPI_DRIVER_NAME, status);
goto cdev_add_err;
}
/* Initialize Mutex */
mutex_init(&gpio_pmodoled_dev->mutex);
/**
* It is important to the OLED's longevity that the lines that
* control it's power are carefully controlled. This is a good
* time to ensure that the device is ot turned on until it is
* instructed to do so.
*/
#ifdef CONFIG_PMODS_DEBUG
printk(KERN_INFO SPI_DRIVER_NAME " [%s] spi_probe: initialize device\n", gpio_pmodoled_dev->name);
#endif
status = gpio_pmodoled_init_gpio(gpio_pmodoled_dev);
if(status) {
dev_err(&spi->dev, "spi_probe: Error initializing GPIO\n");
goto oled_init_error;
}
gpio_pmodoled_disp_init(gpio_pmodoled_dev);
memset(gpio_pmodoled_dev->disp_buf, 0x00, DISPLAY_BUF_SZ);
status = screen_buf_to_display(gpio_pmodoled_dev->disp_buf, gpio_pmodoled_dev);
if(status) {
dev_err(&spi->dev, "spi_probe: Error sending initial Display String\n");
goto oled_init_error;
}
return status;
oled_init_error:
if (&gpio_pmodoled_dev->cdev)
cdev_del(&gpio_pmodoled_dev->cdev);
cdev_add_err:
spi_platform_data_err:
spi_err:
return(status);
}
当syscall调用该函数时,传递一个参数,它是一个struct spi_device型的指针spi。
定义一系列指针,后续作为句柄使用。
判断SPIMASTER的模式。
spi->master->flags & SPI_MASTER_HALF_DUPLEX
如果是SPI_MASTER_HALF_DUPLEX模式,那么不能使用,打印错误信息,然后进入退出处理栈spi_err。
如果不是SPI_MASTER_HALF_DUPLEX模式,那么可以使用,继续执行。
填充SPIDEVICE描述块的成员。
mode,bits_per_word。
设置SPIDEVICE的配置信息。
spi_setup()负责对一个SPIDEVICE描述块进行设置,并返回状态值。
判断返回的状态值。
如果非法,则打印错误信息,并进入退出处理栈spi_err
如果合法,则继续执行。
利用SPIDEVICE扎到对应的PMOD设备描述块。
将spi->dev.platform_data赋值给gpio_pmodoled_dev,它是一个struct gpio_pmodoled_device型的指针。关联到PMOD的设备资源后,后续可以用gpio_pmodoled_dev作为句柄。
判断gpio_pmodoled_dev指针的合法性。
如果为NULL,则非法。打印错误信息,然后修改status,后面返回值需要使用status。然后进入退出处理栈spi_platform_data_err。
如果不为NULL,则合法。那么继续执行。
配置CDEV设备资源。
gpio_pmodoled_setup_cdev()负责配置PMOD中内含的CDEV设备资源,并关联到SPIDEVICE上。返回状态值。
判断返回的状态值。
如果非法,则打印错误信息,并进入退出处理栈cdev_add_err。
如果合法,则继续执行。
初始化PMOD设备资源中的mutex系统资源。
mutex_init()负责初始化一个mutex描述块。
初始化PMOD设备资源中用到的GPIO系统资源。
gpio_pmodoled_init_gpio()负责初始化PMOD设备描述块中使用的GPIO系统资源。返回状态值。
判断返回的状态值的合法性。
如果非法,则打印错误信息,并进入退出处理栈oled_init_error。
如果合法,则继续执行。
初始化PMOD设备资源中用到的显示缓冲资源。
gpio_pmodoled_disp_init()负责初始化PMOD设备描述块中使用的显示资源。
初始化缓冲区。
memset()负责将缓冲区清零。
把初始化好的显示缓冲区刷到硬件上。
screen_buf_to_display()负责把缓冲区flush到硬件上。返回状态值。
判断返回的状态值的合法性。
如果非法,则打印错误信息,并进入退出处理栈oled_init_error。
如果合法,则继续执行。
至此,全部执行完。正常退出,不需要进入退出处理栈。所以直接return。
再编写remove函数。
static int __devexit gpio_pmodoled_spi_remove(struct spi_device *spi)
{
int status;
struct gpio_pmodoled_device *dev;
uint8_t wr_buf[10];
dev = (struct gpio_pmodoled_device*) spi->dev.platform_data;
if(dev == NULL) {
dev_err(&spi->dev, "spi_remove: Error fetch gpio_pmodoled_device struct\n");
return -EINVAL;
}
#ifdef CONFIG_PMODS_DEBUG
printk(KERN_INFO SPI_DRIVER_NAME " [%s] spi_remove: Clearing Display\n", dev->name);
#endif
/* Clear Display */
memset(dev->disp_buf, 0, DISPLAY_BUF_SZ);
status = screen_buf_to_display(dev->disp_buf, dev);
/* Turn off display */
wr_buf[0] = OLED_DISPLAY_OFF;
status = spi_write(spi, wr_buf, 1);
if(status) {
dev_err(&spi->dev, "oled_spi_remove: Error writing to SPI device\n");
}
/* Turn off VCC (VBAT) */
gpio_set_value(dev->iVBAT, 1);
msleep(100);
/* TUrn off VDD Power */
gpio_set_value(dev->iVDD, 1);
#ifdef CONFIG_PMODS_DEBUG
printk(KERN_INFO SPI_DRIVER_NAME " [%s] spi_remove: Free GPIOs\n", dev->name);
#endif
{
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"},
};
gpio_free_array(gpio_pmodoled_ctrl, 4);
}
if(&dev->cdev) {
#ifdef CONFIG_PMODS_DEBUG
printk(KERN_INFO SPI_DRIVER_NAME " [%s] spi_remove: Destroy Char Device\n", dev->name);
#endif
device_destroy(gpio_pmodoled_class, dev->dev_id);
cdev_del(&dev->cdev);
}
cur_minor--;
printk(KERN_INFO SPI_DRIVER_NAME " [%s] spi_remove: Device Removed\n", dev->name);
return status;
}
当syscall调用remove函数时,会传递一个参数,它是一个struct spi_device型的指针。
首先定义一系列指针,后续作为句柄使用。
从SPIDEVICE设备描述块中,找到PMOD的描述块。
dev = (struct gpio_pmodoled_device*) spi->dev.platform_data;
将 dev.platform_data赋值给dev,它是一个struct gpio_pmodoled_device型的指针。我们用dev指向了PMOD的描述块,后续用dev作为句柄。
判断dev的指针合法性。
如果为NULL,则非法。打印错误信息,并直接return.
如果不为NULL,则合法。继续执行。
初始化显示缓冲区,memset()完成。
将显示缓冲区的内容flush到硬件上。
screen_buf_to_display()负责将内容flush到硬件上。
配置wr_buf,然后用SPI发送缓冲区字符。
spi_write()负责写操作,它利用一个SPIDEVICE设备资源实现写操作时序。返回状态值。
判断返回的状态值。
如果非法,则打印错误信息。由于不是致命错误。所以打印错误信息后,并不进入退出处理栈。
如果合法,则继续执行。
设置GPIO。
gpio_set_value()负责将一个在系统中注册的合法GPIO编号,设置为配置值。
msleep(),睡眠等待。
定义一个临时数组gpio_pmodoled_ctrl,它是一个struct gpio型的数组。
同是设置多个GPIO的状态。
gpio_free_array()负责将一个GPIO_ARRAY中GPIO系统资源,同时释放掉。
判断PMOD的CDEV的指针合法性。
如果不为NULL,说明CDEV仍占有内核资源,那么释放掉CDEV的系统资源。
device_destroy()负责注销CLASS描述块。它根据dev_id注销CDEV关联的CLASS描述块。
cdev_del()负责注销CDEV设备资源。它根据传入的CDEV的描述块,在设备中进行匹配查找,然后注销掉系统资源,并取消内核和CDEV描述块之间的关联。
我们定义了静态变量cur_minor。它用来记录当前系统中存在的CDEV设备数量。
修改cur_minor。
cur_minor–;
至此,全部完成。直接return status.