18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * RNG driver for AMD Geode 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/io.h>
308c2ecf20Sopenharmony_ci#include <linux/kernel.h>
318c2ecf20Sopenharmony_ci#include <linux/module.h>
328c2ecf20Sopenharmony_ci#include <linux/pci.h>
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define PFX	KBUILD_MODNAME ": "
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci#define GEODE_RNG_DATA_REG   0x50
388c2ecf20Sopenharmony_ci#define GEODE_RNG_STATUS_REG 0x54
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, PCI_DEVICE_ID_AMD_LX_AES), 0, },
508c2ecf20Sopenharmony_ci	{ 0, },	/* terminate list */
518c2ecf20Sopenharmony_ci};
528c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, pci_tbl);
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistruct amd_geode_priv {
558c2ecf20Sopenharmony_ci	struct pci_dev *pcidev;
568c2ecf20Sopenharmony_ci	void __iomem *membase;
578c2ecf20Sopenharmony_ci};
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic int geode_rng_data_read(struct hwrng *rng, u32 *data)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	struct amd_geode_priv *priv = (struct amd_geode_priv *)rng->priv;
628c2ecf20Sopenharmony_ci	void __iomem *mem = priv->membase;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	*data = readl(mem + GEODE_RNG_DATA_REG);
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	return 4;
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic int geode_rng_data_present(struct hwrng *rng, int wait)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct amd_geode_priv *priv = (struct amd_geode_priv *)rng->priv;
728c2ecf20Sopenharmony_ci	void __iomem *mem = priv->membase;
738c2ecf20Sopenharmony_ci	int data, i;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	for (i = 0; i < 20; i++) {
768c2ecf20Sopenharmony_ci		data = !!(readl(mem + GEODE_RNG_STATUS_REG));
778c2ecf20Sopenharmony_ci		if (data || !wait)
788c2ecf20Sopenharmony_ci			break;
798c2ecf20Sopenharmony_ci		udelay(10);
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci	return data;
828c2ecf20Sopenharmony_ci}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic struct hwrng geode_rng = {
868c2ecf20Sopenharmony_ci	.name		= "geode",
878c2ecf20Sopenharmony_ci	.data_present	= geode_rng_data_present,
888c2ecf20Sopenharmony_ci	.data_read	= geode_rng_data_read,
898c2ecf20Sopenharmony_ci};
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic int __init mod_init(void)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	int err = -ENODEV;
958c2ecf20Sopenharmony_ci	struct pci_dev *pdev = NULL;
968c2ecf20Sopenharmony_ci	const struct pci_device_id *ent;
978c2ecf20Sopenharmony_ci	void __iomem *mem;
988c2ecf20Sopenharmony_ci	unsigned long rng_base;
998c2ecf20Sopenharmony_ci	struct amd_geode_priv *priv;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	for_each_pci_dev(pdev) {
1028c2ecf20Sopenharmony_ci		ent = pci_match_id(pci_tbl, pdev);
1038c2ecf20Sopenharmony_ci		if (ent)
1048c2ecf20Sopenharmony_ci			goto found;
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci	/* Device not found. */
1078c2ecf20Sopenharmony_ci	return err;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cifound:
1108c2ecf20Sopenharmony_ci	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
1118c2ecf20Sopenharmony_ci	if (!priv) {
1128c2ecf20Sopenharmony_ci		err = -ENOMEM;
1138c2ecf20Sopenharmony_ci		goto put_dev;
1148c2ecf20Sopenharmony_ci	}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	rng_base = pci_resource_start(pdev, 0);
1178c2ecf20Sopenharmony_ci	if (rng_base == 0)
1188c2ecf20Sopenharmony_ci		goto free_priv;
1198c2ecf20Sopenharmony_ci	err = -ENOMEM;
1208c2ecf20Sopenharmony_ci	mem = ioremap(rng_base, 0x58);
1218c2ecf20Sopenharmony_ci	if (!mem)
1228c2ecf20Sopenharmony_ci		goto free_priv;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	geode_rng.priv = (unsigned long)priv;
1258c2ecf20Sopenharmony_ci	priv->membase = mem;
1268c2ecf20Sopenharmony_ci	priv->pcidev = pdev;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	pr_info("AMD Geode RNG detected\n");
1298c2ecf20Sopenharmony_ci	err = hwrng_register(&geode_rng);
1308c2ecf20Sopenharmony_ci	if (err) {
1318c2ecf20Sopenharmony_ci		pr_err(PFX "RNG registering failed (%d)\n",
1328c2ecf20Sopenharmony_ci		       err);
1338c2ecf20Sopenharmony_ci		goto err_unmap;
1348c2ecf20Sopenharmony_ci	}
1358c2ecf20Sopenharmony_ci	return err;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cierr_unmap:
1388c2ecf20Sopenharmony_ci	iounmap(mem);
1398c2ecf20Sopenharmony_cifree_priv:
1408c2ecf20Sopenharmony_ci	kfree(priv);
1418c2ecf20Sopenharmony_ciput_dev:
1428c2ecf20Sopenharmony_ci	pci_dev_put(pdev);
1438c2ecf20Sopenharmony_ci	return err;
1448c2ecf20Sopenharmony_ci}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_cistatic void __exit mod_exit(void)
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	struct amd_geode_priv *priv;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	priv = (struct amd_geode_priv *)geode_rng.priv;
1518c2ecf20Sopenharmony_ci	hwrng_unregister(&geode_rng);
1528c2ecf20Sopenharmony_ci	iounmap(priv->membase);
1538c2ecf20Sopenharmony_ci	pci_dev_put(priv->pcidev);
1548c2ecf20Sopenharmony_ci	kfree(priv);
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cimodule_init(mod_init);
1588c2ecf20Sopenharmony_cimodule_exit(mod_exit);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("H/W RNG driver for AMD Geode LX CPUs");
1618c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
162