cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

timer-cs5535.c (5711B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * Clock event driver for the CS5535/CS5536
      4 *
      5 * Copyright (C) 2006, Advanced Micro Devices, Inc.
      6 * Copyright (C) 2007  Andres Salomon <dilinger@debian.org>
      7 * Copyright (C) 2009  Andres Salomon <dilinger@collabora.co.uk>
      8 *
      9 * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book.
     10 */
     11
     12#include <linux/kernel.h>
     13#include <linux/irq.h>
     14#include <linux/interrupt.h>
     15#include <linux/module.h>
     16#include <linux/cs5535.h>
     17#include <linux/clockchips.h>
     18
     19#define DRV_NAME "cs5535-clockevt"
     20
     21static int timer_irq;
     22module_param_hw_named(irq, timer_irq, int, irq, 0644);
     23MODULE_PARM_DESC(irq, "Which IRQ to use for the clock source MFGPT ticks.");
     24
     25/*
     26 * We are using the 32.768kHz input clock - it's the only one that has the
     27 * ranges we find desirable.  The following table lists the suitable
     28 * divisors and the associated Hz, minimum interval and the maximum interval:
     29 *
     30 *  Divisor   Hz      Min Delta (s)  Max Delta (s)
     31 *   1        32768   .00048828125      2.000
     32 *   2        16384   .0009765625       4.000
     33 *   4         8192   .001953125        8.000
     34 *   8         4096   .00390625        16.000
     35 *   16        2048   .0078125         32.000
     36 *   32        1024   .015625          64.000
     37 *   64         512   .03125          128.000
     38 *  128         256   .0625           256.000
     39 *  256         128   .125            512.000
     40 */
     41
     42static struct cs5535_mfgpt_timer *cs5535_event_clock;
     43
     44/* Selected from the table above */
     45
     46#define MFGPT_DIVISOR 16
     47#define MFGPT_SCALE  4     /* divisor = 2^(scale) */
     48#define MFGPT_HZ  (32768 / MFGPT_DIVISOR)
     49#define MFGPT_PERIODIC (MFGPT_HZ / HZ)
     50
     51/*
     52 * The MFGPT timers on the CS5536 provide us with suitable timers to use
     53 * as clock event sources - not as good as a HPET or APIC, but certainly
     54 * better than the PIT.  This isn't a general purpose MFGPT driver, but
     55 * a simplified one designed specifically to act as a clock event source.
     56 * For full details about the MFGPT, please consult the CS5536 data sheet.
     57 */
     58
     59static void disable_timer(struct cs5535_mfgpt_timer *timer)
     60{
     61	/* avoid races by clearing CMP1 and CMP2 unconditionally */
     62	cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,
     63			(uint16_t) ~MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP1 |
     64				MFGPT_SETUP_CMP2);
     65}
     66
     67static void start_timer(struct cs5535_mfgpt_timer *timer, uint16_t delta)
     68{
     69	cs5535_mfgpt_write(timer, MFGPT_REG_CMP2, delta);
     70	cs5535_mfgpt_write(timer, MFGPT_REG_COUNTER, 0);
     71
     72	cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,
     73			MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
     74}
     75
     76static int mfgpt_shutdown(struct clock_event_device *evt)
     77{
     78	disable_timer(cs5535_event_clock);
     79	return 0;
     80}
     81
     82static int mfgpt_set_periodic(struct clock_event_device *evt)
     83{
     84	disable_timer(cs5535_event_clock);
     85	start_timer(cs5535_event_clock, MFGPT_PERIODIC);
     86	return 0;
     87}
     88
     89static int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt)
     90{
     91	start_timer(cs5535_event_clock, delta);
     92	return 0;
     93}
     94
     95static struct clock_event_device cs5535_clockevent = {
     96	.name = DRV_NAME,
     97	.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
     98	.set_state_shutdown = mfgpt_shutdown,
     99	.set_state_periodic = mfgpt_set_periodic,
    100	.set_state_oneshot = mfgpt_shutdown,
    101	.tick_resume = mfgpt_shutdown,
    102	.set_next_event = mfgpt_next_event,
    103	.rating = 250,
    104};
    105
    106static irqreturn_t mfgpt_tick(int irq, void *dev_id)
    107{
    108	uint16_t val = cs5535_mfgpt_read(cs5535_event_clock, MFGPT_REG_SETUP);
    109
    110	/* See if the interrupt was for us */
    111	if (!(val & (MFGPT_SETUP_SETUP | MFGPT_SETUP_CMP2 | MFGPT_SETUP_CMP1)))
    112		return IRQ_NONE;
    113
    114	/* Turn off the clock (and clear the event) */
    115	disable_timer(cs5535_event_clock);
    116
    117	if (clockevent_state_detached(&cs5535_clockevent) ||
    118	    clockevent_state_shutdown(&cs5535_clockevent))
    119		return IRQ_HANDLED;
    120
    121	/* Clear the counter */
    122	cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_COUNTER, 0);
    123
    124	/* Restart the clock in periodic mode */
    125
    126	if (clockevent_state_periodic(&cs5535_clockevent))
    127		cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP,
    128				MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
    129
    130	cs5535_clockevent.event_handler(&cs5535_clockevent);
    131	return IRQ_HANDLED;
    132}
    133
    134static int __init cs5535_mfgpt_init(void)
    135{
    136	unsigned long flags = IRQF_NOBALANCING | IRQF_TIMER | IRQF_SHARED;
    137	struct cs5535_mfgpt_timer *timer;
    138	int ret;
    139	uint16_t val;
    140
    141	timer = cs5535_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING);
    142	if (!timer) {
    143		printk(KERN_ERR DRV_NAME ": Could not allocate MFGPT timer\n");
    144		return -ENODEV;
    145	}
    146	cs5535_event_clock = timer;
    147
    148	/* Set up the IRQ on the MFGPT side */
    149	if (cs5535_mfgpt_setup_irq(timer, MFGPT_CMP2, &timer_irq)) {
    150		printk(KERN_ERR DRV_NAME ": Could not set up IRQ %d\n",
    151				timer_irq);
    152		goto err_timer;
    153	}
    154
    155	/* And register it with the kernel */
    156	ret = request_irq(timer_irq, mfgpt_tick, flags, DRV_NAME, timer);
    157	if (ret) {
    158		printk(KERN_ERR DRV_NAME ": Unable to set up the interrupt.\n");
    159		goto err_irq;
    160	}
    161
    162	/* Set the clock scale and enable the event mode for CMP2 */
    163	val = MFGPT_SCALE | (3 << 8);
    164
    165	cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP, val);
    166
    167	/* Set up the clock event */
    168	printk(KERN_INFO DRV_NAME
    169		": Registering MFGPT timer as a clock event, using IRQ %d\n",
    170		timer_irq);
    171	clockevents_config_and_register(&cs5535_clockevent, MFGPT_HZ,
    172					0xF, 0xFFFE);
    173
    174	return 0;
    175
    176err_irq:
    177	cs5535_mfgpt_release_irq(cs5535_event_clock, MFGPT_CMP2, &timer_irq);
    178err_timer:
    179	cs5535_mfgpt_free_timer(cs5535_event_clock);
    180	printk(KERN_ERR DRV_NAME ": Unable to set up the MFGPT clock source\n");
    181	return -EIO;
    182}
    183
    184module_init(cs5535_mfgpt_init);
    185
    186MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
    187MODULE_DESCRIPTION("CS5535/CS5536 MFGPT clock event driver");
    188MODULE_LICENSE("GPL");