From: Yusuf Caglar Akyuz <[email protected]> This commit blindly imports existing PWM driver from 2.6.10 tree. Build fixes and other improvements will be added in recpective commits.
Signed-off-by: Yusuf Caglar Akyuz <[email protected]> --- arch/arm/mach-davinci/include/mach/pwm.h | 69 ++++ arch/arm/mach-davinci/pwm.c | 501 ++++++++++++++++++++++++++++++ 2 files changed, 570 insertions(+), 0 deletions(-) create mode 100644 arch/arm/mach-davinci/include/mach/pwm.h create mode 100644 arch/arm/mach-davinci/pwm.c diff --git a/arch/arm/mach-davinci/include/mach/pwm.h b/arch/arm/mach-davinci/include/mach/pwm.h new file mode 100644 index 0000000..c73604a --- /dev/null +++ b/arch/arm/mach-davinci/include/mach/pwm.h @@ -0,0 +1,69 @@ +/* + * 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 */ diff --git a/arch/arm/mach-davinci/pwm.c b/arch/arm/mach-davinci/pwm.c new file mode 100644 index 0000000..6790f3f --- /dev/null +++ b/arch/arm/mach-davinci/pwm.c @@ -0,0 +1,501 @@ +/* + * 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) + +struct pwm_davinci_device { + char name[20]; + int intr_complete; + dev_t devno; + davinci_pwmregsovly regs; + wait_queue_head_t intr_wait; + struct clk *pwm_clk; +}; + +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 WDT 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; + +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_dev->regs->ph1d = 0xf; + pwm_dev->regs->rpt = 1; + pwm_dev->regs->cfg |= 0x1; + + 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; + /* This is called when the reference count goes to zero */ + return 0; +} + +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) { + 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; + case PWMIOC_SET_PERIOD: + get_user(mode, (int *)arg); + + if (mode < 0 || mode > 0xffffffff) + return -EINVAL; + + 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; + case PWMIOC_SET_DURATION: + get_user(mode, (int *)arg); + + if (mode < 0 || mode > 0xffffffff) + return -EINVAL; + + 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; + case PWMIOC_SET_RPT_VAL: + get_user(mode, (int *)arg); + + if (mode < 0 || mode > 0xff) + return -EINVAL; + + pwm_dev->regs->rpt = mode; + break; + 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; + 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; + case PWMIOC_START: + pwm_dev->regs->start = 0x1; + break; + case PWMIOC_STOP: + if (pwm_dev->regs->cfg & 0x1 && pwm_dev->regs->cfg & 0x20000) + pwm_dev->regs->cfg &= 0xFFFFFFFC; + 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); +} + +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 */ + 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_init(&c_dev, &pwm_fops); + c_dev.owner = THIS_MODULE; + c_dev.ops = &pwm_fops; + + /* addding character device */ + result = cdev_add(&c_dev, devno, pwm_minor_count); + if (result) { + printk("DaVinciPWM:Error adding DavinciPWM\n"); + unregister_chrdev_region(devno, size); + return result; + } + + pwm_class = class_simple_create(THIS_MODULE, "davinci_pwm"); + if (!pwm_class) { + cdev_del(&c_dev); + return -EIO; + } + + 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); + + /* 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; + } + + /* 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; + } + + 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_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_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"); -- 1.5.6 _______________________________________________ Davinci-linux-open-source mailing list [email protected] http://linux.davincidsp.com/mailman/listinfo/davinci-linux-open-source
