blob: 679fc7aa3cb611ebde93f98548fa306ff4263591 [file] [log] [blame]
/*
* Marvell Timestamp UIO driver
*
* Oleksandr Krol <akrol@marvell.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
* (c) 2012
*
*/
#include <linux/uio_driver.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/io.h>
#include <linux/of.h>
#include <soc/asr/uio_timestamp.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <asm/atomic.h>
#include <linux/delay.h>
#include <linux/cputype.h>
#include <soc/asr/regs-timers.h>
/* vals: */
#define TMR_SOURCE_32K (0x01)
#define TMR_COUNT_EN (0x01)
#define TMR_COUNT_PERIODIC (0x00)
#define ASR1826_TIMR_CR_OFFSET (0x28)
#define NONE_ASR1826_TIMR_CR_OFFSET (0x90)
#ifdef CONFIG_CPU_ASR1903
#undef TMR_CCR
#define TMR_CCR (0x0000)
#undef TMR_CER
#define TMR_CER (0x0084)
#undef TMR_CMR
#define TMR_CMR (0x0088)
#endif
struct timestamp {
void *reg_base;
struct uio_info uio_info;
u32 timer_cnt;
spinlock_t lock;
bool enabled;
};
static struct timestamp *timestamp;
static int show_counter(char *buf, const struct kernel_param *kp)
{
int count;
if (!timestamp || !timestamp->enabled)
return 0;
spin_lock(&timestamp->lock);
count = ioread32(timestamp->reg_base + TMR_CR(0));
spin_unlock(&timestamp->lock);
return sprintf(buf, "counter: %d\t total time: %d [sec]\n", count,
count >> 15);
return 0;
}
static int reset_counter(const char *val, const struct kernel_param *kp)
{
int sleep;
if (!timestamp || !timestamp->enabled)
return 0;
if (kstrtoint(val, 10, &sleep))
return -EINVAL;
timestamp_disable_cnt();
if (sleep != 0)
msleep(sleep);
timestamp_enable_cnt();
return 0;
}
static const struct kernel_param_ops param_ops = {
.get = show_counter,
.set = reset_counter,
};
module_param_cb(timestamp, &param_ops, &timestamp, 0644);
void timestamp_configure_cnt(void)
{
int reg;
if (!timestamp || timestamp->enabled)
return;
spin_lock(&timestamp->lock);
if (timestamp->timer_cnt == 2) {
/* Choose clk source: */
reg = ioread32(timestamp->reg_base + TMR_CCR);
reg &= ~(0x3 << 5);
reg |= (0x2 << 5);
iowrite32(reg, timestamp->reg_base + TMR_CCR);
} else {
/* Choose clk source: */
reg = ioread32(timestamp->reg_base + TMR_CCR);
reg &= ~(0x3 << (timestamp->timer_cnt * 2));
reg |= (TMR_SOURCE_32K << (timestamp->timer_cnt * 2));
iowrite32(reg, timestamp->reg_base + TMR_CCR);
}
/* Set periodic mode: */
reg = ioread32(timestamp->reg_base + TMR_CMR);
reg |= (TMR_COUNT_PERIODIC << timestamp->timer_cnt);
iowrite32(reg, timestamp->reg_base + TMR_CMR);
/* Enable: */
reg = ioread32(timestamp->reg_base + TMR_CER);
reg |= (TMR_COUNT_EN << timestamp->timer_cnt);
iowrite32(reg, timestamp->reg_base + TMR_CER);
timestamp->enabled = true;
spin_unlock(&timestamp->lock);
return;
}
void timestamp_enable_cnt(void)
{
unsigned int reg;
if (!timestamp || timestamp->enabled)
return;
spin_lock(&timestamp->lock);
reg = ioread32(timestamp->reg_base + TMR_CER);
reg |= (TMR_COUNT_EN << timestamp->timer_cnt);
iowrite32(reg, timestamp->reg_base + TMR_CER);
timestamp->enabled = true;
spin_unlock(&timestamp->lock);
return;
}
EXPORT_SYMBOL(timestamp_enable_cnt);
void timestamp_disable_cnt(void)
{
unsigned int reg;
if (!timestamp || !timestamp->enabled)
return;
spin_lock(&timestamp->lock);
reg = ioread32(timestamp->reg_base + TMR_CER);
reg &= ~(TMR_COUNT_EN << timestamp->timer_cnt);
iowrite32(reg, timestamp->reg_base + TMR_CER);
timestamp->enabled = false;
spin_unlock(&timestamp->lock);
return;
}
EXPORT_SYMBOL(timestamp_disable_cnt);
static int timestamp_probe(struct platform_device *pdev)
{
/* reg[0] holds start address and reg[1] holds size */
u32 reg[2];
int ret = 0;
char *dev_name;
char *version;
int proplen;
const __be32 *prop;
struct device_node *np = pdev->dev.of_node;
if (of_property_read_u32_array(pdev->dev.of_node, "reg", reg, 2)) {
pr_err("%s: failed to read regs\n", __func__);
return 0;
}
if (of_property_read_string(pdev->dev.of_node, "dev_name",
(const char **)(&dev_name))) {
pr_err("%s: failed to read device name\n", __func__);
return 0;
}
if (of_property_read_string(pdev->dev.of_node, "version",
(const char **)(&version))) {
pr_err("%s: failed to read driver version\n", __func__);
return 0;
}
timestamp = kzalloc(sizeof(struct timestamp), GFP_KERNEL);
if (!timestamp) {
pr_err("%s: out of memory\n", __func__);
return -ENOMEM;
}
spin_lock_init(&timestamp->lock);
timestamp->reg_base = ioremap(reg[0], reg[1]);
if (!timestamp->reg_base) {
pr_err("%s: can`t remap register area\n", __func__);
ret = -ENOMEM;
goto err;
}
prop = of_get_property(np, "timer-cnt", &proplen);
if (!prop) {
timestamp->timer_cnt = 0;
} else {
timestamp->timer_cnt = be32_to_cpup(prop);
}
pr_err("tmrcnt: %d\n", timestamp->timer_cnt);
platform_set_drvdata(pdev, timestamp);
timestamp->uio_info.name = dev_name;
timestamp->uio_info.version = version;
if (!cpu_is_pxa1826() && !cpu_is_asr1903()) {
timestamp->uio_info.mem[0].addr = reg[0]
+ NONE_ASR1826_TIMR_CR_OFFSET - ASR1826_TIMR_CR_OFFSET;
timestamp->uio_info.mem[0].internal_addr = timestamp->reg_base
+ NONE_ASR1826_TIMR_CR_OFFSET - ASR1826_TIMR_CR_OFFSET;
} else {
timestamp->uio_info.mem[0].addr = reg[0];
timestamp->uio_info.mem[0].internal_addr = timestamp->reg_base;
}
timestamp->uio_info.mem[0].memtype = UIO_MEM_PHYS; /* chage to logical */
timestamp->uio_info.mem[0].size = reg[1];
timestamp->uio_info.mem[0].name = dev_name;
timestamp->uio_info.priv = timestamp;
if (uio_register_device(&pdev->dev, &timestamp->uio_info)) {
iounmap(timestamp->reg_base);
pr_err("%s: register device fails!\n", __func__);
goto err;
}
timestamp_configure_cnt();
return 0;
err:
kfree(timestamp);
return ret;
}
static int timestamp_remove(struct platform_device *pdev)
{
struct timestamp *timestamp = platform_get_drvdata(pdev);
uio_unregister_device(&timestamp->uio_info);
iounmap(timestamp->reg_base);
kfree(timestamp);
return 0;
}
static struct of_device_id timers_dt_ids[] = {
{ .compatible = "marvell,timer1", },
};
static struct platform_driver timestamp_driver = {
.probe = timestamp_probe,
.remove = timestamp_remove,
.driver = {
.name = "timestamp",
.of_match_table = timers_dt_ids,
.owner = THIS_MODULE
},
};
static int __init timestamp_init(void)
{
return platform_driver_register(&timestamp_driver);
}
static void __exit timestamp_exit(void)
{
platform_driver_unregister(&timestamp_driver);
}
module_init(timestamp_init);
module_exit(timestamp_exit);
MODULE_AUTHOR("Eytan Naim");
MODULE_DESCRIPTION("UIO driver for Marvell timestamp");
MODULE_LICENSE("GPL");