From patchwork Mon Sep 20 07:44:47 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sugumar Natarajan X-Patchwork-Id: 194462 Received: from devils.ext.ti.com (devils.ext.ti.com [198.47.26.153]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id o8K8L4cW004827 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Mon, 20 Sep 2010 08:21:40 GMT Received: from dlep35.itg.ti.com ([157.170.170.118]) by devils.ext.ti.com (8.13.7/8.13.7) with ESMTP id o8K8JdSs026178 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Mon, 20 Sep 2010 03:19:39 -0500 Received: from linux.omap.com (localhost [127.0.0.1]) by dlep35.itg.ti.com (8.13.7/8.13.7) with ESMTP id o8K8JdM8019779; Mon, 20 Sep 2010 03:19:39 -0500 (CDT) Received: from linux.omap.com (localhost [127.0.0.1]) by linux.omap.com (Postfix) with ESMTP id 3CA5080628; Mon, 20 Sep 2010 03:19:39 -0500 (CDT) X-Original-To: davinci-linux-open-source@linux.davincidsp.com Delivered-To: davinci-linux-open-source@linux.davincidsp.com Received: from dflp53.itg.ti.com (dflp53.itg.ti.com [128.247.5.6]) by linux.omap.com (Postfix) with ESMTP id C6E3780626 for ; Mon, 20 Sep 2010 03:19:37 -0500 (CDT) Received: from tidmzi-ftp.india.ext.ti.com (localhost [127.0.0.1]) by dflp53.itg.ti.com (8.13.8/8.13.8) with SMTP id o8K8JZH5008815 for ; Mon, 20 Sep 2010 03:19:36 -0500 (CDT) Received: from symphonyindia.ti.com (symphony-ftp [192.168.247.11]) by tidmzi-ftp.india.ext.ti.com (Postfix) with SMTP id 7E3023887A for ; Mon, 20 Sep 2010 13:49:31 +0530 (IST) Received: from localhost.localdomain ([192.168.247.76]) by symphonyindia.ti.com (8.13.1/8.12.10) with ESMTP id o8K8CWWN022650; Mon, 20 Sep 2010 13:42:32 +0530 From: Sugumar Natarajan To: davinci-linux-open-source@linux.davincidsp.com Subject: [PATCH 1/2] davinci: da8xx: eHRPWM driver for simple PWM output control Date: Mon, 20 Sep 2010 13:14:47 +0530 Message-Id: <1284968687-17228-1-git-send-email-sugumar@ti.com> X-Mailer: git-send-email 1.5.6 X-BeenThere: davinci-linux-open-source@linux.davincidsp.com X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: davinci-linux-open-source-bounces@linux.davincidsp.com Errors-To: davinci-linux-open-source-bounces@linux.davincidsp.com X-Greylist: Sender succeeded STARTTLS authentication, not delayed by milter-greylist-4.2.3 (demeter1.kernel.org [140.211.167.41]); Mon, 20 Sep 2010 08:21:40 +0000 (UTC) diff --git a/arch/arm/mach-davinci/Kconfig b/arch/arm/mach-davinci/Kconfig index 8192866..6d588ed 100644 --- a/arch/arm/mach-davinci/Kconfig +++ b/arch/arm/mach-davinci/Kconfig @@ -239,6 +239,14 @@ config DAVINCI_ECAP_PWM help Say Y to select the eCAP module for PWM control. +config DAVINCI_EHRPWM + bool "eHRPWM driver support for simple PWM control" + depends on ARCH_DAVINCI_DA8XX + depends on HAVE_PWM + default n + help + Say Y to select the eHRPWM driver + endmenu endif diff --git a/arch/arm/mach-davinci/Makefile b/arch/arm/mach-davinci/Makefile index 90bd88e..3d567ca 100644 --- a/arch/arm/mach-davinci/Makefile +++ b/arch/arm/mach-davinci/Makefile @@ -45,3 +45,6 @@ obj-$(CONFIG_HAVE_PWM) += davinci_pwm.o # eCAP driver support for PWM obj-$(CONFIG_DAVINCI_ECAP_PWM) += ecap.o + +# eHRPWM driver for simple PWM control +obj-$(CONFIG_DAVINCI_EHRPWM) += ehrpwm.o diff --git a/arch/arm/mach-davinci/ehrpwm.c b/arch/arm/mach-davinci/ehrpwm.c new file mode 100644 index 0000000..5166370 --- /dev/null +++ b/arch/arm/mach-davinci/ehrpwm.c @@ -0,0 +1,302 @@ +/* + * DA850/OMAP-L138 eHRPWM driver for simple PWM output generation + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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 version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TBCTL_REG 0x0 +#define TBPRD_REG 0xA +#define CMPA_REG 0x12 +#define CMPB_REG 0x14 +#define AQCTLA_REG 0x16 +#define AQCTLB_REG 0x18 + +#define TBCTL_FREERUN_FREE 0x2 +#define TBCTL_SYNCOSEL_DISABLE 0x3 + +#define ACTCTLA_CTREQCMP_LOW 0x1 +#define ACTCTLA_CTREQPRD_HIGH 0x2 + +#define NUMBER_OF_PWM_OUTPUTS 2 + +static struct private { + int duty_val; +} pv_data[NUMBER_OF_PWM_OUTPUTS]; + +struct pwm_device *pwm_dev[NUMBER_OF_PWM_OUTPUTS]; + +struct freq_divider_table { + unsigned char clkdiv_val; + unsigned char hspclkdiv_val; + unsigned int resulting_div; +}; + +/* + * The table shows all the combinations of CLKDIV and HSPCLKDIV that + * are available, and the resulting divider. + * resulting divider = CLKDIV * HSPCLKDIV + */ + +static struct freq_divider_table frqtable[] = { + {0, 0, 1}, + {0, 1, 2}, + {0, 2, 4}, + {0, 3, 6}, + {0, 4, 8}, + {0, 5, 10}, + {0, 6, 12}, + {0, 7, 14}, + {1, 4, 16}, + {1, 5, 20}, + {1, 6, 24}, + {1, 7, 28}, + {2, 4, 32}, + {2, 5, 40}, + {2, 6, 48}, + {2, 7, 56}, + {3, 4, 64}, + {3, 5, 80}, + {3, 6, 96}, + {3, 7, 112}, + {4, 4, 128}, + {4, 5, 160}, + {4, 6, 192}, + {4, 7, 224}, + {5, 4, 256}, + {5, 5, 320}, + {5, 6, 384}, + {5, 7, 448}, + {6, 4, 512}, + {6, 5, 640}, + {6, 6, 768}, + {6, 7, 896}, + {7, 4, 1024}, + {7, 5, 1280}, + {7, 6, 1536}, + {7, 7, 1792}, +}; + +/* + * ehrpwm_config_pwm - configures the ehrpwm module for the PWM output with + * given period and duty cycle. + */ + +static int ehrpwm_pwm_config(struct pwm_device *pwm, unsigned int + period_cycles, unsigned int duty_cycle) +{ + int tb_div_val = 0; + int desired_ps_val = 0; + int available_ps_val = 0; + int i; + struct private *private_data = (struct private *)(pwm->private); + + /* + * Prescaler is used when the period_cycles value exceeds the maximum + * value of the period register. + * We always look for minimum prescaler value as it would result in wide + * range of duty cycle control. + */ + + if (period_cycles > 65535) { + desired_ps_val = period_cycles / 65535 + 1; + for (i = 0; i < (sizeof(frqtable) / sizeof(frqtable[0])); i++) { + if (frqtable[i].resulting_div >= desired_ps_val) { + available_ps_val = frqtable[i].resulting_div; + tb_div_val = frqtable[i].clkdiv_val << 10 + | frqtable[i].hspclkdiv_val << 7; + break; + } + } + + period_cycles = period_cycles / available_ps_val; + duty_cycle = duty_cycle / available_ps_val; + } + + __raw_writew((TBCTL_FREERUN_FREE << 14) | (TBCTL_SYNCOSEL_DISABLE << 4) + | tb_div_val , pwm->mmio_base + TBCTL_REG); + __raw_writew((unsigned short)period_cycles, pwm->mmio_base + + TBPRD_REG); + + /* + * 100% duty cycle is obtained when the duty_cycle value is 1 greater + * than period_cycles value ie duty_cycle = period_cycles + 1. + */ + + if (duty_cycle == period_cycles) + duty_cycle = period_cycles + 1; + + private_data->duty_val = duty_cycle; + return 0; +} + +int ehrpwm_pwm_enable(struct pwm_device *pwm) +{ + struct private *private_data = (struct private *)(pwm->private); + + if (!pwm->output_number) { + __raw_writew((unsigned short)private_data->duty_val, + pwm->mmio_base + CMPA_REG); + __raw_writew((ACTCTLA_CTREQCMP_LOW << 4) + | (ACTCTLA_CTREQPRD_HIGH << 2), + pwm->mmio_base + AQCTLA_REG); + } else { + __raw_writew((unsigned short)private_data->duty_val, + pwm->mmio_base + CMPB_REG); + __raw_writew((ACTCTLA_CTREQCMP_LOW << 8) + | (ACTCTLA_CTREQPRD_HIGH << 2), + pwm->mmio_base + AQCTLB_REG); + } + + return 0; +} + +void ehrpwm_pwm_disable(struct pwm_device *pwm) +{ + if (!pwm->output_number) { + __raw_writew(0, pwm->mmio_base + + CMPA_REG); + __raw_writew((ACTCTLA_CTREQCMP_LOW << 4) + | (ACTCTLA_CTREQPRD_HIGH << 2), + pwm->mmio_base + AQCTLA_REG); + } else { + __raw_writew(0 , pwm->mmio_base + + CMPB_REG); + __raw_writew((ACTCTLA_CTREQCMP_LOW << 8) + | (ACTCTLA_CTREQPRD_HIGH << 2), + pwm->mmio_base + AQCTLB_REG); + } +} + +static int __devinit ehrpwm_probe(struct platform_device *pdev) +{ + struct pwm_device **pwm = NULL; + struct resource *r; + int ret = 0; + int i = 0; + + pwm = &pwm_dev[0]; + + for (i = 0; i < NUMBER_OF_PWM_OUTPUTS; i++) { + pwm[i] = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); + if (!pwm[i]) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + goto err_free_device; + return -ENOMEM; + } + } + + pwm[0]->clk = clk_get(&pdev->dev, "ehrpwm"); + if (IS_ERR(pwm[0]->clk)) { + ret = PTR_ERR(pwm[0]->clk); + goto err_free_device; + } + + pwm[0]->pdev = pdev; + pwm[0]->pwm_config_device = ehrpwm_pwm_config; + pwm[0]->pwm_enable = ehrpwm_pwm_enable; + pwm[0]->pwm_disable = ehrpwm_pwm_disable; + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free_clk; + } + + r = request_mem_region(r->start, resource_size(r), pdev->name); + if (!r) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free_clk; + } + + pwm[0]->mmio_base = ioremap(r->start, resource_size(r)); + if (!pwm[0]->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + ret = -ENODEV; + goto err_free_mem; + } + + for (i = 0; i < NUMBER_OF_PWM_OUTPUTS; i++) { + *pwm[i] = *pwm[0]; + pwm[i]->pwm_id = pdev->id + i; + pwm[i]->output_number = i; + pwm[i]->private = &pv_data[i]; + + pwm_add(pwm[i]); + } + + platform_set_drvdata(pdev, pwm); + + clk_enable(pwm[0]->clk); + return 0; + +err_free_mem: + release_mem_region(r->start, resource_size(r)); +err_free_clk: + clk_put(pwm[0]->clk); +err_free_device: + for (i = 0; i < NUMBER_OF_PWM_OUTPUTS; i++) + kfree(pwm[i]); + return ret; +} + +static int __devexit ehrpwm_remove(struct platform_device *pdev) +{ + struct pwm_device **pwm; + struct resource *r; + int i = 0; + + pwm = platform_get_drvdata(pdev); + iounmap(pwm[0]->mmio_base); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + clk_disable(pwm[0]->clk); + clk_put(pwm[0]->clk); + for (i = 0; i < NUMBER_OF_PWM_OUTPUTS; i++) + pwm_remove(pwm[i]); + + return 0; +} + +static struct platform_driver ehrpwm_driver = { + .driver = { + .name = "ehrpwm", + .owner = THIS_MODULE, + }, + .probe = ehrpwm_probe, + .remove = __devexit_p(ehrpwm_remove), +}; + +static int __init ehrpwm_init(void) +{ + return platform_driver_register(&ehrpwm_driver); +} + +static void __exit ehrpwm_exit(void) +{ + platform_driver_unregister(&ehrpwm_driver); +} + +module_init(ehrpwm_init); +module_exit(ehrpwm_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-davinci/include/mach/davinci_pwm.h b/arch/arm/mach-davinci/include/mach/davinci_pwm.h index e6db614..2090fed 100644 --- a/arch/arm/mach-davinci/include/mach/davinci_pwm.h +++ b/arch/arm/mach-davinci/include/mach/davinci_pwm.h @@ -27,6 +27,7 @@ struct pwm_device { struct clk *clk; void __iomem *mmio_base; unsigned int use_count; + unsigned char output_number; unsigned int pwm_id; };