18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * RNG driver for AMD RNGs
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright 2005 (c) MontaVista Software, Inc.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * with the majority of the code coming from:
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG)
98c2ecf20Sopenharmony_ci * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com>
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * derived from
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci * Hardware driver for the AMD 768 Random Number Generator (RNG)
148c2ecf20Sopenharmony_ci * (c) Copyright 2001 Red Hat Inc
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci * derived from
178c2ecf20Sopenharmony_ci *
188c2ecf20Sopenharmony_ci * Hardware driver for Intel i810 Random Number Generator (RNG)
198c2ecf20Sopenharmony_ci * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com>
208c2ecf20Sopenharmony_ci * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com>
218c2ecf20Sopenharmony_ci *
228c2ecf20Sopenharmony_ci * This file is licensed under  the terms of the GNU General Public
238c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any
248c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied.
258c2ecf20Sopenharmony_ci */
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#include <linux/delay.h>
288c2ecf20Sopenharmony_ci#include <linux/hw_random.h>
298c2ecf20Sopenharmony_ci#include <linux/kernel.h>
308c2ecf20Sopenharmony_ci#include <linux/module.h>
318c2ecf20Sopenharmony_ci#include <linux/pci.h>
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define DRV_NAME "AMD768-HWRNG"
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define RNGDATA		0x00
368c2ecf20Sopenharmony_ci#define RNGDONE		0x04
378c2ecf20Sopenharmony_ci#define PMBASE_OFFSET	0xF0
388c2ecf20Sopenharmony_ci#define PMBASE_SIZE	8
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci/*
418c2ecf20Sopenharmony_ci * Data for PCI driver interface
428c2ecf20Sopenharmony_ci *
438c2ecf20Sopenharmony_ci * This data only exists for exporting the supported
448c2ecf20Sopenharmony_ci * PCI ids via MODULE_DEVICE_TABLE.  We do not actually
458c2ecf20Sopenharmony_ci * register a pci_driver, because someone else might one day
468c2ecf20Sopenharmony_ci * want to register another driver on the same PCI id.
478c2ecf20Sopenharmony_ci */
488c2ecf20Sopenharmony_cistatic const struct pci_device_id pci_tbl[] = {
498c2ecf20Sopenharmony_ci	{ PCI_VDEVICE(AMD, 0x7443), 0, },
508c2ecf20Sopenharmony_ci	{ PCI_VDEVICE(AMD, 0x746b), 0, },
518c2ecf20Sopenharmony_ci	{ 0, },	/* terminate list */
528c2ecf20Sopenharmony_ci};
538c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, pci_tbl);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistruct amd768_priv {
568c2ecf20Sopenharmony_ci	void __iomem *iobase;
578c2ecf20Sopenharmony_ci	struct pci_dev *pcidev;
588c2ecf20Sopenharmony_ci	u32 pmbase;
598c2ecf20Sopenharmony_ci};
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic int amd_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	u32 *data = buf;
648c2ecf20Sopenharmony_ci	struct amd768_priv *priv = (struct amd768_priv *)rng->priv;
658c2ecf20Sopenharmony_ci	size_t read = 0;
668c2ecf20Sopenharmony_ci	/* We will wait at maximum one time per read */
678c2ecf20Sopenharmony_ci	int timeout = max / 4 + 1;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	/*
708c2ecf20Sopenharmony_ci	 * RNG data is available when RNGDONE is set to 1
718c2ecf20Sopenharmony_ci	 * New random numbers are generated approximately 128 microseconds
728c2ecf20Sopenharmony_ci	 * after RNGDATA is read
738c2ecf20Sopenharmony_ci	 */
748c2ecf20Sopenharmony_ci	while (read < max) {
758c2ecf20Sopenharmony_ci		if (ioread32(priv->iobase + RNGDONE) == 0) {
768c2ecf20Sopenharmony_ci			if (wait) {
778c2ecf20Sopenharmony_ci				/* Delay given by datasheet */
788c2ecf20Sopenharmony_ci				usleep_range(128, 196);
798c2ecf20Sopenharmony_ci				if (timeout-- == 0)
808c2ecf20Sopenharmony_ci					return read;
818c2ecf20Sopenharmony_ci			} else {
828c2ecf20Sopenharmony_ci				return 0;
838c2ecf20Sopenharmony_ci			}
848c2ecf20Sopenharmony_ci		} else {
858c2ecf20Sopenharmony_ci			*data = ioread32(priv->iobase + RNGDATA);
868c2ecf20Sopenharmony_ci			data++;
878c2ecf20Sopenharmony_ci			read += 4;
888c2ecf20Sopenharmony_ci		}
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	return read;
928c2ecf20Sopenharmony_ci}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic int amd_rng_init(struct hwrng *rng)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	struct amd768_priv *priv = (struct amd768_priv *)rng->priv;
978c2ecf20Sopenharmony_ci	u8 rnen;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	pci_read_config_byte(priv->pcidev, 0x40, &rnen);
1008c2ecf20Sopenharmony_ci	rnen |= BIT(7);	/* RNG on */
1018c2ecf20Sopenharmony_ci	pci_write_config_byte(priv->pcidev, 0x40, rnen);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	pci_read_config_byte(priv->pcidev, 0x41, &rnen);
1048c2ecf20Sopenharmony_ci	rnen |= BIT(7);	/* PMIO enable */
1058c2ecf20Sopenharmony_ci	pci_write_config_byte(priv->pcidev, 0x41, rnen);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	return 0;
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic void amd_rng_cleanup(struct hwrng *rng)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	struct amd768_priv *priv = (struct amd768_priv *)rng->priv;
1138c2ecf20Sopenharmony_ci	u8 rnen;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	pci_read_config_byte(priv->pcidev, 0x40, &rnen);
1168c2ecf20Sopenharmony_ci	rnen &= ~BIT(7);	/* RNG off */
1178c2ecf20Sopenharmony_ci	pci_write_config_byte(priv->pcidev, 0x40, rnen);
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic struct hwrng amd_rng = {
1218c2ecf20Sopenharmony_ci	.name		= "amd",
1228c2ecf20Sopenharmony_ci	.init		= amd_rng_init,
1238c2ecf20Sopenharmony_ci	.cleanup	= amd_rng_cleanup,
1248c2ecf20Sopenharmony_ci	.read		= amd_rng_read,
1258c2ecf20Sopenharmony_ci};
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cistatic int __init mod_init(void)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	int err = -ENODEV;
1308c2ecf20Sopenharmony_ci	struct pci_dev *pdev = NULL;
1318c2ecf20Sopenharmony_ci	const struct pci_device_id *ent;
1328c2ecf20Sopenharmony_ci	u32 pmbase;
1338c2ecf20Sopenharmony_ci	struct amd768_priv *priv;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	for_each_pci_dev(pdev) {
1368c2ecf20Sopenharmony_ci		ent = pci_match_id(pci_tbl, pdev);
1378c2ecf20Sopenharmony_ci		if (ent)
1388c2ecf20Sopenharmony_ci			goto found;
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci	/* Device not found. */
1418c2ecf20Sopenharmony_ci	return -ENODEV;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cifound:
1448c2ecf20Sopenharmony_ci	err = pci_read_config_dword(pdev, 0x58, &pmbase);
1458c2ecf20Sopenharmony_ci	if (err)
1468c2ecf20Sopenharmony_ci		goto put_dev;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	pmbase &= 0x0000FF00;
1498c2ecf20Sopenharmony_ci	if (pmbase == 0) {
1508c2ecf20Sopenharmony_ci		err = -EIO;
1518c2ecf20Sopenharmony_ci		goto put_dev;
1528c2ecf20Sopenharmony_ci	}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
1558c2ecf20Sopenharmony_ci	if (!priv) {
1568c2ecf20Sopenharmony_ci		err = -ENOMEM;
1578c2ecf20Sopenharmony_ci		goto put_dev;
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	if (!request_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE, DRV_NAME)) {
1618c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, DRV_NAME " region 0x%x already in use!\n",
1628c2ecf20Sopenharmony_ci			pmbase + 0xF0);
1638c2ecf20Sopenharmony_ci		err = -EBUSY;
1648c2ecf20Sopenharmony_ci		goto out;
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	priv->iobase = ioport_map(pmbase + PMBASE_OFFSET, PMBASE_SIZE);
1688c2ecf20Sopenharmony_ci	if (!priv->iobase) {
1698c2ecf20Sopenharmony_ci		pr_err(DRV_NAME "Cannot map ioport\n");
1708c2ecf20Sopenharmony_ci		err = -EINVAL;
1718c2ecf20Sopenharmony_ci		goto err_iomap;
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	amd_rng.priv = (unsigned long)priv;
1758c2ecf20Sopenharmony_ci	priv->pmbase = pmbase;
1768c2ecf20Sopenharmony_ci	priv->pcidev = pdev;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	pr_info(DRV_NAME " detected\n");
1798c2ecf20Sopenharmony_ci	err = hwrng_register(&amd_rng);
1808c2ecf20Sopenharmony_ci	if (err) {
1818c2ecf20Sopenharmony_ci		pr_err(DRV_NAME " registering failed (%d)\n", err);
1828c2ecf20Sopenharmony_ci		goto err_hwrng;
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci	return 0;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cierr_hwrng:
1878c2ecf20Sopenharmony_ci	ioport_unmap(priv->iobase);
1888c2ecf20Sopenharmony_cierr_iomap:
1898c2ecf20Sopenharmony_ci	release_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE);
1908c2ecf20Sopenharmony_ciout:
1918c2ecf20Sopenharmony_ci	kfree(priv);
1928c2ecf20Sopenharmony_ciput_dev:
1938c2ecf20Sopenharmony_ci	pci_dev_put(pdev);
1948c2ecf20Sopenharmony_ci	return err;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic void __exit mod_exit(void)
1988c2ecf20Sopenharmony_ci{
1998c2ecf20Sopenharmony_ci	struct amd768_priv *priv;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	priv = (struct amd768_priv *)amd_rng.priv;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	hwrng_unregister(&amd_rng);
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	ioport_unmap(priv->iobase);
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	release_region(priv->pmbase + PMBASE_OFFSET, PMBASE_SIZE);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	pci_dev_put(priv->pcidev);
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	kfree(priv);
2128c2ecf20Sopenharmony_ci}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_cimodule_init(mod_init);
2158c2ecf20Sopenharmony_cimodule_exit(mod_exit);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ciMODULE_AUTHOR("The Linux Kernel team");
2188c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("H/W RNG driver for AMD chipsets");
2198c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
220