-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
unicore32 core architecture: timer and time handling
This patch implements timer and time. RTC and PWM device drivers are also here. Signed-off-by: Guan Xuetao <gxt@mprc.pku.edu.cn>
- Loading branch information
GuanXuetao
committed
Mar 17, 2011
1 parent
10c9c10
commit 02b2ee1
Showing
4 changed files
with
825 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* linux/arch/unicore32/include/asm/timex.h | ||
* | ||
* Code specific to PKUnity SoC and UniCore ISA | ||
* | ||
* Copyright (C) 2001-2010 GUAN Xue-tao | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
* published by the Free Software Foundation. | ||
*/ | ||
|
||
#ifndef __UNICORE_TIMEX_H__ | ||
#define __UNICORE_TIMEX_H__ | ||
|
||
#ifdef CONFIG_ARCH_FPGA | ||
|
||
/* in FPGA, APB clock is 33M, and OST clock is 32K, */ | ||
/* so, 1M is selected for timer interrupt correctly */ | ||
#define CLOCK_TICK_RATE (32*1024) | ||
|
||
#endif | ||
|
||
#if defined(CONFIG_PUV3_DB0913) \ | ||
|| defined(CONFIG_PUV3_NB0916) \ | ||
|| defined(CONFIG_PUV3_SMW0919) | ||
|
||
#define CLOCK_TICK_RATE (14318000) | ||
|
||
#endif | ||
|
||
#include <asm-generic/timex.h> | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
/* | ||
* linux/arch/unicore32/kernel/pwm.c | ||
* | ||
* Code specific to PKUnity SoC and UniCore ISA | ||
* | ||
* Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> | ||
* Copyright (C) 2001-2010 Guan Xuetao | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
* published by the Free Software Foundation. | ||
*/ | ||
|
||
#include <linux/module.h> | ||
#include <linux/kernel.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/slab.h> | ||
#include <linux/err.h> | ||
#include <linux/clk.h> | ||
#include <linux/io.h> | ||
#include <linux/pwm.h> | ||
|
||
#include <asm/div64.h> | ||
#include <mach/hardware.h> | ||
|
||
struct pwm_device { | ||
struct list_head node; | ||
struct platform_device *pdev; | ||
|
||
const char *label; | ||
struct clk *clk; | ||
int clk_enabled; | ||
|
||
unsigned int use_count; | ||
unsigned int pwm_id; | ||
}; | ||
|
||
/* | ||
* period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE | ||
* duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE | ||
*/ | ||
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) | ||
{ | ||
unsigned long long c; | ||
unsigned long period_cycles, prescale, pv, dc; | ||
|
||
if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) | ||
return -EINVAL; | ||
|
||
c = clk_get_rate(pwm->clk); | ||
c = c * period_ns; | ||
do_div(c, 1000000000); | ||
period_cycles = c; | ||
|
||
if (period_cycles < 1) | ||
period_cycles = 1; | ||
prescale = (period_cycles - 1) / 1024; | ||
pv = period_cycles / (prescale + 1) - 1; | ||
|
||
if (prescale > 63) | ||
return -EINVAL; | ||
|
||
if (duty_ns == period_ns) | ||
dc = OST_PWMDCCR_FDCYCLE; | ||
else | ||
dc = (pv + 1) * duty_ns / period_ns; | ||
|
||
/* NOTE: the clock to PWM has to be enabled first | ||
* before writing to the registers | ||
*/ | ||
clk_enable(pwm->clk); | ||
OST_PWMPWCR = prescale; | ||
OST_PWMDCCR = pv - dc; | ||
OST_PWMPCR = pv; | ||
clk_disable(pwm->clk); | ||
|
||
return 0; | ||
} | ||
EXPORT_SYMBOL(pwm_config); | ||
|
||
int pwm_enable(struct pwm_device *pwm) | ||
{ | ||
int rc = 0; | ||
|
||
if (!pwm->clk_enabled) { | ||
rc = clk_enable(pwm->clk); | ||
if (!rc) | ||
pwm->clk_enabled = 1; | ||
} | ||
return rc; | ||
} | ||
EXPORT_SYMBOL(pwm_enable); | ||
|
||
void pwm_disable(struct pwm_device *pwm) | ||
{ | ||
if (pwm->clk_enabled) { | ||
clk_disable(pwm->clk); | ||
pwm->clk_enabled = 0; | ||
} | ||
} | ||
EXPORT_SYMBOL(pwm_disable); | ||
|
||
static DEFINE_MUTEX(pwm_lock); | ||
static LIST_HEAD(pwm_list); | ||
|
||
struct pwm_device *pwm_request(int pwm_id, const char *label) | ||
{ | ||
struct pwm_device *pwm; | ||
int found = 0; | ||
|
||
mutex_lock(&pwm_lock); | ||
|
||
list_for_each_entry(pwm, &pwm_list, node) { | ||
if (pwm->pwm_id == pwm_id) { | ||
found = 1; | ||
break; | ||
} | ||
} | ||
|
||
if (found) { | ||
if (pwm->use_count == 0) { | ||
pwm->use_count++; | ||
pwm->label = label; | ||
} else | ||
pwm = ERR_PTR(-EBUSY); | ||
} else | ||
pwm = ERR_PTR(-ENOENT); | ||
|
||
mutex_unlock(&pwm_lock); | ||
return pwm; | ||
} | ||
EXPORT_SYMBOL(pwm_request); | ||
|
||
void pwm_free(struct pwm_device *pwm) | ||
{ | ||
mutex_lock(&pwm_lock); | ||
|
||
if (pwm->use_count) { | ||
pwm->use_count--; | ||
pwm->label = NULL; | ||
} else | ||
pr_warning("PWM device already freed\n"); | ||
|
||
mutex_unlock(&pwm_lock); | ||
} | ||
EXPORT_SYMBOL(pwm_free); | ||
|
||
static inline void __add_pwm(struct pwm_device *pwm) | ||
{ | ||
mutex_lock(&pwm_lock); | ||
list_add_tail(&pwm->node, &pwm_list); | ||
mutex_unlock(&pwm_lock); | ||
} | ||
|
||
static struct pwm_device *pwm_probe(struct platform_device *pdev, | ||
unsigned int pwm_id, struct pwm_device *parent_pwm) | ||
{ | ||
struct pwm_device *pwm; | ||
struct resource *r; | ||
int ret = 0; | ||
|
||
pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); | ||
if (pwm == NULL) { | ||
dev_err(&pdev->dev, "failed to allocate memory\n"); | ||
return ERR_PTR(-ENOMEM); | ||
} | ||
|
||
pwm->clk = clk_get(NULL, "OST_CLK"); | ||
if (IS_ERR(pwm->clk)) { | ||
ret = PTR_ERR(pwm->clk); | ||
goto err_free; | ||
} | ||
pwm->clk_enabled = 0; | ||
|
||
pwm->use_count = 0; | ||
pwm->pwm_id = pwm_id; | ||
pwm->pdev = pdev; | ||
|
||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
if (r == NULL) { | ||
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 == NULL) { | ||
dev_err(&pdev->dev, "failed to request memory resource\n"); | ||
ret = -EBUSY; | ||
goto err_free_clk; | ||
} | ||
|
||
__add_pwm(pwm); | ||
platform_set_drvdata(pdev, pwm); | ||
return pwm; | ||
|
||
err_free_clk: | ||
clk_put(pwm->clk); | ||
err_free: | ||
kfree(pwm); | ||
return ERR_PTR(ret); | ||
} | ||
|
||
static int __devinit puv3_pwm_probe(struct platform_device *pdev) | ||
{ | ||
struct pwm_device *pwm = pwm_probe(pdev, pdev->id, NULL); | ||
|
||
if (IS_ERR(pwm)) | ||
return PTR_ERR(pwm); | ||
|
||
return 0; | ||
} | ||
|
||
static int __devexit pwm_remove(struct platform_device *pdev) | ||
{ | ||
struct pwm_device *pwm; | ||
struct resource *r; | ||
|
||
pwm = platform_get_drvdata(pdev); | ||
if (pwm == NULL) | ||
return -ENODEV; | ||
|
||
mutex_lock(&pwm_lock); | ||
list_del(&pwm->node); | ||
mutex_unlock(&pwm_lock); | ||
|
||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
release_mem_region(r->start, resource_size(r)); | ||
|
||
clk_put(pwm->clk); | ||
kfree(pwm); | ||
return 0; | ||
} | ||
|
||
static struct platform_driver puv3_pwm_driver = { | ||
.driver = { | ||
.name = "PKUnity-v3-PWM", | ||
}, | ||
.probe = puv3_pwm_probe, | ||
.remove = __devexit_p(pwm_remove), | ||
}; | ||
|
||
static int __init pwm_init(void) | ||
{ | ||
int ret = 0; | ||
|
||
ret = platform_driver_register(&puv3_pwm_driver); | ||
if (ret) { | ||
printk(KERN_ERR "failed to register puv3_pwm_driver\n"); | ||
return ret; | ||
} | ||
|
||
return ret; | ||
} | ||
arch_initcall(pwm_init); | ||
|
||
static void __exit pwm_exit(void) | ||
{ | ||
platform_driver_unregister(&puv3_pwm_driver); | ||
} | ||
module_exit(pwm_exit); | ||
|
||
MODULE_LICENSE("GPL v2"); |
Oops, something went wrong.