blob: dfbb255d333a8f1452c325cbf5072272dc14b15f [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2021 Amlogic, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/major.h>
#include <linux/list.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/gpio/consumer.h>
#include <linux/list.h>
#include <linux/regmap.h>
#include <linux/clk.h>
#include <linux/hrtimer.h>
#include <linux/amlogic/glb_timer.h>
#include "global_timer.h"
//#define GLB_TIMER_TEST_CASE
static struct meson_glb_timer *gbt;
static struct regmap_config meson_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static int meson_glb_timer_dt_parser(struct platform_device *pdev)
{
int i;
const char *name = NULL;
struct meson_glb_timer *glb_timer;
resource_size_t size;
glb_timer = platform_get_drvdata(pdev);
/* map reg */
for (i = 0; i < REG_NUM; i++) {
glb_timer->reg[i] = devm_of_iomap(&pdev->dev, pdev->dev.of_node, i, &size);
if (IS_ERR(glb_timer->reg[i]))
return PTR_ERR(glb_timer->reg[i]);
of_property_read_string_index(pdev->dev.of_node, "reg-names", i, &name);
meson_regmap_config.max_register = size - 4;
meson_regmap_config.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%pOFn-%s",
pdev->dev.of_node, name);
if (!meson_regmap_config.name)
return -ENOMEM;
glb_timer->regmap[i] = devm_regmap_init_mmio(&pdev->dev, glb_timer->reg[i],
&meson_regmap_config);
if (IS_ERR(glb_timer->regmap[i]))
return PTR_ERR(glb_timer->regmap[i]);
}
return 0;
}
/* Timer Module TopCtrl*/
static inline void meson_glb_topctl(struct meson_glb_timer *glb, unsigned int ops,
unsigned long long ts)
{
unsigned int ts_l;
unsigned int ts_h;
struct regmap *regmap = glb->regmap[REG_TOPCTL];
switch (ops) {
case RESET_TIMER:
regmap_update_bits(regmap, TOP_CTRL0, BIT(2), BIT(2));
regmap_update_bits(regmap, TOP_CTRL0, BIT(2), 0);
break;
case HOLD_COUNTER:
regmap_update_bits(regmap, TOP_CTRL0, BIT(1), BIT(1));
break;
case CLEAR_TIMER:
regmap_update_bits(regmap, TOP_CTRL0, BIT(3), BIT(3));
break;
case SET_FORCE_COUNT:
ts_l = ts & GENMASK_ULL(31, 0);
ts_h = (ts >> 32) & GENMASK_ULL(31, 0);
regmap_write(regmap, TOP_CTRL1, ts_l);
regmap_write(regmap, TOP_CTRL2, ts_h);
regmap_update_bits(regmap, TOP_CTRL0, BIT(0), BIT(0));
break;
default:
dev_err(&glb->pdev->dev, "wrong ops\n");
break;
}
}
static unsigned long long meson_glb_get_cur_count(struct meson_glb_timer *glb)
{
unsigned int ts_l;
unsigned int ts_h;
unsigned long long ts;
struct regmap *regmap = glb->regmap[REG_TOPCTL];
regmap_read(regmap, TOP_TS0, &ts_l);
regmap_read(regmap, TOP_TS1, &ts_h);
ts = ts_h;
return (ts << 32) | ts_l;
}
/* Trigger Select Source Module */
static inline void meson_glb_trig_src_enable(struct meson_glb_timer *glb, u8 srcn, bool on)
{
regmap_update_bits(glb->regmap[REG_SRCSEL], SRC_OFFSET(TRIG_SRC0_CTRL0, srcn), BIT(31),
on << 31);
}
static inline int meson_glb_trig_src_config(struct meson_glb_timer *glb, u8 srcn,
unsigned int func)
{
struct regmap *regmap = glb->regmap[REG_SRCSEL];
/* hold high bit when read low bit */
regmap_update_bits(regmap, SRC_OFFSET(TRIG_SRC0_CTRL0, srcn), BIT(30), BIT(30));
/* set trigger method */
regmap_update_bits(regmap, SRC_OFFSET(TRIG_SRC0_CTRL0, srcn), GENMASK(17, 16),
(func & GENMASK(1, 0)) << 16);
/* set if oneshot mode */
regmap_update_bits(regmap, SRC_OFFSET(TRIG_SRC0_CTRL0, srcn), BIT(29),
((func & BIT(2)) << 29));
return 0;
}
static inline void meson_glb_trig_src_oneshot(struct meson_glb_timer *glb, u8 srcn)
{
regmap_update_bits(glb->regmap[REG_SRCSEL], SRC_OFFSET(TRIG_SRC0_CTRL0, srcn),
BIT(1), BIT(1));
}
static inline void meson_glb_trig_src_swtrigger(struct meson_glb_timer *glb, u8 srcn)
{
regmap_update_bits(glb->regmap[REG_SRCSEL], SRC_OFFSET(TRIG_SRC0_CTRL0, srcn),
BIT(0), BIT(0));
}
static inline unsigned long long meson_glb_trig_src_get_count(struct meson_glb_timer *glb, u8 srcn)
{
unsigned int ts_l;
unsigned int ts_h;
unsigned long long ts;
struct regmap *regmap = glb->regmap[REG_SRCSEL];
regmap_read(regmap, SRC_OFFSET(TRIG_SRC0_TS_L, srcn), &ts_l);
regmap_read(regmap, SRC_OFFSET(TRIG_SRC0_TS_H, srcn), &ts_h);
ts = ts_h;
return (ts << 32) | ts_l;
}
/* Output Module */
static inline void meson_glb_output_enable(struct meson_glb_timer *glb, u8 srcn, bool on)
{
regmap_update_bits(glb->regmap[REG_OUTCTL], SRC_OFFSET(OUTPUT_CTRL0, srcn), BIT(31),
on << 31);
}
static inline void meson_glb_output_mode_config(struct meson_glb_timer *glb, u8 srcn,
unsigned int mode)
{
struct regmap *regmap = glb->regmap[REG_OUTCTL];
if (mode & SOFTWARE_MODE) {
regmap_update_bits(regmap, SRC_OFFSET(OUTPUT_CTRL0, srcn), BIT(4), BIT(4));
regmap_update_bits(regmap, SRC_OFFSET(OUTPUT_CTRL0, srcn), BIT(2), BIT(2));
}
if (mode & ONESHOT_MODE)
regmap_update_bits(regmap, SRC_OFFSET(OUTPUT_CTRL0, srcn), BIT(3), BIT(3));
}
static inline void meson_glb_output_sw_start(struct meson_glb_timer *glb, u8 srcn)
{
regmap_update_bits(glb->regmap[REG_OUTCTL], SRC_OFFSET(OUTPUT_CTRL0, srcn), BIT(1), BIT(1));
}
static inline void meson_glb_output_sw_stop(struct meson_glb_timer *glb, u8 srcn)
{
regmap_update_bits(glb->regmap[REG_OUTCTL], SRC_OFFSET(OUTPUT_CTRL0, srcn), BIT(0), BIT(0));
}
static inline void meson_glb_output_fource_output(struct meson_glb_timer *glb, u8 srcn,
enum output_level level)
{
struct regmap *regmap = glb->regmap[REG_OUTCTL];
/* output level */
regmap_update_bits(regmap, SRC_OFFSET(OUTPUT_CTRL0, srcn), GENMASK(17, 16),
BIT(17) | (level << 16));
/* change output dir */
regmap_update_bits(regmap, SRC_OFFSET(OUTPUT_CTRL0, srcn), GENMASK(19, 18), BIT(19));
}
static inline void meson_glb_output_fource_input(struct meson_glb_timer *glb, u8 srcn)
{
regmap_update_bits(glb->regmap[REG_OUTCTL], SRC_OFFSET(OUTPUT_CTRL0, srcn),
GENMASK(19, 18), GENMASK(19, 18));
}
static inline void meson_glb_output_pulse_config(struct meson_glb_timer *glb, u8 srcn,
bool pulse_polarity, u32 pulse_width,
u32 pulse_interval, unsigned int pulse_num)
{
struct regmap *regmap = glb->regmap[REG_OUTCTL];
if (pulse_polarity)
regmap_update_bits(regmap, SRC_OFFSET(OUTPUT_CTRL0, srcn), BIT(28), BIT(28));
regmap_write(regmap, SRC_OFFSET(OUTPUT_PULSE_WDITH, srcn), pulse_width);
regmap_write(regmap, SRC_OFFSET(OUTPUT_INTERVAL, srcn), pulse_interval);
regmap_write(regmap, SRC_OFFSET(OUTPUT_ST0, srcn), pulse_num - 1);
}
static inline void meson_glb_output_set_expire(struct meson_glb_timer *glb, u8 srcn,
unsigned long long ts)
{
unsigned int ts_l;
unsigned int ts_h;
struct regmap *regmap = glb->regmap[REG_OUTCTL];
ts_l = ts & GENMASK_ULL(31, 0);
ts_h = (ts >> 32) & GENMASK_ULL(31, 0);
regmap_write(regmap, SRC_OFFSET(OUTPUT_EXPIRES_TS_L, srcn), ts_l);
regmap_write(regmap, SRC_OFFSET(OUTPUT_EXPIRES_TS_H, srcn), ts_h);
}
/* Output Source Select */
static inline void meson_glb_set_output_src(struct meson_glb_timer *glb, u8 srcn)
{
regmap_update_bits(glb->regmap[REG_OUTSEL], (srcn << 2),
BIT(31) | GENMASK(3, 0), srcn | BIT(31));
}
int glb_timer_mipi_config(u8 srcn, unsigned int trig)
{
if (!gbt) {
pr_err("gbl timer not probe yet\n");
return -ENODEV;
}
meson_glb_trig_src_config(gbt, srcn, trig);
meson_glb_trig_src_enable(gbt, srcn, 1);
return 0;
}
EXPORT_SYMBOL(glb_timer_mipi_config);
unsigned long long glb_timer_get_counter(u8 srcn)
{
if (!gbt) {
pr_err("gbl timer not probe yet\n");
return -ENODEV;
}
return meson_glb_trig_src_get_count(gbt, srcn);
}
EXPORT_SYMBOL(glb_timer_get_counter);
static ssize_t current_val_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct meson_glb_timer *glb_timer = dev_get_drvdata(dev);
ssize_t ret = 0;
ret = sprintf(buf, "%lld\n", meson_glb_get_cur_count(glb_timer));
return ret;
}
static ssize_t reset_timer_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct meson_glb_timer *glb_timer = dev_get_drvdata(dev);
meson_glb_topctl(glb_timer, RESET_TIMER, 0);
return size;
}
static DEVICE_ATTR_RO(current_val);
static DEVICE_ATTR_WO(reset_timer);
static struct attribute *glb_timer_attrs[] = {
&dev_attr_current_val.attr,
&dev_attr_reset_timer.attr,
NULL
};
static const struct attribute_group glb_timer_attribute_group = {
.attrs = glb_timer_attrs,
.name = "glb_timer"
};
#ifdef GLB_TIMER_TEST_CASE
static unsigned int test_time;
static enum hrtimer_restart tst_hrtimer_hanlder(struct hrtimer *hrtimer)
{
struct meson_glb_timer *glb;
glb = container_of(hrtimer, struct meson_glb_timer, tst_hrtimer);
if (++test_time % 5 == 0) {
dev_info(&glb->pdev->dev, "reset glb timer !!\n");
meson_glb_topctl(glb, RESET_TIMER, 0);
} else {
dev_info(&glb->pdev->dev, "read current count %lld\n",
meson_glb_get_cur_count(glb));
}
if (test_time != 30)
hrtimer_start(&glb->tst_hrtimer, ktime_set(0, 100 * 1000 * 1000), HRTIMER_MODE_REL);
return HRTIMER_NORESTART;
}
#endif
static int meson_glb_timer_probe(struct platform_device *pdev)
{
int ret;
struct meson_glb_timer *glb_timer;
glb_timer = devm_kzalloc(&pdev->dev, sizeof(*glb_timer), GFP_KERNEL);
/* set clk */
glb_timer->glb_clk = devm_clk_get(&pdev->dev, "glb_clk");
if (IS_ERR(glb_timer->glb_clk)) {
dev_err(&pdev->dev, "Can't find glb_clk\n");
return PTR_ERR(glb_timer->glb_clk);
}
ret = clk_prepare_enable(glb_timer->glb_clk);
if (ret) {
dev_err(&pdev->dev, "clk enable failed\n");
return ret;
}
platform_set_drvdata(pdev, glb_timer);
glb_timer->pdev = pdev;
ret = meson_glb_timer_dt_parser(pdev);
if (ret)
return ret;
/* /sys/devices/platform/soc/fe000000.apb4/fe08e000.global_timer/glb_timer/ */
ret = sysfs_create_group(&pdev->dev.kobj, &glb_timer_attribute_group);
#ifdef GLB_TIMER_TEST_CASE
hrtimer_init(&glb_timer->tst_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
glb_timer->tst_hrtimer.function = tst_hrtimer_hanlder;
/* 100ms */
hrtimer_start(&glb_timer->tst_hrtimer, ktime_set(0, 100 * 1000 * 1000), HRTIMER_MODE_REL);
#endif
gbt = glb_timer;
return 0;
}
static int meson_glb_timer_remove(struct platform_device *pdev)
{
return 0;
}
#ifdef CONFIG_PM
static int meson_glb_timer_resume(struct device *dev)
{
return 0;
}
static int meson_glb_timer_suspend(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops meson_glb_timer_pm_ops = {
.suspend = meson_glb_timer_suspend,
.resume = meson_glb_timer_resume,
};
#endif
static const struct of_device_id meson_glb_timer_dt_match[] = {
{
.compatible = "amlogic, meson-glb-timer",
},
{}
};
static struct platform_driver meson_glb_timer_driver = {
.probe = meson_glb_timer_probe,
.remove = meson_glb_timer_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = meson_glb_timer_dt_match,
#ifdef CONFIG_PM
.pm = &meson_glb_timer_pm_ops,
#endif
},
};
static int __init meson_glb_timer_driver_init(void)
{
return platform_driver_register(&meson_glb_timer_driver);
}
static void __exit meson_glb_timer_driver_exit(void)
{
platform_driver_unregister(&meson_glb_timer_driver);
}
module_init(meson_glb_timer_driver_init);
module_exit(meson_glb_timer_driver_exit);
MODULE_AUTHOR("AMLOGIC");
MODULE_DESCRIPTION("AMLOGIC GLOBAL TIMER DRIVER");
MODULE_LICENSE("GPL v2");