Davinci DM6446 Linux 内核分析——davinci_pwm.c

http://www.61ic.com/Article/DaVinci/DM644X/201201/40305.html

 

/* include Linux files */
 #include <linux/config.h>
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/kernel.h>    /* printk() */
 #include <linux/slab.h>        /* kmalloc() */
 #include <linux/fs.h>        /* everything... */
 #include <linux/errno.h>    /* error codes */
 #include <linux/types.h>    /* size_t */
 #include <linux/cdev.h>        /* Used for struct cdev */
 #include <linux/dma-mapping.h>    /* For class_simple_create */
 #include <linux/interrupt.h>    /* For IRQ_HANDLED and irqreturn_t */
 #include <asm/uaccess.h>    /* for VERIFY_READ/VERIFY_WRITE/
                  copy_from_user */
 #include <linux/wait.h>
 #include <linux/devfs_fs_kernel.h>    /* for devfs */
 #include <asm/hardware/clock.h>
 #include <asm/arch/davinci_pwm.h>
 #include <asm/arch/cpu.h>
 #include <asm/semaphore.h>
 #include <asm/arch/irqs.h>
 

#define    DRIVER_NAME        "PWM"
 #define    DAVINCI_PWM_TIMEOUT    (1*HZ)
 

/*
  一、用ti的datasheet上的话来说明达芬奇平台pwm模块的特点:
     1.This PWM peripheral is basically a timer with a period counter and a first-phase
     duration comparator, where bit width of the period and first-phase duration are
     both programmable.
     2.The PWM has the following features:
     · 32-bit period counter
     · 32-bit first-phase duration counter
     · 8-bit repeat counter for one-shot operation. One-shot operation will produce
      N + 1 periods of the waveform, where N is the repeat counter value.
     · Configurable to operate in either one-shot or continuous mode.
     · One-shot operation can be triggered by the CCD VSYNC output of the video
      processing subsystem to allow any of the PWM instantiations to be used as a CCD timer.
     · Configurable PWM output pin inactive state.
     · Interrupt and EDMA synchronization events.
     · Emulation support for stop or free-run operation.
 

 二、应用程序调用过程
     1.使用open()打开;
     2.使用ioctl()设置参数。
  三、调试中可能出现的问题
     1.应用程序使用ioctl调用时,最后一个形参必须是指针,从驱动中能看出;
     2.一般没什么大难题。
 */
 

/* 用于管理达芬奇平台上的每个pwm接口 */
 struct pwm_davinci_device {
     char name[20];
     int intr_complete;
     dev_t devno;    // 字符设备号
 

    davinci_pwmregsovly regs;    // 该pwm接口的物理寄存器结构体
 

    wait_queue_head_t intr_wait;
     struct clk *pwm_clk;    // 该pwm接口的时钟
 

};
 

char *dm644x_name[] = { "PWM0_CLK", "PWM1_CLK", "PWM2_CLK" };
 char *dm646x_name[] = { "PWM0_CLK", "PWM1_CLK" };
 char *dm355_name[] = { "PWM0_CLK", "PWM1_CLK", "PWM2_CLK", "PWM3_CLK" };
 

/* Instance of the private PWM device structure */
 static struct pwm_davinci_device *pwm_dev_array[DAVINCI_PWM_MINORS];
 static DEFINE_SPINLOCK(pwm_dev_array_lock);
 

static unsigned int pwm_major = 0;
 static unsigned int pwm_minor_start = 0;
 static unsigned int pwm_minor_count = DM644X_PWM_MINORS;
 

static unsigned int pwm_device_count = 1;
 

/* For registeration of charatcer device*/
 static struct cdev c_dev;
 

/* 通过次设备号来获得该pwm接口的控制机构体 */
 struct pwm_davinci_device *pwm_dev_get_by_minor(unsigned index)
 {
     struct pwm_davinci_device *pwm_dev;
 

    spin_lock(&pwm_dev_array_lock);
     pwm_dev = pwm_dev_array[index];
     spin_unlock(&pwm_dev_array_lock);
     return pwm_dev;
 }
 

static loff_t pwm_llseek(struct file *file, loff_t offset, int whence)
 {
     return -ESPIPE;        /* Not seekable */
 }
 

 

/* 字符设备驱动的打开函数 */
 static int pwm_open(struct inode *inode, struct file *file)
 {
     unsigned int minor = iminor(inode);
     struct pwm_davinci_device *pwm_dev;
 

    pwm_dev = pwm_dev_get_by_minor(minor);
 

    /* sample configuration */
     pwm_dev->regs->per = 0xf;    // pwm总周期
 

    pwm_dev->regs->ph1d = 0xf;    // 第一相的周期
 

    pwm_dev->regs->rpt = 1;        // One-shot mode下的重复次数
 

    pwm_dev->regs->cfg |= 0x1;    // 配置成One shot mode
 

 

    pwm_dev->intr_complete = 0;
 

    return 0;
 }
 

 

/* 字符设备驱动的关闭函数, */
 static int pwm_release(struct inode *inode, struct file *file)
 {
     unsigned int minor = iminor(inode);
     struct pwm_davinci_device *pwm_dev;
 

    pwm_dev = pwm_dev_get_by_minor(minor);
 

    pwm_dev->regs->cfg &= 0xFFFFFFFC;    // 关闭pwm
 

    /* This is called when the reference count goes to zero */
     return 0;
 }
 

 

/* 字符设备驱动的ioctl函数,用于设置pwm参数,控制pwm运行模式*/
 int pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
      unsigned long arg)
 {
     int mode;
     unsigned int minor = iminor(inode);
     struct pwm_davinci_device *pwm_dev;
 

    pwm_dev = pwm_dev_get_by_minor(minor);
 

    switch (cmd) {
     /* 设置pwm模式,有两种模式,One-Shot Mode和Continuous Mode */
     case PWMIOC_SET_MODE:
         if (pwm_dev->regs->cfg & 0x20000)
             return -EBUSY;
 

        get_user(mode, (int *)arg);
         if (mode == PWM_ONESHOT_MODE) {
             pwm_dev->regs->cfg &= 0xFFFFFFFC;
             pwm_dev->regs->cfg |= 0x1;
         } else if (mode == PWM_CONTINUOUS_MODE) {
             pwm_dev->regs->cfg &= 0xFFFFFFFC;
             pwm_dev->regs->cfg |= 0x2;
         } else
             return -EINVAL;
         break;
     /*
     设置pwm周期,设置时如果pwm运行在continuous模式并且正在运行,那么必须的等待pwm一个
     周期完成后(通过产生中断来同步)再设置per寄存器
     */
     case PWMIOC_SET_PERIOD:
         get_user(mode, (int *)arg);
 

        if (mode < 0 || mode > 0xffffffff)
             return -EINVAL;
 

        // 检测pwm是否运行在continuous模式
 

        if (pwm_dev->regs->cfg & 0x2 && pwm_dev->regs->cfg & 0x20000) {
             if (mode < 7)
                 return -EINVAL;
 

            /* Enable PWM interrupts */
             pwm_dev->regs->cfg |= 0x40;
 

            /* wait for the transaction to complete */
             wait_event_timeout(pwm_dev->intr_wait,
                      pwm_dev->intr_complete,
                      DAVINCI_PWM_TIMEOUT);
 

            if (pwm_dev->intr_complete)
                 pwm_dev->regs->per = mode;
             else
                 return -1;
         } else
             pwm_dev->regs->per = mode;
         break;
        
     /*
     设置pwm第一相周期,设置时如果pwm运行在continuous模式并且正在运行,那么必须的等待pwm一个
     周期完成后(通过产生中断来同步)再设置ph1d寄存器
     */
     case PWMIOC_SET_DURATION:
         get_user(mode, (int *)arg);
 

        if (mode < 0 || mode > 0xffffffff)
             return -EINVAL;
 

        // 检测pwm是否运行在continuous模式
 

        if (pwm_dev->regs->cfg & 0x2 && pwm_dev->regs->cfg & 0x20000) {
             /* Enable PWM interrupts */
             pwm_dev->regs->cfg |= 0x40;
 

            /* wait for the transaction to complete */
             wait_event_timeout(pwm_dev->intr_wait,
                      pwm_dev->intr_complete,
                      DAVINCI_PWM_TIMEOUT);
 

            if (pwm_dev->intr_complete)
                 pwm_dev->regs->ph1d = mode;
             else
                 return -1;
         } else
             pwm_dev->regs->ph1d = mode;
         break;
        
     /* 在one-shot operation,设置重复次数 */
     case PWMIOC_SET_RPT_VAL:
         get_user(mode, (int *)arg);
 

        if (mode < 0 || mode > 0xff)
             return -EINVAL;
 

        pwm_dev->regs->rpt = mode;
         break;
        
     /* 设置First-phase output level,也就是第一相的输出电平 */
     case PWMIOC_SET_FIRST_PHASE_STATE:
         get_user(mode, (int *)arg);
 

        if (pwm_dev->regs->cfg & 0x20000)
             return -EBUSY;
         if (mode == 1)    // 输出高电平
 

            pwm_dev->regs->cfg |= 0x10;
         else if (mode == 0)    // 输出低电平
 

            pwm_dev->regs->cfg &= ~0x10;
         else
             return -EINVAL;
         break;
        
     /* 设置Inactive output level,也就是pwm停止时的输出电平 */
     case PWMIOC_SET_INACT_OUT_STATE:
         get_user(mode, (int *)arg);
 

        if (pwm_dev->regs->cfg & 0x20000)
             return -EBUSY;
         if (mode == 1)
             pwm_dev->regs->cfg |= 0x20;
         else if (mode == 0)
             pwm_dev->regs->cfg &= ~0x20;
         else
             return -EINVAL;
         break;
        
     /* pwm开始运行 */
     case PWMIOC_START:
         pwm_dev->regs->start = 0x1;
         break;
        
     /* pwm停止。针对不同的运行模式采取不同的操作 */
     case PWMIOC_STOP:
         // 如果运行在one-shot mode,则直接停止
 

        if (pwm_dev->regs->cfg & 0x1 && pwm_dev->regs->cfg & 0x20000)
             pwm_dev->regs->cfg &= 0xFFFFFFFC;
         /* 如果运行在continuous mode,则通过中断来同步,先设置成one-shot mode,运行完后
         自然就停止了。
         */
         if (pwm_dev->regs->cfg & 0x2 && pwm_dev->regs->cfg & 0x20000) {
             unsigned long temp;
             temp = pwm_dev->regs->cfg;
             temp &= 0xFFFFFFFC;
             temp |= 0x1;
 

            /* Enable PWM interrupts */
             pwm_dev->regs->cfg |= 0x40;
 

            /* wait for the transaction to complete */
             wait_event_timeout(pwm_dev->intr_wait,
                      pwm_dev->intr_complete,
                      DAVINCI_PWM_TIMEOUT);
 

            if (pwm_dev->intr_complete)
                 pwm_dev->regs->cfg = temp;
             else
                 return -1;
         }
         break;
     }
 

    return 0;
 }
 

static int pwm_remove(struct device *device)
 {
     return 0;
 }
 

static void pwm_platform_release(struct device *device)
 {
     /* this function does nothing */
 }
 

/* 字符设备驱动的例程集 */
 static struct file_operations pwm_fops = {
     .owner = THIS_MODULE,
     .llseek = pwm_llseek,
     .open = pwm_open,
     .release = pwm_release,
     .ioctl = pwm_ioctl,
 };
 

static struct class_simple *pwm_class = NULL;
 

static struct platform_device pwm_device[] = {
     [0] = {
         .name = "davinci_pwm0",
         .id = 0,
         .dev = {
             .release = pwm_platform_release,
         }
     },
     [1] = {
         .name = "davinci_pwm1",
         .id = 1,
         .dev = {
             .release = pwm_platform_release,
         }
     },
     [2] = {
         .name = "davinci_pwm2",
         .id = 2,
         .dev = {
             .release = pwm_platform_release,
         }
     },
     [3] = {.name = "davinci_pwm3",
      .id = 3,
      .dev = {
             .release = pwm_platform_release,
         }
     }
 };
 

static struct device_driver pwm_driver[] = {
     [0] = {
         .name = "davinci_pwm0",
         .bus = &platform_bus_type,
         .remove = pwm_remove
     },
     [1] = {
         .name = "davinci_pwm1",
         .bus = &platform_bus_type,
         .remove = pwm_remove
     },
     [2] = {
         .name = "davinci_pwm2",
         .bus = &platform_bus_type,
         .remove = pwm_remove
     },
     [3] = {
         .name = "davinci_pwm3",
         .bus = &platform_bus_type,
         .remove = pwm_remove
     },
 };
 

/*
  * This function marks a transaction as complete.
  */
 static inline void pwm_davinci_complete_intr(struct pwm_davinci_device *dev)
 {
     dev->intr_complete = 1;
     wake_up(&dev->intr_wait);
 }
 

 

/* pwm中断处理程序,用于同步,设置某些寄存器 */
 static irqreturn_t pwm_isr(int irq, void *dev_id, struct pt_regs *regs)
 {
     struct pwm_davinci_device *dev = dev_id;
 

    /* Disable PWM interrupts */
     dev->regs->cfg &= ~0x40;
 

    pwm_davinci_complete_intr(dev);
     return IRQ_HANDLED;
 }
 

 

/* 驱动模块加载的初始化函数 */
 static int __init pwm_init(void)
 {
     int result;
     dev_t devno;
     unsigned int size, i, j;
     char *name[DAVINCI_PWM_MINORS];
 

    if (cpu_is_davinci_dm6467()) {
         pwm_minor_count = DM646X_PWM_MINORS;
         for (i = 0; i < pwm_minor_count; i++)
             name[i] = dm646x_name[i];
     } else if (cpu_is_davinci_dm355()) {
         pwm_minor_count = DM355_PWM_MINORS;
         for (i = 0; i < pwm_minor_count; i++)
             name[i] = dm355_name[i];
     } else {
         pwm_minor_count = DM644X_PWM_MINORS;
         for (i = 0; i < pwm_minor_count; i++)
             name[i] = dm644x_name[i];
     }
 

    size = pwm_device_count * pwm_minor_count;
     /* Register the driver in the kernel */
     /* 自动分配主设备号并分配size个次设备号*/
     result = alloc_chrdev_region(&devno, 0, size, DRIVER_NAME);
     if (result < 0) {
         printk("DaVinciPWM: Module intialization failed.\
                         could not register character device\n");
         return -ENODEV;
     }
     // 获取主设备号
 

    pwm_major = MAJOR(devno);
 

    /* Initialize of character device */
     /* 初始化cdev,kobj,并注册c_dev.ops */
     cdev_init(&c_dev, &pwm_fops);
     c_dev.owner = THIS_MODULE;
     c_dev.ops = &pwm_fops;
 

    /* addding character device */
     /*
      将该c_dev加入到kobj_map中。这样当应用程序调用open()时,便能根据设备号找到该cdev。查找cdev
      是在chrdev_open()例程中进行的,在该例程中,找到cdev后会将其注册到inode->i_cdev,并将
      inode->i_devices加入到该cdev->list()(list_add(&inode->i_devices, &p->list)),
      再将该cdev->ops注册到filp->f_op,最后调用filp->f_op->open(inode,filp);
      */
     result = cdev_add(&c_dev, devno, pwm_minor_count);
     if (result) {
         printk("DaVinciPWM:Error adding DavinciPWM\n");
         unregister_chrdev_region(devno, size);
         return result;
     }
 

    /* 生成一个simple class类并注册到subsys */
     pwm_class = class_simple_create(THIS_MODULE, "davinci_pwm");
     if (!pwm_class) {
         cdev_del(&c_dev);
         return -EIO;
     }
 

    /*
      注册所有达芬奇平台pwm接口的device driver和platform device;将device加入到
      pwm_class,在/dev/下生成相应的节点,并开启时钟。
     */
     for (i = 0; i < pwm_device_count; i++) {
         for (j = 0; j < pwm_minor_count; j++) {
             pwm_dev_array[j] =
              kmalloc(sizeof(struct pwm_davinci_device),GFP_KERNEL);
             pwm_dev_array[j]->devno = devno;
             init_waitqueue_head(&pwm_dev_array[j]->intr_wait);
             sprintf(pwm_dev_array[j]->name, "davinci_pwm%d", j);
 

            /* 注册为 platform driver */
             /* register driver as a platform driver */
             if (driver_register(&pwm_driver[j]) != 0) {
                 unregister_chrdev_region(devno, size);
                 cdev_del(&c_dev);
                 kfree(pwm_dev_array[j]);
                 return -EINVAL;
             }
 

            /* 注册为 platform device */
             /* Register the drive as a platform device */
             if (platform_device_register(&pwm_device[j]) != 0) {
                 driver_unregister(&pwm_driver[j]);
                 unregister_chrdev_region(devno, size);
                 cdev_del(&c_dev);
                 kfree(pwm_dev_array[j]);
                 return -EINVAL;
             }
 

            /* 在/dev目录下生成节点 */
             devno =
              MKDEV(pwm_major,
                  pwm_minor_start + i * pwm_minor_count + j);
             class_simple_device_add(pwm_class, devno, NULL,
                         "davinci_pwm%d", j);
 

            /* 注册中断例程 */
             /*
              * DM355 has PWM3 IRQ at #28
              */
             if (j == 3) {
                 result =
                     request_irq(IRQ_DM355_PWMINT3, pwm_isr,
                         SA_INTERRUPT,
                         pwm_dev_array[j]->name,
                         pwm_dev_array[j]);
             } else {
                 result = request_irq(IRQ_PWMINT0 + j,
                         pwm_isr, SA_INTERRUPT,
                         pwm_dev_array[j]->name,
                         pwm_dev_array[j]);
             }
 

            if (result < 0) {
                 printk("Cannot initialize IRQ \n");
                 platform_device_unregister(&pwm_device[j]);
                 driver_unregister(&pwm_driver[j]);
                 kfree(pwm_dev_array[j]);
                 return result;
             }
 

            /* 打开pwm模块时钟 */
             pwm_dev_array[j]->pwm_clk = clk_get(NULL, *(name + j));
             if (IS_ERR(pwm_dev_array[j]->pwm_clk)) {
                 printk("Cannot get clock\n");
                 return -1;
             }
             clk_use(pwm_dev_array[j]->pwm_clk);
             clk_enable(pwm_dev_array[j]->pwm_clk);
 

            /* 获得该pwm接口的物理寄存器地址 */
             pwm_dev_array[j]->regs =
              (davinci_pwmregsovly) IO_ADDRESS(DAVINCI_PWM0_BASE +
                              j * 0x400);
         }
     }
     return 0;
 }
 

 

/* 驱动模块卸载时调用的函数 */
 static void __exit pwm_exit(void)
 {
     dev_t devno;
     unsigned int size, i;
 

    /* 释放掉所有申请的资源 */
     if (pwm_class != NULL) {
         size = pwm_device_count * pwm_minor_count;
         for (i = 0; i < size; i++) {
             platform_device_unregister(&pwm_device[i]);
             driver_unregister(&pwm_driver[i]);
             devno = MKDEV(pwm_major, pwm_minor_start + i);
             class_simple_device_remove(devno);
             if ((i == 3) && (cpu_is_davinci_dm355()))
                 free_irq(IRQ_DM355_PWMINT3, pwm_dev_array[i]);
             else
                 free_irq(IRQ_PWMINT0 + i, pwm_dev_array[i]);
             clk_unuse(pwm_dev_array[i]->pwm_clk);
             clk_disable(pwm_dev_array[i]->pwm_clk);
             kfree(pwm_dev_array[i]);
         }
         class_simple_destroy(pwm_class);
     }
 

    cdev_del(&c_dev);
 

    /* Release major/minor numbers */
     if (pwm_major != 0) {
         devno = MKDEV(pwm_major, pwm_minor_start);
         size = pwm_device_count * pwm_minor_count;
         unregister_chrdev_region(devno, size);
     }
 }
 

module_init(pwm_init);
 module_exit(pwm_exit);
 

MODULE_AUTHOR("Texas Instruments");
 MODULE_LICENSE("GPL");
 

 

davinci_pwm.h:
 

/*
  * linux/drivers/char/davinci_pwm.h
  *
  * BRIEF MODULE DESCRIPTION
  * DaVinci PWM register definitions
  *
  * Copyright (C) 2006 Texas Instruments.
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
  * Free Software Foundation; either version 2 of the License, or (at your
  * option) any later version.
  *
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  * You should have received a copy of the GNU General Public License along
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 

#ifndef _DAVINCI_PWM_H
 #define _DAVINCI_PWM_H
 

/**************************************************************************\
 * Register Overlay Structure
 \**************************************************************************/
 typedef struct {
     unsigned int pid;
     unsigned int pcr;
     unsigned int cfg;
     unsigned int start;
     unsigned int rpt;
     unsigned int per;
     unsigned int ph1d;
 } davinci_pwmregs;
 

/**************************************************************************\
 * Overlay structure typedef definition
 \**************************************************************************/
 typedef volatile davinci_pwmregs *davinci_pwmregsovly;
 

#define PWM_MINORS        3
 #define DM646X_PWM_MINORS    2
 #define DM644X_PWM_MINORS    3
 #define DM355_PWM_MINORS    4
 #define DAVINCI_PWM_MINORS    DM355_PWM_MINORS /* MAX of all PWM_MINORS */
 

#define    PWMIOC_SET_MODE            0x01
 #define    PWMIOC_SET_PERIOD        0x02
 #define    PWMIOC_SET_DURATION        0x03
 #define    PWMIOC_SET_RPT_VAL        0x04
 #define    PWMIOC_START            0x05
 #define    PWMIOC_STOP            0x06
 #define    PWMIOC_SET_FIRST_PHASE_STATE    0x07
 #define    PWMIOC_SET_INACT_OUT_STATE    0x08
 

#define    PWM_ONESHOT_MODE    0
 #define    PWM_CONTINUOUS_MODE    1
 

#endif                /* _DAVINCI_PWM_H */

你可能感兴趣的:(linux,内核,Davinci,DM6446,davinci_pwm.c)