How to write a Platform Device/Driver - PWM Driver for Tegra2

Posted on January 19, 2014
Tags:
by Sanchayan Maity

I will get straight to the point in this tutorial and give the codes here. The explanation given in the earlier post for ADC should suffice.

Do note that this is only an example, and what you might need to do will depend on what is available and what is not. Also, the code is not exactly up to the mark as per coding standards, but, I was too excited while working and rolling this out, so. I hope this clears the idea of how to use platform device/driver. Also, just in case it is not clear to people who are starting out, the core driver and header files are being changed. This will require a kernel recompilation and updating the uImage on the module, for our driver to work.

1. Header File: http://git.toradex.com/cgit/linux-toradex.git/tree/include/linux/pwm.h?h=tegra

2. Core Driver File: http://git.toradex.com/cgit/linux-toradex.git/tree/arch/arm/mach-tegra/pwm.c?h=tegra

3. Board File: http://git.toradex.com/cgit/linux-toradex.git/tree/arch/arm/mach-tegra/board-colibri_t20.c?h=tegra

Below is the change I made to the core drive file in the tegra_pwm_probe function.


platform_set_drvdata(pdev, pwm);

/*-------------------Register our PWM device---------------------*/
// Added by Sanchayan
pwm->colibripwm_dev = platform_device_alloc("colibri_pwm", -1);
if (!pwm->colibripwm_dev){
    printk("PWM Device creation failed\n");
}
platform_set_drvdata(pwm->colibripwm_dev, pwm);
ret = platform_device_add(pwm->colibripwm_dev);
if (ret < 0) {
    printk("PWM Device addition failed\n");
    platform_device_put(pwm->colibripwm_dev);
}
/*---------------------------------------------------------------*/

mutex_lock(&pwm_lock);

In the tegra_pwm_remove function, I added the below line just before return.


platform_device_unregister(pwm->colibripwm_dev);

In the board file I commented out lines 737 to 830. This was done as these lines exported the PWM to led driver framework. The led driver framework only allowed controlling the PWM’s duty cycle and period was fixed at 19600, as you can see in those lines. Hence, this driver to allow period or frequency to be controlled as well. Also, line 1479 was commented to prevent registration of these PWM’s with the led framework.

The driver code is as follows:


#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/init.h"
#include "linux/platform_device.h"
#include "linux/fs.h"
#include "linux/errno.h"
#include "asm/uaccess.h"
#include "linux/kdev_t.h"
#include "linux/device.h"
#include "linux/cdev.h"
#include "linux/slab.h"
#include "linux/ioctl.h"
#include "linux/pwm.h"

#define ENABLE_PWM            _IOW('q', 1, pwmData *)
#define DISABLE_PWM            _IOW('q', 2, pwmData *)
#define PWM_CHANNELS        4

typedef struct
{
    unsigned int pwmChannel;
    unsigned int pwmDutyCycle;
    unsigned int pwmPeriod;
}pwmData;

struct pwm_device {
    struct list_head    node;
    struct platform_device    *pdev;
    struct platform_device *colibripwm_dev;
    const char        *label;
    struct clk        *clk;

    int            clk_enb;
    void __iomem        *mmio_base;

    unsigned int        in_use;
    unsigned int        id;
};

struct pwm_driver
{
    unsigned int period[PWM_CHANNELS];
    unsigned int pwmId[PWM_CHANNELS];
    struct pwm_device *pwmdev[PWM_CHANNELS];
};

static dev_t first;         // Global variable for the first device number
static struct cdev c_dev;   // Global variable for the character device structure
static struct class *cl;    // Global variable for the device class
static int init_result;
static struct pwm_driver *pwmDriver;

static ssize_t pwm_read(struct file* F, char *buf, size_t count, loff_t *f_pos)
{
    return -EPERM;
}

static ssize_t pwm_write(struct file* F, const char *buf, size_t count, loff_t *f_pos)
{
    return -EPERM;
}

static int pwm_open(struct inode *inode, struct file *file)
{
    return 0;
}

static int pwm_close(struct inode *inode, struct file *file)
{
    return 0;
}

static long pwm_device_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
    int retval;
    pwmData data;

    switch (cmd)
    {
        case ENABLE_PWM:
        if (copy_from_user(&data, (pwmData*)arg, sizeof(pwmData)))
        {
            return -EFAULT;
        }
        retval = pwm_config(pwmDriver->pwmdev[data.pwmChannel - 1], data.pwmDutyCycle, data.pwmPeriod);
        if (retval == 0)
        {
            retval = pwm_enable(pwmDriver->pwmdev[data.pwmChannel - 1]);
            return retval;
        }
        else
        {
            return retval;
        }
        break;

        case DISABLE_PWM:
        if (copy_from_user(&data, (pwmData*)arg, sizeof(pwmData)))
        {
            return -EFAULT;
        }
        retval = pwm_config(pwmDriver->pwmdev[data.pwmChannel - 1], 0, data.pwmPeriod);
        if (retval == 0)
        {
            pwm_disable(pwmDriver->pwmdev[data.pwmChannel - 1]);
        }
        else
        {
            return retval;
        }
        break;

        default:
            break;
    }

     return 0;
}

static int pwm_device_probe(struct platform_device *pdev)
{
    int i, ret = 0;

    pwmDriver = kzalloc(sizeof(struct pwm_driver), GFP_KERNEL);

    if (!pwmDriver)
    {
        printk(KERN_ALERT "Platform get drvdata returned NULL\n");
        return -ENOMEM;
    }

    for (i = 1; I < PWM_CHANNELS; i++)
    {
        switch (i)
        {
            case 0:
                pwmDriver->pwmdev[0] = pwm_request(0, "PWM_1");
            break;

            case 1:
                pwmDriver->pwmdev[1] = pwm_request(1, "PWM_2");
            break;

            case 2:
                pwmDriver->pwmdev[2] = pwm_request(2, "PWM_3");
            break;

            case 3:
                pwmDriver->pwmdev[3] = pwm_request(3, "PWM_4");
            break;

            default:
                break;
        }
        if (IS_ERR(pwmDriver->pwmdev[i])) {
            ret = PTR_ERR(pwmDriver->pwmdev[i]);
            dev_err(&pdev->dev, "unable to request PWM %d\n", i);
            goto err;
        }
    }

    platform_set_drvdata(pdev, pwmDriver);

    return 0;

err:
    if (i > 0) {
        for (i = i - 1; i >= 1; i--) {
            pwm_free(pwmDriver->pwmdev[i]);
    }
}

kfree(pwmDriver);

return ret;
}

static int pwm_device_remove(struct platform_device *pdev)
{
    int i;
    for (i = 1; i < PWM_CHANNELS; i++) {
        pwm_free(pwmDriver->pwmdev[i]);
    }
    printk("PWM Platform Device removed\n");
    return 0;
}

static struct platform_driver pwm_driver = {
    .probe = pwm_device_probe,
    .remove = pwm_device_remove,
    .driver = {
        .name = "colibri_pwm",
        .owner = THIS_MODULE,
    },
};

static struct file_operations FileOps =
{
    .owner                = THIS_MODULE,
    .open                 = pwm_open,
    .read                 = pwm_read,
    .write                = pwm_write,
    .release              = pwm_close,
    .unlocked_ioctl       = pwm_device_ioctl,
};

static int pwm_init(void)
{
    init_result = platform_driver_probe(&pwm_driver, &pwm_device_probe);

    if (init_result < 0)
    {
        printk(KERN_ALERT "PWM Platform Driver probe failed with :%d\n", init_result);
        return -1;
    }
    else
    {
        init_result = alloc_chrdev_region( &first, 0, 1, "pwm_drv" );
        if( 0 > init_result )
        {
            platform_driver_unregister(&pwm_driver);
            printk( KERN_ALERT "PWM Device Registration failed\n" );
            return -1;
        }
        if ( (cl = class_create( THIS_MODULE, "chardev" ) ) == NULL )
        {
            platform_driver_unregister(&pwm_driver);
            printk( KERN_ALERT "PWM Class creation failed\n" );
            unregister_chrdev_region( first, 1 );
            return -1;
        }

        if( device_create( cl, NULL, first, NULL, "pwm_drv" ) == NULL )
        {
            platform_driver_unregister(&pwm_driver);
            printk( KERN_ALERT "PWM Device creation failed\n" );
            class_destroy(cl);
            unregister_chrdev_region( first, 1 );
            return -1;
        }

        cdev_init( &c_dev, &FileOps );

        if( cdev_add( &c_dev, first, 1 ) == -1)
        {
            platform_driver_unregister(&pwm_driver);
            printk( KERN_ALERT "PWM Device addition failed\n" );
            device_destroy( cl, first );
            class_destroy( cl );
            unregister_chrdev_region( first, 1 );
            return -1;
        }
    }

    return 0;
}

static void pwm_exit(void)
{
    platform_driver_unregister(&pwm_driver);
    kfree(pwmDriver);
    cdev_del( &c_dev );
    device_destroy( cl, first );
    class_destroy( cl );
    unregister_chrdev_region( first, 1 );

    printk(KERN_ALERT "PWM Driver unregistered\n");
}

module_init(pwm_init);
module_exit(pwm_exit);

MODULE_AUTHOR("Sanchayan Maity");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Colibri T20 PWM Driver");

The Makefile for the driver:


CROSS_COMPILE ?= /home/sanchayan/Toradex/gcc-linaro/bin/arm-linux-gnueabihf-
ARCH          ?= arm
SOURCE_DIR    ?= /home/sanchayan/Toradex/T20V2.0/linux-toradex

AS          = $(CROSS_COMPILE)as
LD          = $(CROSS_COMPILE)ld
CC          = $(CROSS_COMPILE)gcc
CPP         = $(CC) -E
AR          = $(CROSS_COMPILE)ar
NM          = $(CROSS_COMPILE)nm
STRIP       = $(CROSS_COMPILE)strip
OBJCOPY     = $(CROSS_COMPILE)objcopy
OBJDUMP     = $(CROSS_COMPILE)objdump

obj-m += pwm_driver.o
ccflags-y += -I$(SOURCE_DIR)/arch/arm

all:
make ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(SOURCE_DIR) M=$(PWD) modules

clean:
rm *.o *.ko *.symvers *.order

The user space application code:


#include "stdio.h"
#include "fcntl.h"
#include "linux/ioctl.h"

#define ENABLE_PWM            _IOW('q', 1, pwmData *)
#define DISABLE_PWM            _IOW('q', 2, pwmData *)
#define PWM_CHANNELS        4

typedef struct
{
    unsigned int pwmChannel;
    unsigned int pwmDutyCycle;
    unsigned int pwmPeriod;
}pwmData;

int main(void)
{
    int fd;
    int choice;
    int pwm_channel;
    int pwm_period;
    int pwm_dutycycle;
    int retVal;
    int loop = 1;
    pwmData data;

    fd = open( "/dev/pwm_drv", O_RDWR );

    if( fd < 0 )
    {
        printf("Cannot open device \t");
        printf(" fd = %d \n",fd);
        return 0;
    }

    while (loop)
    {

        printf("1: Configure PWM 2: Disable PWM 3: Exit\n");
        printf("Enter choice: \t");
        scanf("%d", &choice);

        switch(choice)
        {
            case 1:
                printf("\nEnter PWM Channel: ");
                scanf("%d", &pwm_channel);
                printf("\nEnter PWM Duty Cycle: ");
                scanf("%d", &pwm_dutycycle);
                printf("\nEnter PWM Period: ");
                scanf("%d", &pwm_period);
                data.pwmChannel = pwm_channel;
                data.pwmDutyCycle = pwm_dutycycle;
                data.pwmPeriod = pwm_period;
                retVal = ioctl(fd, ENABLE_PWM, &data);
                if (retVal < 0)
                {
                    printf("Error: %d\n", retVal);
                }
                else
                {
                    printf("Return Value: %d\n", retVal);
                }
                break;

                case 2:
                    printf("\nEnter PWM Channel: ");
                    scanf("%d", &pwm_channel);
                    printf("\nEnter PWM Duty Cycle: ");
                    scanf("%d", &pwm_dutycycle);
                    printf("\nEnter PWM Period: ");
                    scanf("%d", &pwm_period);
                    data.pwmChannel = pwm_channel;
                    data.pwmDutyCycle = pwm_dutycycle;
                    data.pwmPeriod = pwm_period;
                    retVal = ioctl(fd, DISABLE_PWM, &data);
                    if (retVal < 0)
                    {
                        printf("Error: %d\n", retVal);
                    }
                    else
                    {
                        printf("Return Value: %d\n", retVal);
                    }
                    break;

                    case 3:
                        loop = 0;
                    break;

                    default:
                        break;
        }
    }

    if( 0 != close(fd) )
    {
        printf("Could not close device\n");
    }

    return 0;
}