| /* |
| * 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(×tamp->lock); |
| count = ioread32(timestamp->reg_base + TMR_CR(0)); |
| spin_unlock(×tamp->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, ¶m_ops, ×tamp, 0644); |
| |
| void timestamp_configure_cnt(void) |
| { |
| int reg; |
| if (!timestamp || timestamp->enabled) |
| return; |
| spin_lock(×tamp->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(×tamp->lock); |
| return; |
| } |
| |
| void timestamp_enable_cnt(void) |
| { |
| unsigned int reg; |
| if (!timestamp || timestamp->enabled) |
| return; |
| |
| spin_lock(×tamp->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(×tamp->lock); |
| |
| return; |
| } |
| EXPORT_SYMBOL(timestamp_enable_cnt); |
| |
| void timestamp_disable_cnt(void) |
| { |
| unsigned int reg; |
| if (!timestamp || !timestamp->enabled) |
| return; |
| |
| spin_lock(×tamp->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(×tamp->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(×tamp->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, ×tamp->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(×tamp->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(×tamp_driver); |
| } |
| |
| static void __exit timestamp_exit(void) |
| { |
| platform_driver_unregister(×tamp_driver); |
| } |
| |
| module_init(timestamp_init); |
| module_exit(timestamp_exit); |
| |
| MODULE_AUTHOR("Eytan Naim"); |
| MODULE_DESCRIPTION("UIO driver for Marvell timestamp"); |
| MODULE_LICENSE("GPL"); |