Add a kernel driver module (mydriver.c+Konfig+Makefile)

Previously, when registering SPIDRIVER, the probe function used
gpio_pmodoled_setup_cdev()
gpio_pmodoled_init_gpio()
gpio_pmodoled_disp_init()
Now let's write these functions.

Write gpio_pmodoled_setup_cdev() first.

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->spi = spi;

	*dev_id = MKDEV(MAJOR(gpio_pmodoled_dev_id), cur_minor++);
	status = cdev_add(&dev->cdev, *dev_id, 1);
	if(status < 0) {
		return status;
	}
	
	/* Add Device node in system */
	device = device_create(gpio_pmodoled_class, NULL,
					*dev_id, NULL,
					"%s", dev->name);
	if(IS_ERR(device)) {
		status = PTR_ERR(device);
		dev_err(&spi->dev, "failed to create device node %s, err %d\n",
				dev->name, status);
		cdev_del(&dev->cdev);
	}
	
	return status;
}

When a caller calls a function, it passes some parameters.
dev is a struct gpio_pmodoled_device pointer that executes a PMOD device description block.
dev_id is a dev_t pointer to a DEVID description block.
SPI is a struct spi_device pointer to a SPIDEVICE device description block.

First initialize the CDEV description block.
The PMOD description block contains a CDEV description block.This is the description block that we want to use.
In addition, a fops is associated with the CDEV.
We have defined a global variable gpio_pmodoled_cdev_fops, which is a struct file_operations data variable, not a pointer.
cdev_init() is responsible for initializing the CVD description block and registering it with the kernel.

Fill in the CDEV description block,
cdev.owner,cdev.ops.

Fill in the PMOD description block,
spi.

Generate DEV_ID.
MAJOR() extracts MAJOR code from dev_id.
MKDEV() is responsible for combining major code and minor code into dev_id.

Register CDEV.
cdev_add() is responsible for associating the CDEV description block with dev_id and registering it with the kernel.Returns the status value.
Determine the status of the return,
If illegal, return directly.
If legal, continue to execute.

Generate device_node in the system.
device_create() is responsible for registering the device that DEV_ID points to with the kernel by associating it with a CLASS description block.Returns a struct device pointer to a DEVICE description block.
Assign the returned pointer to the device, which is a struct device type pointer to the DEVICE description block, followed by a device as the handle.

Judges the pointer validity of the device.
If illegal, set the status, which you will need to use when returning later.Then print the error information and log off the CDEV from the system.
If legal, continue to execute.

So far, it's all done.Direct return status.

Write gpio_pmodoled_init_gpio()

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);
		if(!status) {
			dev_err(&dev->spi->dev, "!! gpio_is_valid for GPIO %d, %s FAILED!, status: %d\n",
					gpio_pmodoled_ctrl[i].gpio, gpio_pmodoled_ctrl[i].label, status);
			goto gpio_invalid;
		}
	}

	status = gpio_request_array(gpio_pmodoled_ctrl, ARRAY_SIZE(gpio_pmodoled_ctrl));
	if(status) {
		dev_err(&dev->spi->dev, "!!  gpio_request_array FAILED!\n");
		dev_err(&dev->spi->dev, "          status is: %d\n", status);
		gpio_free_array(gpio_pmodoled_ctrl, 4);
		goto gpio_invalid;
	}

gpio_invalid:
	return status;
}

When the caller calls the function, it passes some parameters.
dev is a struct gpio_pmodoled_device pointer to the PMOD device description block.

First, define a temporary array, gpio_pmodoled_ctrl, which is an array of struct gpio-type variables.

Determines the validity of the GPIO description block defined in the array.
The cycle structure is used to check one by one.
gpio_is_valid() is responsible for checking if the GPIO number given in the GPIO description block is valid.Returns the status value.
Determines whether the returned status is legal.
If illegal, print the error message.Jump out of the loop and enter the exit processing stack gpio_invalid.
If legal, continue to execute.

At the end of the loop, register GPIO with the kernel.
gpio_request_array() is responsible for registering GPIO with the kernel and GPIO description blocks in GPIO_ARRAY into the system one by one.Returns the status value.
Determine the status value returned,
If illegal, print the error message and enter the exit processing stack gpio_invalid.
If legal, continue to execute.

So far, it's all done.Direct return status.

Write gpio_pmodoled_disp_init()

static void gpio_pmodoled_disp_init(struct gpio_pmodoled_device *dev) 
{
	int status;
	uint8_t wr_buf[20];
	
	// We are going to be sending commands
	// so clear the data/cmd bit
	gpio_set_value(dev->iDC, OLED_CONTROLLER_CMD);

	// Start by turning VDD on and wait for the power to come up
	gpio_set_value(dev->iVDD, 0);
	msleep(1);

	// Display off Command
	wr_buf[0] = OLED_DISPLAY_OFF;
	status = spi_write(dev->spi, wr_buf, 1);
	
	/* Bring Reset Low and then High */
	gpio_set_value(dev->iRES, 1);
	msleep(1);
	gpio_set_value(dev->iRES, 0);
	msleep(1);
	gpio_set_value(dev->iRES, 1);

	// Send the set charge pump and set precharge period commands
	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);

	/* Turn on VCC and wait 100ms */
	gpio_set_value(dev->iVBAT, 0);
	msleep(100);

	/* Set Display COntrast */
	wr_buf[0] = OLED_CONTRAST_CTRL;
	wr_buf[1] = 0x0F;
	
	/* Invert the display */
	wr_buf[2] = OLED_SET_SEGMENT_REMAP; // Remap Columns
	wr_buf[3] = OLED_SET_COM_DIR;	// Remap Rows

	// Select sequential COM configuration
	wr_buf[4] = OLED_SET_COM_PINS;
	wr_buf[5] = 0x00;
	wr_buf[6] = 0xC0;
	wr_buf[7] = 0x20;
	wr_buf[8] = 0x00;

	// Turn on Display
	wr_buf[9] = OLED_DISPLAY_ON;
	
	status = spi_write(dev->spi, wr_buf, 10);
}

When the caller calls the function, it passes some parameters.
dev is a struct gpio_pmodoled_device pointer to a PMOD description block.

First set up GPIO to brush the data to the hardware.
gpio_set_value() is responsible for setting the data to the hardware pin of the specified GPIO number.
msleep(), sleep waiting.

Then configure wr_buf, use SPIDEVICE, generate the SPI write sequence, and brush the data to the hardware.
spi_write() is responsible for the data in wr_buf, generating the SPI writing sequence one by one, and brushing it to the hardware.

Set GPIO to generate wave sequence.
With gpio_set_value() and msleep(), the desired waveform can be generated.

Then configure wr_buf, use SPIDEVICE, generate the SPI write sequence, and brush the data to the hardware.
spi_write() is responsible for the data in wr_buf, generating the SPI writing sequence one by one, and brushing it to the hardware.

Set GPIO to generate wave sequence.
With gpio_set_value() and msleep(), the desired waveform can be generated.

Then configure wr_buf, use SPIDEVICE, generate the SPI write sequence, and brush the data to the hardware.
spi_write() is responsible for the data in wr_buf, generating the SPI writing sequence one by one, and brushing it to the hardware.

Posted by dookie on Mon, 29 Apr 2019 15:40:37 -0700