summaryrefslogtreecommitdiffstats
path: root/drivers/clocksource/i8253.c
AgeCommit message (Expand)Author
2018-11-04clockevents/drivers/i8253: Add support for PIT shutdown quirkMichael Kelley
2017-11-02License cleanup: add SPDX GPL-2.0 license identifier to files with no licenseGreg Kroah-Hartman
2016-12-25clocksource: Use a plain u64 instead of cycle_tThomas Gleixner
2015-08-10clockevents/drivers/i8253: Migrate to new 'set-state' interfaceViresh Kumar
2012-11-13time: Kill xtime_lock, replacing it with jiffies_lockJohn Stultz
2011-11-21time: x86: Replace LATCH with PIT_LATCH in i8253 clocksource driverDeepak Saxena
2011-07-01i8253: Create common clockevent implementationThomas Gleixner
2011-06-10i8253: Export i8253_lock unconditionallyThomas Gleixner
2011-06-09i8253: Remove I8253_LOCK configThomas Gleixner
2011-06-09i8253: Consolidate all kernel definitions of i8253_lockRalf Baechle
2011-06-09i8253: Create linux/i8253.h and use it in all 8253 related filesRalf Baechle
2011-05-14clocksource: add common i8253 PIT clocksourceRussell King
04' href='#n104'>104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
/*
 * Copyright (c) 2010-2011 Picochip Ltd., Jamie Iles
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * All enquiries to support@picochip.com
 */
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hw_random.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#define DATA_REG_OFFSET		0x0200
#define CSR_REG_OFFSET		0x0278
#define CSR_OUT_EMPTY_MASK	(1 << 24)
#define CSR_FAULT_MASK		(1 << 1)
#define TRNG_BLOCK_RESET_MASK	(1 << 0)
#define TAI_REG_OFFSET		0x0380

/*
 * The maximum amount of time in microseconds to spend waiting for data if the
 * core wants us to wait.  The TRNG should generate 32 bits every 320ns so a
 * timeout of 20us seems reasonable.  The TRNG does builtin tests of the data
 * for randomness so we can't always assume there is data present.
 */
#define PICO_TRNG_TIMEOUT		20

static void __iomem *rng_base;
static struct clk *rng_clk;
static struct device *rng_dev;

static inline u32 picoxcell_trng_read_csr(void)
{
	return __raw_readl(rng_base + CSR_REG_OFFSET);
}

static inline bool picoxcell_trng_is_empty(void)
{
	return picoxcell_trng_read_csr() & CSR_OUT_EMPTY_MASK;
}

/*
 * Take the random number generator out of reset and make sure the interrupts
 * are masked. We shouldn't need to get large amounts of random bytes so just
 * poll the status register. The hardware generates 32 bits every 320ns so we
 * shouldn't have to wait long enough to warrant waiting for an IRQ.
 */
static void picoxcell_trng_start(void)
{
	__raw_writel(0, rng_base + TAI_REG_OFFSET);
	__raw_writel(0, rng_base + CSR_REG_OFFSET);
}

static void picoxcell_trng_reset(void)
{
	__raw_writel(TRNG_BLOCK_RESET_MASK, rng_base + CSR_REG_OFFSET);
	__raw_writel(TRNG_BLOCK_RESET_MASK, rng_base + TAI_REG_OFFSET);
	picoxcell_trng_start();
}

/*
 * Get some random data from the random number generator. The hw_random core
 * layer provides us with locking.
 */
static int picoxcell_trng_read(struct hwrng *rng, void *buf, size_t max,
			       bool wait)
{
	int i;

	/* Wait for some data to become available. */
	for (i = 0; i < PICO_TRNG_TIMEOUT && picoxcell_trng_is_empty(); ++i) {
		if (!wait)
			return 0;

		udelay(1);
	}

	if (picoxcell_trng_read_csr() & CSR_FAULT_MASK) {
		dev_err(rng_dev, "fault detected, resetting TRNG\n");
		picoxcell_trng_reset();
		return -EIO;
	}

	if (i == PICO_TRNG_TIMEOUT)
		return 0;

	*(u32 *)buf = __raw_readl(rng_base + DATA_REG_OFFSET);
	return sizeof(u32);
}

static struct hwrng picoxcell_trng = {
	.name		= "picoxcell",
	.read		= picoxcell_trng_read,
};

static int picoxcell_trng_probe(struct platform_device *pdev)
{
	int ret;
	struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);

	rng_base = devm_ioremap_resource(&pdev->dev, mem);
	if (IS_ERR(rng_base))
		return PTR_ERR(rng_base);

	rng_clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(rng_clk)) {
		dev_warn(&pdev->dev, "no clk\n");
		return PTR_ERR(rng_clk);
	}

	ret = clk_enable(rng_clk);
	if (ret) {
		dev_warn(&pdev->dev, "unable to enable clk\n");
		return ret;
	}

	picoxcell_trng_start();
	ret = hwrng_register(&picoxcell_trng);
	if (ret)
		goto err_register;

	rng_dev = &pdev->dev;
	dev_info(&pdev->dev, "pixoxcell random number generator active\n");

	return 0;

err_register:
	clk_disable(rng_clk);
	return ret;
}

static int picoxcell_trng_remove(struct platform_device *pdev)
{
	hwrng_unregister(&picoxcell_trng);
	clk_disable(rng_clk);

	return 0;
}

#ifdef CONFIG_PM
static int picoxcell_trng_suspend(struct device *dev)
{
	clk_disable(rng_clk);

	return 0;
}

static int picoxcell_trng_resume(struct device *dev)
{
	return clk_enable(rng_clk);
}

static const struct dev_pm_ops picoxcell_trng_pm_ops = {
	.suspend	= picoxcell_trng_suspend,
	.resume		= picoxcell_trng_resume,
};
#endif /* CONFIG_PM */

static struct platform_driver picoxcell_trng_driver = {
	.probe		= picoxcell_trng_probe,
	.remove		= picoxcell_trng_remove,
	.driver		= {
		.name	= "picoxcell-trng",
		.owner	= THIS_MODULE,
#ifdef CONFIG_PM
		.pm	= &picoxcell_trng_pm_ops,
#endif /* CONFIG_PM */
	},
};

module_platform_driver(picoxcell_trng_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jamie Iles");
MODULE_DESCRIPTION("Picochip picoXcell TRNG driver");