Compiling and Testing PWM Capture Driver Based on NXP LS1028A Platform

In practical application scenarios, a PWM output is required to control the rotation of a stepper motor, and an IO pin controls the direction of rotation. However, one issue arises: how much does the stepper motor advance or retreat? The system cannot obtain the actual distance the motor has moved, so a PWM capture function is needed to achieve this.

Driver Compilation:

#include <linux/init.h> 
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/export.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define DRIVER_NAME     "pwm_in_driver"
#define 
                        OK1028IRQ_CNT   1 /*device number and quantity*/
#define OK1028IRQ_NAME  "pwm_in"
#define DTS_A_GPIO      "encoder,A_gpio"
#define DTS_B_GPIO      "encoder,B_gpio"
#define DTS_OF_NAME     "forlinx,pwm_in"
#define DEBUG           0    /*debug information 1 on 0 off*/
#define FOR_INFO(fmt, arg...)   /*debug information definition*/              \
({                                  \
    pr_info("FORLINX: (%s, %d): " fmt, __func__, __LINE__, ##arg);  \
})
#define FOR_ERR(fmt, arg...)                        \
({                                  \
    pr_err("FORLINX: (%s, %d): " fmt, __func__, __LINE__, ##arg);   \
})                                  \
struct irq_keydesc{
        int gpio; /*gpio号*/
        int irqnum; /*interrupt number*/
        char name[10]; /* Name */
        irqreturn_t (*handler)(int, void *); /*Interrupt service function*/
};
struct gpioirq_dev{
        dev_t   devid; /*device number*/
        struct cdev cdev; /* character device */
        struct class *class; /* class */
        struct cdev cdev; /* device */
        int major; /* main device number */
        int minor; /* secondary device number */
        struct device_node *nd; /* device node */
        struct device *dev;  /*device tree node*/
        struct irq_keydesc gpio_pwm_A; /*phase A interrupt IO*/
         struct irq_keydesc gpio_pwm_A; /*phase B interrupt IO*/
        atomic_t value; /*passing data at the user level*/
        long int number; /*count times*/
};
static struct gpioirq_dev gpioirq; /* irq device */
/* @description : interrupt service function.
 * @param - irq : interrupt signal
 * @param - dev_id : device structure
 * @return : interrupt execution results
 */
static irqreturn_t key_handler_A(int irq, void *dev_id)
{
        int gpio_valueA=3,gpio_valueB=3;
        gpio_valueA = gpio_get_value(gpioirq.gpio_pwm_A.gpio);
        gpio_valueB = gpio_get_value(gpioirq.gpio_pwm_B.gpio);
        if ((gpio_valueA == 0 && gpio_valueB == 1) || (gpio_valueA == 1 && gpio_valueB == 0)) {
                gpioirq.number ++;
        }
        else if ((gpio_valueA == 0 && gpio_valueB == 0) || (gpio_valueA == 1 && gpio_valueB == 1)) {
                gpioirq.number --;
        }
        #if DEBUG
        FOR_INFO("key_handler_A gpio_valueA = %d gpio_valueB = %d number = %ld\r\n", 
                        gpio_valueA, gpio_valueB,gpioirq.number);
        #endif
        return IRQ_RETVAL(IRQ_HANDLED);
}
static irqreturn_t key_handler_B(int irq, void *dev_id)
{
        int gpio_valueA=3,gpio_valueB=3;
        gpio_valueA = gpio_get_value(gpioirq.gpio_pwm_A.gpio);
        gpio_valueB = gpio_get_value(gpioirq.gpio_pwm_B.gpio);
        if ((gpio_valueB == 0 && gpio_valueA == 0) || (gpio_valueB == 1 && gpio_valueA == 1)) {
                gpioirq.number ++;
        }
        else if ((gpio_valueB == 0 && gpio_valueA == 1) || (gpio_valueB == 1 && gpio_valueA == 0)) {
                gpioirq.number --;
        }
        #if DEBUG
        FOR_INFO("key_handler_B gpio_valueA = %d gpio_valueB = %d number = %ld\r\n", 
                        gpio_valueA, gpio_valueB,gpioirq.number);
        #endif
        return IRQ_RETVAL(IRQ_HANDLED);
}
/*
 * @description : open device
 * @param – inode : the inode passed to the driver
 * @param - filp : for device files, the file structure has a member variable called private_data.
 * Normally private_data is pointed to the device structure when open.
 * @return : 0 success; other Failure
 */
static int ok1028irq_open(struct inode *inode, struct file *filp)
{
        filp->private_data = &gpioirq; /* set private data */
        #if DEBUG
        FOR_INFO("This is ok1028irq_open\r\n");
        #endif
        return 0;
}
/*
 * @description : Read data from device
 * @param – filp : Device file to open (file descriptor)
 * @param – buf : Data buffer returned to user space
 * @param - cnt : Length of data to be read
 * @param – offt : Offset from the beginning of the file
 * @return : Number of bytes read. A negative value indicates a read failure
 */
static ssize_t ok1028irq_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
        static long int value = 0;
        struct gpioirq_dev *dev = (struct gpioirq_dev *)
            filp->private_data;
        atomic_set(&dev->value, dev->number);
        value = atomic_read(&dev->value);
        if(copy_to_user(buf, &value, sizeof(value)) != 0 )
        {
                return -EFAULT; // copy failed, error code returned
        }
        #if DEBUG
        FOR_INFO("This is ok1028irq_read,value = %ld ,dev->number = %ld\r\n",value,dev->number);
        #endif
    return 0;  // returns the number of bytes copied
}
/*
 * @description : Write data to the device
 * @param – filp : Device file to open (file descriptor)
 * @param – buf : User-space data buffer to be written
 * @param - count : Length of data to be written
 * @param – ppos : Offset from the beginning of the file
 * @return : Number of data bytes written. If non-zero, read failed
 */
static ssize_t ok1028irq_write(struct file *filp, const char __user *buf, 
size_t count, loff_t *ppos)
{
    char kernel_buf[1] = {0};
    int data=0;
    // Copy data from user space to kernel space
    if (copy_from_user(kernel_buf, buf, 1) != 0) {
        return -EFAULT;
    }
    // convert strings to long integers
    if (kstrtoint(kernel_buf, 10, &data) < 0) { return -EINVAL; } // printk("kernel_buf = %s, data = %d", kernel_buf, data); if (data == 1) { // clear the count of num gpioirq.number = 0; #if DEBUG FOR_INFO("This is ok1028irq_write,kernel_buf = %s, data = %d\r\n, dev->number = %ld",kernel_buf,data,gpioirq.number);
        #endif
        data=0;
    }
    return 0; // returns the number of bytes written
}
/* device operation function */
static struct file_operations pwm_in_fops = {
        .owner = THIS_MODULE,
        .open = ok1028irq_open,
        .read = ok1028irq_read,
        .write = ok1028irq_write,
};
/*
  * @description : 	Drive entry function
  * @param : No
  * @return : No
  */
static int my_gpio_probe(struct platform_device *pdev){
        int ret = 0;
    
        /*GPIO initialization function*/
        struct device *dev = &pdev->dev;
    struct device_node *dev_node = dev->of_node; 
        /* 1. Build device number */
        if (gpioirq.major) {
                gpioirq.devid = MKDEV(gpioirq.major, 0);
                register_chrdev_region(gpioirq.devid, OK1028IRQ_CNT,
                                        OK1028IRQ_NAME);
        }
        else {
                alloc_chrdev_region(&gpioirq.devid, 0, OK1028IRQ_CNT,
                                        OK1028IRQ_NAME);
                gpioirq.major = MAJOR(gpioirq.devid);
                gpioirq.minor = MINOR(gpioirq.devid);
        }
        /* 2. register the character device */
        cdev_init(&gpioirq.cdev, &pwm_in_fops);  //operation function
        cdev_add(&gpioirq.cdev, gpioirq.devid, OK1028IRQ_CNT);
        /* 3. create a class */
        gpioirq.class = class_create(THIS_MODULE, OK1028IRQ_NAME);
        if (IS_ERR(gpioirq.class)) {
                return PTR_ERR(gpioirq.class);
        }
        /* 4. create device */
        gpioirq.device = device_create(gpioirq.class, NULL,
                gpioirq.devid, NULL, OK1028IRQ_NAME);
        if (IS_ERR(gpioirq.device)) {
                return PTR_ERR(gpioirq.device);
        }
        /*set gpio.name*/
        memset(gpioirq.gpio_pwm_A.name, 0, sizeof(gpioirq.gpio_pwm_A.name));
        sprintf(gpioirq.gpio_pwm_A.name, "PWM_A_INT");
        memset(gpioirq.gpio_pwm_B.name, 0, sizeof(gpioirq.gpio_pwm_B.name));
        sprintf(gpioirq.gpio_pwm_B.name, "PWM_B_INT");
        /*acquire device tree node information*/
        gpioirq.gpio_pwm_A.gpio = of_get_named_gpio(dev_node, DTS_A_GPIO, 0);  
        gpioirq.gpio_pwm_B.gpio = of_get_named_gpio(dev_node, DTS_B_GPIO, 0);
        #if DEBUG
        FOR_INFO("gpio_pwm_A num : %d\n",gpioirq.gpio_pwm_A.gpio);
                FOR_INFO("gpio_pwm_B num : %d\n",gpioirq.gpio_pwm_B.gpio);
        #endif
        /*determine whether the GPIO is valid*/
        if (!gpio_is_valid(gpioirq.gpio_pwm_A.gpio)) {  
        FOR_ERR("Invalid INT gpio: %d\n", gpioirq.gpio_pwm_A.gpio);
        return -EBADR;
    }
        if (!gpio_is_valid (gpioirq.gpio_pwm_B.gpio)) {
        FOR_ERR("Invalid INT gpio: %d\n", gpioirq.gpio_pwm_B.gpio);
        return -EBADR;
    }
/*require GPIO*/
        ret = gpio_request (gpioirq.gpio_pwm_A.gpio, gpioirq.gpio_pwm_A.name);  
    if (ret < 0) { 
FOR_ERR("Request PWM_A_INT GPIO failed, ret = %d\n", ret); 
gpio_free(gpioirq.gpio_pwm_A.gpio); 
ret = gpio_request(gpioirq.gpio_pwm_A.gpio, gpioirq.gpio_pwm_A.name); 
    if (ret < 0){ 
FOR_ERR("Retrying request PWM_A_INT GPIO still failed , ret = %d\n", ret); return ret; } } 
ret = gpio_request(gpioirq.gpio_pwm_B.gpio, gpioirq.gpio_pwm_B.name); 
    if (ret < 0) { FOR_ERR("Request PWM_B_INT GPIO failed, ret = %d\n", ret); 
gpio_free(gpioirq.gpio_pwm_B.gpio); 
ret = gpio_request(gpioirq.gpio_pwm_B.gpio, gpioirq.gpio_pwm_B.name); 
     if (ret < 0) { 
FOR_ERR("Retrying request PWM_B_INT GPIO still failed , ret = %d\n", ret); 
return ret; 
} } 
/*get GPIO interrupt number*/ gpioirq.gpio_pwm_A.irqnum = gpio_to_irq(gpioirq.gpio_pwm_A.gpio); 
gpioirq.gpio_pwm_B.irqnum = gpio_to_irq(gpioirq.gpio_pwm_B.gpio); 
#if DEBUG FOR_INFO("gpio_pwm_A.irqnum = %d\n", gpioirq.gpio_pwm_A.irqnum); 
FOR_INFO("gpio_pwm_B.irqnum = %d\n", gpioirq.gpio_pwm_B.irqnum); #endif /*set the GPIO to the input state*/ gpio_direction_input(gpioirq.gpio_pwm_A.gpio); 
gpio_direction_input(gpioirq.gpio_pwm_A.gpio); /* request an interrupt and register the interrupt function */ gpioirq.gpio_pwm_A.handler = key_handler_A; gpioirq.gpio_pwm_B.handler = key_handler_B; 
ret = request_irq(gpioirq.gpio_pwm_A.irqnum, gpioirq.gpio_pwm_A.handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, gpioirq.gpio_pwm_A.name, &gpioirq); if(ret < 0){ printk("irq %d request failed!\r\n", gpioirq.gpio_pwm_A.irqnum); 
return -EFAULT; } 
ret = request_irq(gpioirq.gpio_pwm_B.irqnum, gpioirq.gpio_pwm_B.handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, gpioirq.gpio_pwm_B.name, &gpioirq); 
    if(ret < 0){ printk("irq %d request failed!\r\n", gpioirq.gpio_pwm_B.irqnum); 
return -EFAULT; } return 0; } /* * @description : driver export function * @param : No * @return : No */ static int ok1028irq_exit(struct platform_device *pdev){ /*release the interrupt*/ 
ret = request_irq(gpioirq.gpio_pwm_A.irqnum, gpioirq.gpio_pwm_A.handler, free_irq(gpioirq.gpio_pwm_B.irqnum, &gpioirq); 
/*release IO*/ gpio_free(gpioirq.gpio_pwm_A.gpio); gpio_free(gpioirq.gpio_pwm_B.gpio); 
/*delete character device*/ cdev_del(&gpioirq.cdev); /* unregistered character devices */ 
unregister_chrdev_region(gpioirq.devid, OK1028IRQ_CNT); /*Destruct device*/ device_destroy(gpioirq.class, gpioirq.devid); 
/*Destruct class*/ class_destroy(gpioirq.class); return 0; } 
static const struct of_device_id of_pwm_in_match[] = { 
  { .compatible = DTS_OF_NAME }, {}, 
  }; 
static struct platform_driver ok1028_encoder = { .probe = my_gpio_probe, .remove = ok1028irq_exit, .driver = { .name = DRIVER_NAME, .of_match_table = of_pwm_in_match, .owner = THIS_MODULE, }, }; 
module_platform_driver(ok1028_encoder); 
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("forlinx_wht"); 
MODULE_DESCRIPTION("GPIO ENCODER");

Device Tree:

   pwm_in{
                compatible = "forlinx,pwm_in";
                encoder,A_gpio = <&gpio1 26 1>;
                encoder,B_gpio = <&gpio2 29 1>;
        };

Makefile Compilation:

I put the driver under drivers/input/, so just open the Makefile file in this directory and add the following:

obj-y                           += pwm_inter.o

Note: obj-y : compiled into kernel obj-m : compiled as ko module

Test APP

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#include 
int main(int argc, char *argv[])
{
        int fd;
        int ret = 0;
        char *filename;
        long int data;
        if (argc != 3) {
                printf("Error Usage!\r\n");
                printf("Usage: %s\n", argv[0]);
                printf(": Path to the file\n");
                printf(": r for read, w for write\n");
                return -1;
        }
        filename = argv[1];
        fd = open(filename, O_RDWR);
        if (fd < 0) { printf("Can't open file %s\r\n", filename); return -1; } if (argv[2][0] == 'r') { ret = read(fd, &data, sizeof(data)); if (ret < 0) { /* data read error or invalid */ printf("read err=%d\n",ret); } else { /* the data is read correctly */ if (data) /* read the data */ printf("key value = %ld\r\n", data/4); } } else if (argv[2][0] == 'w') { ret = write(fd, "1", 1); if (ret < 0){ printf("write err=%d\n",ret); } } else{ printf("Invalid action: %s\n", argv[2]); printf("Valid actions: r (read), w (write)\n"); ret = -1; } close(fd); return ret; } 

Makefile Compilation:

export CC=/home/zyh/otheruser_home/compile_user/WHT/1028/forlinx_OpenIL-v1.9-202009/sources/tools/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc
TARGET=app_pwmin
OBJS=app_pwmin.o
CFLAGS+=-c -Wall -g
LDFLAGS+= -lpthread
$(TARGET):$(OBJS)
        $(CC) $^ $(LDFLAGS) -o $@  
%.o:%.c
        $(CC) $^ $(CFLAGS) -o $@
clean:
        $(RM) *.o $(TARGET) -r
install:
        install -m 0755 $(TARGET) /usr/bin/

Drive loading test:

Copy the compiled pwm_inter.ko or kernel, along with the device tree and app to the development board. This time, we take the example of compiling into a ko module.

It is recommended that for the first test, the #define DEBUG in the driver is changed to 1 so that you can see the basic information when the driver is loaded and run.

e.g.

Driver loading

Note: My driver here is pwm_inter_copy.ko and pwm_inter.ko are the same, the names are different because of different options during testing.

Motor forward rotation printing information:

Click to reverse the print information:

How to use app:

App to read the data:

Clear drive count in the app:

After testing is complete, turn off the print information and compile the driver into the kernel for the final version test. How to compile the driver into the kernel will not be described here, as it is assumed this is already known.

An external motor from the client has been connected, which is the one seen at the beginning. The motor is driven by PWM, and an IO is used to control its forward and reverse rotation. The PWM drive script is relatively rough, so please feel free to take a look.

#!/bin/bash
echo 1 > /sys/class/pwm/pwmchip0/export
echo 1000000 > /sys/class/pwm/pwmchip0/pwm1/period
echo 500000 > /sys/class/pwm/pwmchip0/pwm1/duty_cycle
echo 376 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio376/direction
if [ "$1" == "0" ]
then
        echo 0 >  /sys/class/gpio/gpio376/value   #forward rotation
else
        echo 1 >  /sys/class/gpio/gpio376/value   #reverse rotation
fi
echo 1 > /sys/class/pwm/pwmchip0/pwm1/enable
echo 0 > /sys/class/pwm/pwmchip0/pwm1/enable
#cat /sys/class/pwm/pwmchip0/pwm1/{enable,period,duty_cycle}

Let's take a look at the complete test flow:

Start the motor to rotate forward, and then read the data:

[root@
                        LS1028
ARDB ~/pwm] # ./pwm_out.sh 0
[root@LS1028ARDB ~/pwm] # ./app_pwmin /dev/pwm_in r
key value = 200

Drive the motor to reverse, and then read the data. In order to see the difference of the data more intuitively, I reverse it twice:

[root@LS1028ARDB ~/pwm] # ./pwm_out.sh 1
[root@LS1028ARDB ~/pwm] # ./pwm_out.sh 1
[root@LS1028ARDB ~/pwm] # ./app_pwmin /dev/pwm_in r
key value = -200

It can be seen that the belt motor position is now -200, that is, 200 pulse positions have been retreated.