Chinaunix首页 | 论坛 | 博客
  • 博客访问: 33054
  • 博文数量: 21
  • 博客积分: 338
  • 博客等级: 一等列兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-31 09:41
文章分类

全部博文(21)

文章存档

2011年(21)

我的朋友

分类:

2011-03-13 21:20:29

--------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://zhiqiang0071.cublog.cn
--------------------------------------------

davinci_pwm.c:

/*
 * Copyright (C) 2006 Texas Instruments Inc
 *
 * 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 program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */


/* 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 */

阅读(406) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~