sdk-hwV1.3/lichee/linux-4.9/drivers/rtc/sunxi_timer_alarm.c

329 lines
8.5 KiB
C

/*
* sunxi_timer_alarm.c
* (C) Copyright 2018-2024
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Frank <frank@allwinnertech.com>
* Martin <wuyan.martin@allwinnertech.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; either version 2 of
* the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/export.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/pm_wakeirq.h>
#include <linux/pm_domain.h>
/*
static const unsigned long long prescale[] = {
1, 2, 4, 8, 16, 32, 64, 128
};
*/
static const unsigned long long clkrate[] = {
32768, //32.768KHz max time: 2^32 / 32768 * 128 seconds = 194 days
24*1000*1000, //24MHz max time: 2^32 / 24M * 128 seconds = 6.36 hours
};
#define PRESCALE_INDEX (7)
#define CLKRATE_INDEX (0)
#define TIMER_IRQ_EN_REG 0x00
#define TIMER_IRQ_EN(val) BIT(val)
#define TIMER_IRQ_ST_REG 0x04
#define TIMER_CTL_REG(val) (0x10 * val + 0x10)
#define TIMER_CTL_ENABLE BIT(0)
#define TIMER_CTL_RELOAD BIT(1)
#define TIMER_CTL_CLK_SRC(val) (((val) & 0x3) << 2)
#define TIMER_CTL_CLK_SRC_OSC24M (1)
#define TIMER_CTL_CLK_PRES(val) (((val) & 0x7) << 4)
#define TIMER_CTL_ONESHOT BIT(7)
#define TIMER_INTVAL_REG(val) (0x10 * (val) + 0x14)
#define TIMER_CNTVAL_REG(val) (0x10 * (val) + 0x18)
//#define REG_TO_SEC(reg, prescale_index, clkrate_index) ((unsigned long long)(((unsigned long long)reg << prescale_index) / clkrate[clkrate_index])) //unit: seconds
#define SEC_TO_REG(sec, prescale_index, clkrate_index) ((unsigned int)(((unsigned long long)sec * clkrate[clkrate_index]) >> prescale_index))
struct _alarm_res {
void __iomem *timer_base;
unsigned long jiffies_start; //for printing
unsigned long long interval; //timer's interrupt interval (unit: seconds)
void *store_mem;
unsigned int size;
int irq;
spinlock_t lock;
};
static struct _alarm_res alarm_res;
static void set_alarm_regs(u8 timer)
{
u32 val = 0;
unsigned long flags;
spin_lock_irqsave(&alarm_res.lock, flags);
/* set Timer1's interval value */
val = SEC_TO_REG(alarm_res.interval, PRESCALE_INDEX, CLKRATE_INDEX);
writel(val, alarm_res.timer_base + TIMER_INTVAL_REG(timer));
/*
* set timer mode (0:continuous, 1:single-shot)
* set clock pre-scale (0x0:1, 0x1:2, 0x2:4, 0x3:8, 0x4:16, 0x5:32, 0x6:64, 0x7:128)
* set clock source (0x0:LOSC, 0x1:OSC24M)
* enable reload value
*/
val = (0x1 << 7) | (PRESCALE_INDEX << 4) | (CLKRATE_INDEX << 2) | (TIMER_CTL_RELOAD);
writel(val, alarm_res.timer_base + TIMER_CTL_REG(timer));
while (!(readl(alarm_res.timer_base + TIMER_CTL_REG(timer)) & 0x2))
;
/* enable interrupt */
writel(TIMER_IRQ_EN(timer), alarm_res.timer_base);
val = readl(alarm_res.timer_base + TIMER_CTL_REG(timer));
writel(val | TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
alarm_res.timer_base + TIMER_CTL_REG(timer));
pr_debug("%s(): expect interval = %llus\n", __func__, alarm_res.interval);
alarm_res.jiffies_start = jiffies;
spin_unlock_irqrestore(&alarm_res.lock, flags);
}
static void clear_alarm_regs(u8 timer)
{
unsigned long flags;
u32 val = readl(alarm_res.timer_base + TIMER_CTL_REG(timer));
pr_debug("%s()\n", __func__);
spin_lock_irqsave(&alarm_res.lock, flags);
writel(val & ~TIMER_CTL_ENABLE, alarm_res.timer_base + TIMER_CTL_REG(timer));
writel(0, alarm_res.timer_base + TIMER_INTVAL_REG(timer)); //clear interval value
writel(0, alarm_res.timer_base + TIMER_CNTVAL_REG(timer)); //clear current value
writel((1 << timer), alarm_res.timer_base + TIMER_IRQ_ST_REG); //clear Timer1's IRQ pending status
spin_unlock_irqrestore(&alarm_res.lock, flags);
}
static ssize_t
interval_show(struct class *class, struct class_attribute *attr, char *buf)
{
unsigned long long value = 0;
unsigned long flags;
value = alarm_res.interval;
return sprintf(buf, "%llu\n", value);
}
static ssize_t
interval_store(struct class *class, struct class_attribute *attr,
const char *buf, size_t count)
{
int err = 0;
unsigned long long value = 0;
unsigned long flags;
err = kstrtoull(buf, 10, &value);
if (err) {
pr_err("%s(): ERROR: invalid value\n", __func__);
return -EINVAL;
}
alarm_res.interval = value;
return count;
}
static struct class_attribute timer_alarm_attrs[] = {
__ATTR(interval, S_IWUSR | S_IRUGO, interval_show, interval_store),
__ATTR_NULL,
};
static struct class timer_alarm_class = {
.name = "timer_alarm",
.owner = THIS_MODULE,
.class_attrs = timer_alarm_attrs,
};
static irqreturn_t alarm_interrupt(int irq, void *dev_id)
{
writel((1 << 1), alarm_res.timer_base + TIMER_IRQ_ST_REG);
pr_debug("%s(): actual interval = %lus\n",
__func__, (jiffies - alarm_res.jiffies_start) / HZ); //note: jiffies does not update when CPU is sleeping
alarm_res.jiffies_start = jiffies; //for continuous mode
return IRQ_HANDLED;
}
static int alarm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource res;
struct device_node *np = pdev->dev.of_node;
int err = 0;
dev_dbg(dev, "%s()\n", __func__);
alarm_res.timer_base = of_iomap(np, 0);
if (!alarm_res.timer_base) {
pr_crit("Can't map registers");
return -ENXIO;
}
if (of_address_to_resource(np, 0, &res))
return -EINVAL;
alarm_res.jiffies_start = 0;
alarm_res.interval = 0;
alarm_res.size = resource_size(&res);
alarm_res.store_mem = kmalloc(alarm_res.size, GFP_KERNEL);
if (!alarm_res.store_mem) {
printk("alloc memory failed!\n");
return -ENOMEM;
}
spin_lock_init(&alarm_res.lock);
alarm_res.irq = irq_of_parse_and_map(np, 0);
if (!alarm_res.irq) {
dev_err(dev, "parse IRQ failed\n");
return -EINVAL;
}
if (of_property_read_bool(np, "wakeup-source")) {
err = device_init_wakeup(dev, true);
if (unlikely(err)) {
dev_err(dev, "device_init_wakeup() failed\n");
return err;
}
if (!device_can_wakeup(dev)) {
dev_err(dev, "not a wakup device\n");
return -EINVAL;
}
err = dev_pm_set_wake_irq(dev, alarm_res.irq);
if (unlikely(err)) {
dev_err(dev, "dev_pm_set_wake_irq() failed\n");
return err;
}
} else {
dev_err(dev, "cannot find wakeup-source\n");
return -EINVAL;
}
err = devm_request_irq(dev, alarm_res.irq, alarm_interrupt, IRQF_TIMER, "alarm", dev);
if (unlikely(err)) {
dev_err(dev, "devm_request_irq() failed\n");
return err;
}
//use sysfs for user space interaction
err = class_register(&timer_alarm_class);
if (unlikely(err)) {
dev_err(dev, "class_register() failed\n");
return err;
}
return 0;
}
static int alarm_remove (struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
dev_dbg(dev, "%s()\n", __func__);
clear_alarm_regs(1);
dev_pm_clear_wake_irq(dev);
device_init_wakeup(dev, false);
class_unregister(&timer_alarm_class);
return 0;
}
int sunxi_alarm_suspend(struct device *dev)
{
memcpy(alarm_res.store_mem, alarm_res.timer_base, alarm_res.size);
clear_alarm_regs(1);
if (alarm_res.interval)
set_alarm_regs(1);
return 0;
}
int sunxi_alarm_resume(struct device *dev)
{
clear_alarm_regs(1);
memcpy(alarm_res.timer_base, alarm_res.store_mem, alarm_res.size);
return 0;
}
static const struct of_device_id dt_ids[] = {
{ .compatible = "allwinner,timer_alarm"},
{},
};
MODULE_DEVICE_TABLE(of, dt_ids);
const struct dev_pm_ops sunxi_alarm_pm_ops = {
.suspend = sunxi_alarm_suspend,
.resume = sunxi_alarm_resume,
};
static struct platform_driver alarm_driver = {
.driver = {
.name = "timer_alarm",
.pm = &sunxi_alarm_pm_ops,
.of_match_table = of_match_ptr(dt_ids),
},
.probe = alarm_probe,
.remove = alarm_remove,
};
static int alarm_init(void)
{
int ret;
ret = platform_driver_register(&alarm_driver);
if (ret)
pr_err("platform_driver_register() failed: %d\n", ret);
return ret;
}
static void alarm_exit(void)
{
platform_driver_unregister(&alarm_driver);
}
module_init(alarm_init);
module_exit(alarm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Frank & Martin");
MODULE_DESCRIPTION("Allwinner timer alarm");