linux/drivers/clocksource/ingenic-ost.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * JZ47xx SoCs TCU Operating System Timer driver
   4 *
   5 * Copyright (C) 2016 Maarten ter Huurne <maarten@treewalker.org>
   6 * Copyright (C) 2020 Paul Cercueil <paul@crapouillou.net>
   7 */
   8
   9#include <linux/clk.h>
  10#include <linux/clocksource.h>
  11#include <linux/mfd/ingenic-tcu.h>
  12#include <linux/mfd/syscon.h>
  13#include <linux/of.h>
  14#include <linux/platform_device.h>
  15#include <linux/pm.h>
  16#include <linux/regmap.h>
  17#include <linux/sched_clock.h>
  18
  19#define TCU_OST_TCSR_MASK       0xffc0
  20#define TCU_OST_TCSR_CNT_MD     BIT(15)
  21
  22#define TCU_OST_CHANNEL         15
  23
  24/*
  25 * The TCU_REG_OST_CNT{L,R} from <linux/mfd/ingenic-tcu.h> are only for the
  26 * regmap; these are for use with the __iomem pointer.
  27 */
  28#define OST_REG_CNTL            0x4
  29#define OST_REG_CNTH            0x8
  30
  31struct ingenic_ost_soc_info {
  32        bool is64bit;
  33};
  34
  35struct ingenic_ost {
  36        void __iomem *regs;
  37        struct clk *clk;
  38
  39        struct clocksource cs;
  40};
  41
  42static struct ingenic_ost *ingenic_ost;
  43
  44static u64 notrace ingenic_ost_read_cntl(void)
  45{
  46        /* Read using __iomem pointer instead of regmap to avoid locking */
  47        return readl(ingenic_ost->regs + OST_REG_CNTL);
  48}
  49
  50static u64 notrace ingenic_ost_read_cnth(void)
  51{
  52        /* Read using __iomem pointer instead of regmap to avoid locking */
  53        return readl(ingenic_ost->regs + OST_REG_CNTH);
  54}
  55
  56static u64 notrace ingenic_ost_clocksource_readl(struct clocksource *cs)
  57{
  58        return ingenic_ost_read_cntl();
  59}
  60
  61static u64 notrace ingenic_ost_clocksource_readh(struct clocksource *cs)
  62{
  63        return ingenic_ost_read_cnth();
  64}
  65
  66static int __init ingenic_ost_probe(struct platform_device *pdev)
  67{
  68        const struct ingenic_ost_soc_info *soc_info;
  69        struct device *dev = &pdev->dev;
  70        struct ingenic_ost *ost;
  71        struct clocksource *cs;
  72        struct regmap *map;
  73        unsigned long rate;
  74        int err;
  75
  76        soc_info = device_get_match_data(dev);
  77        if (!soc_info)
  78                return -EINVAL;
  79
  80        ost = devm_kzalloc(dev, sizeof(*ost), GFP_KERNEL);
  81        if (!ost)
  82                return -ENOMEM;
  83
  84        ingenic_ost = ost;
  85
  86        ost->regs = devm_platform_ioremap_resource(pdev, 0);
  87        if (IS_ERR(ost->regs))
  88                return PTR_ERR(ost->regs);
  89
  90        map = device_node_to_regmap(dev->parent->of_node);
  91        if (!map) {
  92                dev_err(dev, "regmap not found");
  93                return -EINVAL;
  94        }
  95
  96        ost->clk = devm_clk_get(dev, "ost");
  97        if (IS_ERR(ost->clk))
  98                return PTR_ERR(ost->clk);
  99
 100        err = clk_prepare_enable(ost->clk);
 101        if (err)
 102                return err;
 103
 104        /* Clear counter high/low registers */
 105        if (soc_info->is64bit)
 106                regmap_write(map, TCU_REG_OST_CNTL, 0);
 107        regmap_write(map, TCU_REG_OST_CNTH, 0);
 108
 109        /* Don't reset counter at compare value. */
 110        regmap_update_bits(map, TCU_REG_OST_TCSR,
 111                           TCU_OST_TCSR_MASK, TCU_OST_TCSR_CNT_MD);
 112
 113        rate = clk_get_rate(ost->clk);
 114
 115        /* Enable OST TCU channel */
 116        regmap_write(map, TCU_REG_TESR, BIT(TCU_OST_CHANNEL));
 117
 118        cs = &ost->cs;
 119        cs->name        = "ingenic-ost";
 120        cs->rating      = 320;
 121        cs->flags       = CLOCK_SOURCE_IS_CONTINUOUS;
 122        cs->mask        = CLOCKSOURCE_MASK(32);
 123
 124        if (soc_info->is64bit)
 125                cs->read = ingenic_ost_clocksource_readl;
 126        else
 127                cs->read = ingenic_ost_clocksource_readh;
 128
 129        err = clocksource_register_hz(cs, rate);
 130        if (err) {
 131                dev_err(dev, "clocksource registration failed");
 132                clk_disable_unprepare(ost->clk);
 133                return err;
 134        }
 135
 136        if (soc_info->is64bit)
 137                sched_clock_register(ingenic_ost_read_cntl, 32, rate);
 138        else
 139                sched_clock_register(ingenic_ost_read_cnth, 32, rate);
 140
 141        return 0;
 142}
 143
 144static int __maybe_unused ingenic_ost_suspend(struct device *dev)
 145{
 146        struct ingenic_ost *ost = dev_get_drvdata(dev);
 147
 148        clk_disable(ost->clk);
 149
 150        return 0;
 151}
 152
 153static int __maybe_unused ingenic_ost_resume(struct device *dev)
 154{
 155        struct ingenic_ost *ost = dev_get_drvdata(dev);
 156
 157        return clk_enable(ost->clk);
 158}
 159
 160static const struct dev_pm_ops __maybe_unused ingenic_ost_pm_ops = {
 161        /* _noirq: We want the OST clock to be gated last / ungated first */
 162        .suspend_noirq = ingenic_ost_suspend,
 163        .resume_noirq  = ingenic_ost_resume,
 164};
 165
 166static const struct ingenic_ost_soc_info jz4725b_ost_soc_info = {
 167        .is64bit = false,
 168};
 169
 170static const struct ingenic_ost_soc_info jz4770_ost_soc_info = {
 171        .is64bit = true,
 172};
 173
 174static const struct of_device_id ingenic_ost_of_match[] = {
 175        { .compatible = "ingenic,jz4725b-ost", .data = &jz4725b_ost_soc_info, },
 176        { .compatible = "ingenic,jz4770-ost", .data = &jz4770_ost_soc_info, },
 177        { }
 178};
 179
 180static struct platform_driver ingenic_ost_driver = {
 181        .driver = {
 182                .name = "ingenic-ost",
 183#ifdef CONFIG_PM_SUSPEND
 184                .pm = &ingenic_ost_pm_ops,
 185#endif
 186                .of_match_table = ingenic_ost_of_match,
 187        },
 188};
 189builtin_platform_driver_probe(ingenic_ost_driver, ingenic_ost_probe);
 190