2011年1月20日 星期四

BeagleBoard PWM with RC Servo


OMAP3 PWM resource

http://elinux.org/BeagleBoardPWM

http://www.jumpnowtek.com/index.php?option=com_content&view=article&id=56&Itemid=63

RC Servo resource

http://robot.avayanex.com/?tag=rc-servo

http://robot.pixnet.net/blog/post/26779024

RC Servo duty cycle 最短要 20.83 ms, 也就是48Hz 以下.
Pulse width範圍在 1~2.5 ms左右,各廠牌型號Servo略有不同

2.OMAP3 PWM
OMAP3 總共支援 4 個PWM channel, 在BeagleBoard線路有拉出來的有3組,分別是PWM 9, 10, 11
位於Expansion Connector Pin 4,  8, 10. (參考BBSRM Table 20)

3.kernel 需要稍作修改(2.6.29)
將mux設為PWM pin(參考 OMAP35x Technical Reference Manual (Rev.M) 7.4.4.3 p.772)

arch/arm/mach-omap2/mux.c
...

#if 0
/* UART2 */
MUX_CFG_34XX("AA25_34XX_UART2_TX", 0x178,
        OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_OUTPUT)
MUX_CFG_34XX("AD25_34XX_UART2_RX", 0x17A,
        OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_INPUT_PULLUP)
MUX_CFG_34XX("AB25_34XX_UART2_RTS", 0x176,
        OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_OUTPUT)
MUX_CFG_34XX("AB26_34XX_UART2_CTS", 0x174,
        OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_INPUT_PULLUP)
#endif

/* PWM */
/*  OMAP35x Technical Reference Manual (Rev.M) 7.4.4.3 p.772*/
MUX_CFG_34XX("AA25_34XX_UART2_TX", 0x178,
    OMAP34XX_MUX_MODE2 | OMAP34XX_PIN_OUTPUT)
MUX_CFG_34XX("AD25_34XX_UART2_RX", 0x17A,
    OMAP34XX_MUX_MODE2 | OMAP34XX_PIN_OUTPUT)
MUX_CFG_34XX("AB25_34XX_UART2_RTS", 0x176,
    OMAP34XX_MUX_MODE2 | OMAP34XX_PIN_OUTPUT)
MUX_CFG_34XX("AB26_34XX_UART2_CTS", 0x174,
    OMAP34XX_MUX_MODE2 | OMAP34XX_PIN_OUTPUT)

再來要disable CONFIG_OMAP_RESET_CLOCKS這樣PWM才能正常工作.

3.OMAP3 PWM driver

https://github.com/scottellis/omap3-pwm

https://github.com/neo01124/omap3-pwm

以上面兩個PWM driver為基礎作修改可分別控制3組PWM.
Device node
/dev/pwm/9
/dev/pwm/10
/dev/pwm/11

Clock source從文件及source code所了解的應該是可以更改.
我實際測試的結果是

PWM9    13MHz
PWM10  32KHz
PWM11  32KHz

不只是更改GPTi input frequency 這麼單純,必須實際修改omap34xx clock source.
搞了半天還是不能把32KHz 換成 13MHz

Frequency預設是48Hz
13MHz clock source 精確度很好.
32KHz clock source 的 duty cycle 範圍約在 4 ~ 13%, 約只有50個刻度.因此精確度並不高.



4.PWM Driver source

From github : git@github.com:gigijoe/omap3-pwm.git

omap_pwm.h

/*
 Copyright (c) 2010, Scott Ellis
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY Scott Ellis ''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 Scott Ellis 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.
 

 Register definitions used by the pwm driver.
 Some for the PADCONF pin muxing, the rest for the PWM timer control.
*/

#ifndef PWM_H
#define PWM_H

#define OMAP34XX_PADCONF_START  0x48002030
#define OMAP34XX_PADCONF_SIZE   0x05cc

#define GPT8_MUX_OFFSET        (0x4800217A - OMAP34XX_PADCONF_START)
#define GPT9_MUX_OFFSET        (0x48002174 - OMAP34XX_PADCONF_START)
#define GPT10_MUX_OFFSET    (0x48002176 - OMAP34XX_PADCONF_START)
#define GPT11_MUX_OFFSET    (0x48002178 - OMAP34XX_PADCONF_START)

#define PWM_ENABLE_MUX        0x0002    /* IDIS | PTD | DIS | M2 */

#define CLK_32K_FREQ    32768
#define CLK_13K_FREQ    13312
#define CLK_SYS_FREQ    13000000

#define GPTIMER8        0x4903E000
#define GPTIMER9        0x49040000
#define GPTIMER10         0x48086000
#define GPTIMER11        0x48088000

#define GPT_REGS_PAGE_SIZE      4096

#define PWM8_CTL_BASE        GPTIMER8
#define PWM9_CTL_BASE        GPTIMER9
#define PWM10_CTL_BASE        GPTIMER10
#define PWM11_CTL_BASE        GPTIMER11


/* GPT register offsets */
#define GPT_TIOCP_CFG 0x010
#define GPT_TISTAT    0x014
#define GPT_TISR      0x018
#define GPT_TIER      0x01C
#define GPT_TWER      0x020
#define GPT_TCLR      0x024
#define GPT_TCRR      0x028
#define GPT_TLDR      0x02C
#define GPT_TTGR      0x030
#define GPT_TWPS      0x034
#define GPT_TMAR      0x038
#define GPT_TCAR1     0x03C
#define GPT_TSICR     0x040
#define GPT_TCAR2     0x044
#define GPT_TPIR      0x048
#define GPT_TNIR      0x04C
#define GPT_TCVR      0x050
#define GPT_TOCR      0x054
#define GPT_TOWR      0x058  

/* TCLR bits for PWM */
#define GPT_TCLR_ST         (1 << 0)    /* stop/start */
#define GPT_TCLR_AR         (1 << 1)    /* one shot/auto-reload */
#define GPT_TCLR_PTV_MASK        (7 << 2)    /* prescaler value 2^(PTV + 1) */
#define GPT_TCLR_PRE        (1 << 5)    /* disable/enable prescaler */
#define GPT_TCLR_CE         (1 << 6)    /* disable/enable compare */
#define GPT_TCLR_SCPWM      (1 << 7)    /* PWM value when off */
#define GPT_TCLR_TCM_MASK        (3 << 8)    /* transition capture mode */

#define GPT_TCLR_TRG_MASK     (3 << 10)    /* trigger output mode */
#define GPT_TCLR_TRG_OVFL    (1 << 10)    /* trigger on overflow */
#define GPT_TCLR_TRG_OVFL_MATCH    (2 << 10)    /* trigger on overflow and match */   

#define GPT_TCLR_PT         (1 << 12)    /* pulse/toggle modulation */
#define GPT_TCLR_CAPT_MODE      (1 << 13)    /* capture mode config */
#define GPT_TCLR_GPO_CFG        (1 << 14)    /* pwm or capture mode */

/* Ioctl definitions */

#define PWM_IOC_MAGIC 0x00

#define PWM_SET_DUTYCYCLE _IOW(PWM_IOC_MAGIC , 1, int)
#define PWM_GET_DUTYCYCLE _IOW(PWM_IOC_MAGIC , 2, int)
#define PWM_SET_FREQUENCY _IOW(PWM_IOC_MAGIC , 3, int)
#define PWM_GET_FREQUENCY _IOW(PWM_IOC_MAGIC , 4, int)
#define PWM_ON _IO(PWM_IOC_MAGIC , 5)
#define PWM_OFF _IO(PWM_IOC_MAGIC , 6)
#define PWM_SET_POLARITY _IOW(PWM_IOC_MAGIC , 7, int)
#define PWM_SET_CLK _IOW(PWM_IOC_MAGIC , 8, int)
#define PWM_SET_PRE _IOW(PWM_IOC_MAGIC , 9, int)
#define PWM_PLUS_DUTYCYCLE _IOW(PWM_IOC_MAGIC , 10, int)
#define PWM_MINUS_DUTYCYCLE _IOW(PWM_IOC_MAGIC , 11, int)

#define PWM_IOC_MAXNR 11

#endif /* ifndef PWM_H */





omap_pwm.c

/*
 Copyright (c) 2010, Scott Ellis
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY Scott Ellis ''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 Scott Ellis 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.
*/

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#include "omap_pwm.h"

/* default frequency of 1 kHz */
//#define DEFAULT_TLDR    0xFFFFFFE0
/* default frequency of 48 Hz */
//#define DEFAULT_TLDR    0xFFFFFD55

/* default 50% duty cycle */
/* TMAR = (0xFFFFFFFF - ((0xFFFFFFFF - (DEFAULT_TLDR + 1)) / 2)) */
//#define DEFAULT_TMAR    0xFFFFFFEF
//#define DEFAULT_TMAR    0xFFFFFEAB

/* default TCLR is off state */
#define DEFAULT_TCLR (GPT_TCLR_PT | GPT_TCLR_TRG_OVFL_MATCH | GPT_TCLR_CE | GPT_TCLR_AR)

//#define DEFAULT_PWM_FREQUENCY 1024
#define DEFAULT_PWM_FREQUENCY 48

static int defaultFrequency = DEFAULT_PWM_FREQUENCY;
module_param(defaultFrequency, int, S_IRUGO);
MODULE_PARM_DESC(defaultFrequency, "The PWM frequency, power of two, max of 16384");


#define USER_BUFF_SIZE    128

struct gpt {
    u32 timer_num;
    u32 mux_offset;
    u32 gpt_base;
    u32 input_freq;
    u32 old_mux;
    u32 tldr;
    u32 tmar;
    u32 tclr;
    u32 num_freqs;
};

struct pwm_dev {
    int frequency;
    struct semaphore sem;
    struct gpt gpt;
    char *user_buff;
};

static dev_t devt;
static struct cdev cdev;

static struct pwm_dev pwm9_dev;
static struct pwm_dev pwm10_dev;
static struct pwm_dev pwm11_dev;


static int init_mux(struct pwm_dev *pwm_dev)
{
    void __iomem *base;

    base = ioremap(OMAP34XX_PADCONF_START, OMAP34XX_PADCONF_SIZE);
    if (!base) {
        printk(KERN_ALERT "init_mux(): ioremap() failed\n");
        return -1;
    }

    pwm_dev->gpt.old_mux = ioread16(base + pwm_dev->gpt.mux_offset);
    iowrite16(PWM_ENABLE_MUX, base + pwm_dev->gpt.mux_offset);
    iounmap(base);   

    return 0;
}

static int restore_mux(struct pwm_dev *pwm_dev)
{
    void __iomem *base;

    if (pwm_dev->gpt.old_mux) {
        base = ioremap(OMAP34XX_PADCONF_START, OMAP34XX_PADCONF_SIZE);
   
        if (!base) {
            printk(KERN_ALERT "restore_mux(): ioremap() failed\n");
            return -1;
        }

        iowrite16(pwm_dev->gpt.old_mux, base + pwm_dev->gpt.mux_offset);
        iounmap(base);   
    }

    return 0;
}

static int set_pwm_frequency(struct pwm_dev *pwm_dev, int frequency)
{
    void __iomem *base;

    base = ioremap(pwm_dev->gpt.gpt_base, GPT_REGS_PAGE_SIZE);
    if (!base) {
        printk(KERN_ALERT "set_pwm_frequency(): ioremap failed\n");
        return -1;
    }

    if (frequency < 0) {
        frequency = DEFAULT_PWM_FREQUENCY;
    } else {
        /* only powers of two, for simplicity */
        //frequency &= ~0x01;

        if (frequency > (pwm_dev->gpt.input_freq / 2))
            frequency = pwm_dev->gpt.input_freq / 2;
        else if (frequency == 0)
            frequency = DEFAULT_PWM_FREQUENCY;
    }

    pwm_dev->frequency = frequency;

    /* PWM_FREQ = 32768 / ((0xFFFF FFFF - TLDR) + 1) */
    pwm_dev->gpt.tldr = 0xFFFFFFFF - ((pwm_dev->gpt.input_freq / pwm_dev->frequency) - 1);

    /* just for convenience */   
    pwm_dev->gpt.num_freqs = 0xFFFFFFFE - pwm_dev->gpt.tldr;   

    iowrite32(pwm_dev->gpt.tldr, base + GPT_TLDR);

    /* initialize TCRR to TLDR, have to start somewhere */
    iowrite32(pwm_dev->gpt.tldr, base + GPT_TCRR);

    iounmap(base);

    return 0;
}

static int pwm_off(struct pwm_dev *pwm_dev)
{
    void __iomem *base;

    base = ioremap(pwm_dev->gpt.gpt_base, GPT_REGS_PAGE_SIZE);
    if (!base) {
        printk(KERN_ALERT "pwm_off(): ioremap failed\n");
        return -1;
    }

    pwm_dev->gpt.tclr &= ~GPT_TCLR_ST;
    iowrite32(pwm_dev->gpt.tclr, base + GPT_TCLR);
    iounmap(base);

    return 0;
}

static int pwm_on(struct pwm_dev *pwm_dev)
{
    void __iomem *base;

    base = ioremap(pwm_dev->gpt.gpt_base, GPT_REGS_PAGE_SIZE);

    if (!base) {
        printk(KERN_ALERT "pwm_on(): ioremap failed\n");
        return -1;
    }

    /* set the duty cycle */
    iowrite32(pwm_dev->gpt.tmar, base + GPT_TMAR);
   
    /* now turn it on */
    pwm_dev->gpt.tclr = ioread32(base + GPT_TCLR);
    pwm_dev->gpt.tclr |= GPT_TCLR_ST;

    iowrite32(pwm_dev->gpt.tclr, base + GPT_TCLR);
     iounmap(base);

    return 0;
}

static int scpwm(struct pwm_dev *pwm_dev, int sc)
{
    void __iomem *base;

    base = ioremap(pwm_dev->gpt.gpt_base, GPT_REGS_PAGE_SIZE);
    if (!base) {
        printk(KERN_ALERT "pwm_off(): ioremap failed\n");
        return -1;
    }

    if (sc == 1)
        pwm_dev->gpt.tclr |= GPT_TCLR_SCPWM;
    else
        pwm_dev->gpt.tclr &= ~GPT_TCLR_SCPWM;

    iowrite32(pwm_dev->gpt.tclr, base + GPT_TCLR);
    iounmap(base);

    return 0;
}

static int prescale(struct pwm_dev *pwm_dev, int div)
{
    void __iomem *base;
    int i = 0;
    base = ioremap(pwm_dev->gpt.gpt_base, GPT_REGS_PAGE_SIZE);

    if (!base) {
        printk(KERN_ALERT "pwm_off(): ioremap failed\n");
        return -1;
    }

    while (div > 2) {
        i++;
        div /= 2;
    }

    pwm_dev->gpt.tclr |= GPT_TCLR_PRE; //enable prescaler
    pwm_dev->gpt.tclr &= i << 2; //set prescaler ratio
    iowrite32(pwm_dev->gpt.tclr, base + GPT_TCLR);
    iounmap(base);

    return 0;
}

static int set_duty_cycle(struct pwm_dev *pwm_dev, unsigned int duty_cycle)
{
    unsigned int new_tmar;

    pwm_off(pwm_dev);

    if (duty_cycle == 0)
        return 0;
 
    new_tmar = (duty_cycle * pwm_dev->gpt.num_freqs) / 100;

    if (new_tmar < 1)
        new_tmar = 1;
    else if (new_tmar > pwm_dev->gpt.num_freqs)
        new_tmar = pwm_dev->gpt.num_freqs;
       
    pwm_dev->gpt.tmar = pwm_dev->gpt.tldr + new_tmar;
printk("TMAR : 0x%08x\n", pwm_dev->gpt.tmar);   
    return pwm_on(pwm_dev);
}

static ssize_t pwm_read(struct file *filp, char __user *buff, size_t count,
            loff_t *offp)
{
    size_t len;
    unsigned int duty_cycle;
    ssize_t error = 0;

      struct pwm_dev *pwm_dev = (struct pwm_dev *)filp->private_data;
 
    if (!buff)
        return -EFAULT;

    /* tell the user there is no more */
    if (*offp > 0)
        return 0;

    if (down_interruptible(&pwm_dev->sem))
        return -ERESTARTSYS;

    if (pwm_dev->gpt.tclr & GPT_TCLR_ST) {
        duty_cycle = (100 * (pwm_dev->gpt.tmar - pwm_dev->gpt.tldr))
                / pwm_dev->gpt.num_freqs;

        snprintf(pwm_dev->user_buff, USER_BUFF_SIZE,
                "PWM%d Frequency %u Hz Duty Cycle %u%%\n",
                pwm_dev->gpt.timer_num, pwm_dev->frequency, duty_cycle);
    }
    else {
        snprintf(pwm_dev->user_buff, USER_BUFF_SIZE,
                "PWM%d Frequency %u Hz Stopped\n",
                pwm_dev->gpt.timer_num, pwm_dev->frequency);
    }

    len = strlen(pwm_dev->user_buff);
 
    if (len + 1 < count)
        count = len + 1;

    if (copy_to_user(buff, pwm_dev->user_buff, count))  {
        printk(KERN_ALERT "pwm_read(): copy_to_user() failed\n");
        error = -EFAULT;
    }
    else {
        *offp += count;
        error = count;
    }

    up(&pwm_dev->sem);

    return error;   
}

static ssize_t pwm_write(struct file *filp, const char __user *buff,
            size_t count, loff_t *offp)
{
    size_t len;
    unsigned int duty_cycle;
    ssize_t error = 0;
   
      struct pwm_dev *pwm_dev = (struct pwm_dev *)filp->private_data;

    if (down_interruptible(&pwm_dev->sem))
        return -ERESTARTSYS;

    if (!buff || count < 1) {
        printk(KERN_ALERT "pwm_write(): input check failed\n");
        error = -EFAULT;
        goto pwm_write_done;
    }
   
    /* we are only expecting a small integer, ignore anything else */
    if (count > 8)
        len = 8;
    else
        len = count;
       
    memset(pwm_dev->user_buff, 0, 16);

    if (copy_from_user(pwm_dev->user_buff, buff, len)) {
        printk(KERN_ALERT "pwm_write(): copy_from_user() failed\n");
        error = -EFAULT;    
        goto pwm_write_done;
    }

    if(pwm_dev->user_buff[0] == '+')    {
        pwm_off(pwm_dev);
        if(pwm_dev->user_buff[1] != '\0')
            pwm_dev->gpt.tmar += simple_strtoul(&pwm_dev->user_buff[1], NULL, 0);
        pwm_on(pwm_dev);
printk("TMAR : 0x%08x\n", pwm_dev->gpt.tmar);
    } else if(pwm_dev->user_buff[0] == '-')    {
        pwm_off(pwm_dev);
        if(pwm_dev->user_buff[1] != '\0')
            pwm_dev->gpt.tmar -= simple_strtoul(&pwm_dev->user_buff[1], NULL, 0);
        pwm_on(pwm_dev);
printk("TMAR : 0x%08x\n", pwm_dev->gpt.tmar);
    } else    {
        duty_cycle = simple_strtoul(pwm_dev->user_buff, NULL, 0);

        set_duty_cycle(pwm_dev, duty_cycle);

    }
    /* pretend we ate it all */
    *offp += count;

    error = count;
pwm_write_done:

    up(&pwm_dev->sem);
   
    return error;
}

static int pwm_open(struct inode *inode, struct file *filp)
{
    int error = 0;

    struct pwm_dev *pwm_dev = 0;
   
    unsigned int minor = iminor(inode);

    switch(minor)    {
        case 9:        pwm_dev = &pwm9_dev;   
            break;
        case 10:    pwm_dev = &pwm10_dev;   
            break;
        case 11:    pwm_dev = &pwm11_dev;
            break;
    }
     
    if(pwm_dev == 0)
        return -EFAULT;

    filp->private_data = pwm_dev;

    if (down_interruptible(&pwm_dev->sem))
        return -ERESTARTSYS;

    if (pwm_dev->gpt.old_mux == 0) {
        if (init_mux(pwm_dev)) 
            error = -EIO;
        else if (set_pwm_frequency(pwm_dev, defaultFrequency))
            error = -EIO;
    }

    if (!pwm_dev->user_buff) {
        pwm_dev->user_buff = kmalloc(USER_BUFF_SIZE, GFP_KERNEL);
        if (!pwm_dev->user_buff)
            error = -ENOMEM;
    }

    up(&pwm_dev->sem);

    return error;   
}

int pwm_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
    int retval = 0;
      struct pwm_dev *pwm_dev = (struct pwm_dev *)filp->private_data;

    /*
    * extract the type and number bitfields, and don't decode
    * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
    */
    if (_IOC_TYPE(cmd) != PWM_IOC_MAGIC)
        return -ENOTTY;
    if (_IOC_NR(cmd) > PWM_IOC_MAXNR)
        return -ENOTTY;

     switch (cmd) {
        case PWM_ON:
            if (pwm_on(pwm_dev))
                retval = -EIO;
            break;
        case PWM_OFF:
            if (pwm_off(pwm_dev))
                retval = -EIO;
            break;
        case PWM_SET_DUTYCYCLE:
            if (set_duty_cycle(pwm_dev, arg))
                retval = -EIO;
            break;
        case PWM_GET_DUTYCYCLE:
            if (pwm_dev->gpt.tclr & GPT_TCLR_ST) { //PWM is on
                retval = (100 * (pwm_dev->gpt.tmar - pwm_dev->gpt.tldr))
                        / pwm_dev->gpt.num_freqs; //real duty cycle
            } else {
                printk(KERN_ALERT "PWM%d is OFF\n", pwm_dev->gpt.timer_num);
                retval = -EIO;
            }
            break;
        case PWM_SET_FREQUENCY:
            if (set_pwm_frequency(pwm_dev, arg))
                retval = -EIO;
            break;
        case PWM_GET_FREQUENCY:
            retval = pwm_dev->frequency;
            break;
        case PWM_SET_POLARITY:
            if (scpwm(pwm_dev, arg))
                retval = -EIO;
            break;
        case PWM_SET_CLK:
/* Do no change clock source !!! */
/*
            if (pwm_dev->gpt.timer_num == 9) {
                printk(KERN_ALERT "Only 32K clk can be used with GPT9\n");
                retval = -EIO;
            } else {
                if (arg == 1)
                    pwm_dev->gpt.input_freq = CLK_13K_FREQ;
                else
                    pwm_dev->gpt.input_freq = CLK_32K_FREQ;
            }
*/
        break;
        case PWM_SET_PRE:
            if (prescale(pwm_dev, arg))
                retval = -EIO;
            break;
        case PWM_PLUS_DUTYCYCLE:
            pwm_off(pwm_dev);
            pwm_dev->gpt.tmar++;
            pwm_on(pwm_dev);
            break;
        case PWM_MINUS_DUTYCYCLE:
            pwm_off(pwm_dev);
            if(pwm_dev->gpt.tmar > 0)
                pwm_dev->gpt.tmar--;
            pwm_on(pwm_dev);
            break;
        default: /* redundant, as cmd was checked against MAXNR */
            return -ENOTTY;
    }

    return retval;
}

static struct file_operations pwm_fops = {
    .owner = THIS_MODULE,
    .read = pwm_read,
    .write = pwm_write,
    .open = pwm_open,
    .ioctl = pwm_ioctl,
};

static int __init pwm_init_cdev(void)
{
    int error;

    error = alloc_chrdev_region(&devt, 9, 3, "pwm");    /*    minor number start from 9, count 3*/

    if (error < 0) {
        printk(KERN_ALERT "alloc_chrdev_region() failed: %d \n",
            error);
        return -1;
    }

    cdev_init(&cdev, &pwm_fops);
    cdev.owner = THIS_MODULE;
   
    error = cdev_add(&cdev, devt, 3);
    if (error) {
        printk(KERN_ALERT "cdev_add() failed: %d\n", error);
        unregister_chrdev_region(devt, 3);
        return -1;
    }   

    return 0;
}
 
static int __init pwm_init(void)
{
    memset(&pwm9_dev, 0, sizeof(struct pwm_dev));

    /* change these 4 values to use a different PWM */
    pwm9_dev.frequency = defaultFrequency;
    pwm9_dev.gpt.timer_num = 9;
    pwm9_dev.gpt.mux_offset = GPT9_MUX_OFFSET;
    pwm9_dev.gpt.gpt_base = PWM9_CTL_BASE;
    pwm9_dev.gpt.input_freq = CLK_SYS_FREQ;    /* GPT9 can only use 13MHz clock source */

    pwm9_dev.gpt.tldr = 0xFFFFFFFF - (pwm9_dev.gpt.input_freq / pwm9_dev.frequency) + 1;
    pwm9_dev.gpt.tmar = 0xFFFFFFFF - ((0xFFFFFFFF - (pwm9_dev.gpt.tldr + 1)) / 2);
    pwm9_dev.gpt.tclr = DEFAULT_TCLR;

    sema_init(&pwm9_dev.sem, 1);

    memset(&pwm10_dev, 0, sizeof(struct pwm_dev));

    /* change these 4 values to use a different PWM */
    pwm10_dev.frequency = defaultFrequency;
    pwm10_dev.gpt.timer_num = 10;
    pwm10_dev.gpt.mux_offset = GPT10_MUX_OFFSET;
    pwm10_dev.gpt.gpt_base = PWM10_CTL_BASE;
    pwm10_dev.gpt.input_freq = CLK_32K_FREQ;

    pwm10_dev.gpt.tldr = 0xFFFFFFFF - (pwm10_dev.gpt.input_freq / pwm10_dev.frequency) + 1;
    pwm10_dev.gpt.tmar = 0xFFFFFFFF - ((0xFFFFFFFF - (pwm10_dev.gpt.tldr + 1)) / 2);
    pwm10_dev.gpt.tclr = DEFAULT_TCLR;

    sema_init(&pwm10_dev.sem, 1);

    memset(&pwm11_dev, 0, sizeof(struct pwm_dev));

    /* change these 4 values to use a different PWM */
    pwm11_dev.frequency = defaultFrequency;
    pwm11_dev.gpt.timer_num = 11;
    pwm11_dev.gpt.mux_offset = GPT11_MUX_OFFSET;
    pwm11_dev.gpt.gpt_base = PWM11_CTL_BASE;
    pwm11_dev.gpt.input_freq = CLK_32K_FREQ;

    pwm11_dev.gpt.tldr = 0xFFFFFFFF - (pwm11_dev.gpt.input_freq / pwm11_dev.frequency) + 1;
    pwm11_dev.gpt.tmar = 0xFFFFFFFF - ((0xFFFFFFFF - (pwm11_dev.gpt.tldr + 1)) / 2);
    pwm11_dev.gpt.tclr = DEFAULT_TCLR;

    sema_init(&pwm11_dev.sem, 1);

    if (pwm_init_cdev())
        goto init_fail;

    printk(KERN_INFO "OMAP3 PWM: character device initialized (major=%d)\n", MAJOR(devt));

    return 0;

init_fail:
    return -1;
}
module_init(pwm_init);

static void __exit pwm_exit(void)
{
    cdev_del(&cdev);

    pwm_off(&pwm9_dev);
    restore_mux(&pwm9_dev);

    if (pwm9_dev.user_buff)
        kfree(pwm9_dev.user_buff);

    pwm_off(&pwm10_dev);
    restore_mux(&pwm10_dev);

    if (pwm10_dev.user_buff)
        kfree(pwm10_dev.user_buff);

    pwm_off(&pwm11_dev);
    restore_mux(&pwm11_dev);

    if (pwm11_dev.user_buff)
        kfree(pwm11_dev.user_buff);

    unregister_chrdev_region(devt, 3);

}
module_exit(pwm_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Scott Ellis - Jumpnow");
MODULE_AUTHOR("Steve Chang");
MODULE_DESCRIPTION("PWM example for Gumstix Overo");







沒有留言:

張貼留言