196d63c02SMichael Buesch /*
296d63c02SMichael Buesch * RNG driver for AMD RNGs
396d63c02SMichael Buesch *
496d63c02SMichael Buesch * Copyright 2005 (c) MontaVista Software, Inc.
596d63c02SMichael Buesch *
696d63c02SMichael Buesch * with the majority of the code coming from:
796d63c02SMichael Buesch *
896d63c02SMichael Buesch * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG)
996d63c02SMichael Buesch * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com>
1096d63c02SMichael Buesch *
1196d63c02SMichael Buesch * derived from
1296d63c02SMichael Buesch *
1396d63c02SMichael Buesch * Hardware driver for the AMD 768 Random Number Generator (RNG)
1477122d0bSAlan Cox * (c) Copyright 2001 Red Hat Inc
1596d63c02SMichael Buesch *
1696d63c02SMichael Buesch * derived from
1796d63c02SMichael Buesch *
1896d63c02SMichael Buesch * Hardware driver for Intel i810 Random Number Generator (RNG)
1996d63c02SMichael Buesch * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com>
2096d63c02SMichael Buesch * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com>
2196d63c02SMichael Buesch *
2296d63c02SMichael Buesch * This file is licensed under the terms of the GNU General Public
2396d63c02SMichael Buesch * License version 2. This program is licensed "as is" without any
2496d63c02SMichael Buesch * warranty of any kind, whether express or implied.
2596d63c02SMichael Buesch */
2696d63c02SMichael Buesch
27984e976fSPatrick McHardy #include <linux/delay.h>
28055ae890SCorentin LABBE #include <linux/hw_random.h>
291ce1cd82SRob Herring #include <linux/io.h>
30055ae890SCorentin LABBE #include <linux/kernel.h>
31055ae890SCorentin LABBE #include <linux/module.h>
32055ae890SCorentin LABBE #include <linux/pci.h>
3396d63c02SMichael Buesch
34f8169bfbSCorentin LABBE #define DRV_NAME "AMD768-HWRNG"
3596d63c02SMichael Buesch
363c343a37SCorentin LABBE #define RNGDATA 0x00
373c343a37SCorentin LABBE #define RNGDONE 0x04
383c343a37SCorentin LABBE #define PMBASE_OFFSET 0xF0
393c343a37SCorentin LABBE #define PMBASE_SIZE 8
403c343a37SCorentin LABBE
4196d63c02SMichael Buesch /*
4296d63c02SMichael Buesch * Data for PCI driver interface
4396d63c02SMichael Buesch *
4496d63c02SMichael Buesch * This data only exists for exporting the supported
4596d63c02SMichael Buesch * PCI ids via MODULE_DEVICE_TABLE. We do not actually
4696d63c02SMichael Buesch * register a pci_driver, because someone else might one day
4796d63c02SMichael Buesch * want to register another driver on the same PCI id.
4896d63c02SMichael Buesch */
4996d63c02SMichael Buesch static const struct pci_device_id pci_tbl[] = {
50409a7363SJoe Perches { PCI_VDEVICE(AMD, 0x7443), 0, },
51409a7363SJoe Perches { PCI_VDEVICE(AMD, 0x746b), 0, },
5296d63c02SMichael Buesch { 0, }, /* terminate list */
5396d63c02SMichael Buesch };
5496d63c02SMichael Buesch MODULE_DEVICE_TABLE(pci, pci_tbl);
5596d63c02SMichael Buesch
567bad2cc0SCorentin LABBE struct amd768_priv {
573c343a37SCorentin LABBE void __iomem *iobase;
587bad2cc0SCorentin LABBE struct pci_dev *pcidev;
5969db7009SPrarit Bhargava u32 pmbase;
607bad2cc0SCorentin LABBE };
6196d63c02SMichael Buesch
amd_rng_read(struct hwrng * rng,void * buf,size_t max,bool wait)6285962d22SCorentin LABBE static int amd_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
6396d63c02SMichael Buesch {
6485962d22SCorentin LABBE u32 *data = buf;
657bad2cc0SCorentin LABBE struct amd768_priv *priv = (struct amd768_priv *)rng->priv;
6685962d22SCorentin LABBE size_t read = 0;
6785962d22SCorentin LABBE /* We will wait at maximum one time per read */
6885962d22SCorentin LABBE int timeout = max / 4 + 1;
6996d63c02SMichael Buesch
7085962d22SCorentin LABBE /*
7185962d22SCorentin LABBE * RNG data is available when RNGDONE is set to 1
7285962d22SCorentin LABBE * New random numbers are generated approximately 128 microseconds
7385962d22SCorentin LABBE * after RNGDATA is read
7485962d22SCorentin LABBE */
7585962d22SCorentin LABBE while (read < max) {
7685962d22SCorentin LABBE if (ioread32(priv->iobase + RNGDONE) == 0) {
7785962d22SCorentin LABBE if (wait) {
7885962d22SCorentin LABBE /* Delay given by datasheet */
7985962d22SCorentin LABBE usleep_range(128, 196);
8085962d22SCorentin LABBE if (timeout-- == 0)
8185962d22SCorentin LABBE return read;
8285962d22SCorentin LABBE } else {
8385962d22SCorentin LABBE return 0;
84984e976fSPatrick McHardy }
8585962d22SCorentin LABBE } else {
863c343a37SCorentin LABBE *data = ioread32(priv->iobase + RNGDATA);
8785962d22SCorentin LABBE data++;
8885962d22SCorentin LABBE read += 4;
8985962d22SCorentin LABBE }
9085962d22SCorentin LABBE }
9196d63c02SMichael Buesch
9285962d22SCorentin LABBE return read;
9396d63c02SMichael Buesch }
9496d63c02SMichael Buesch
amd_rng_init(struct hwrng * rng)9596d63c02SMichael Buesch static int amd_rng_init(struct hwrng *rng)
9696d63c02SMichael Buesch {
977bad2cc0SCorentin LABBE struct amd768_priv *priv = (struct amd768_priv *)rng->priv;
9896d63c02SMichael Buesch u8 rnen;
9996d63c02SMichael Buesch
1007bad2cc0SCorentin LABBE pci_read_config_byte(priv->pcidev, 0x40, &rnen);
1011c335d44SCorentin LABBE rnen |= BIT(7); /* RNG on */
1027bad2cc0SCorentin LABBE pci_write_config_byte(priv->pcidev, 0x40, rnen);
10396d63c02SMichael Buesch
1047bad2cc0SCorentin LABBE pci_read_config_byte(priv->pcidev, 0x41, &rnen);
1051c335d44SCorentin LABBE rnen |= BIT(7); /* PMIO enable */
1067bad2cc0SCorentin LABBE pci_write_config_byte(priv->pcidev, 0x41, rnen);
10796d63c02SMichael Buesch
10896d63c02SMichael Buesch return 0;
10996d63c02SMichael Buesch }
11096d63c02SMichael Buesch
amd_rng_cleanup(struct hwrng * rng)11196d63c02SMichael Buesch static void amd_rng_cleanup(struct hwrng *rng)
11296d63c02SMichael Buesch {
1137bad2cc0SCorentin LABBE struct amd768_priv *priv = (struct amd768_priv *)rng->priv;
11496d63c02SMichael Buesch u8 rnen;
11596d63c02SMichael Buesch
1167bad2cc0SCorentin LABBE pci_read_config_byte(priv->pcidev, 0x40, &rnen);
1171c335d44SCorentin LABBE rnen &= ~BIT(7); /* RNG off */
1187bad2cc0SCorentin LABBE pci_write_config_byte(priv->pcidev, 0x40, rnen);
11996d63c02SMichael Buesch }
12096d63c02SMichael Buesch
12196d63c02SMichael Buesch static struct hwrng amd_rng = {
12296d63c02SMichael Buesch .name = "amd",
12396d63c02SMichael Buesch .init = amd_rng_init,
12496d63c02SMichael Buesch .cleanup = amd_rng_cleanup,
12585962d22SCorentin LABBE .read = amd_rng_read,
12696d63c02SMichael Buesch };
12796d63c02SMichael Buesch
amd_rng_mod_init(void)128f0d9ff8cSRandy Dunlap static int __init amd_rng_mod_init(void)
12996d63c02SMichael Buesch {
130b7c3635eSColin Ian King int err;
13196d63c02SMichael Buesch struct pci_dev *pdev = NULL;
13296d63c02SMichael Buesch const struct pci_device_id *ent;
13396d63c02SMichael Buesch u32 pmbase;
1347bad2cc0SCorentin LABBE struct amd768_priv *priv;
13596d63c02SMichael Buesch
13696d63c02SMichael Buesch for_each_pci_dev(pdev) {
13796d63c02SMichael Buesch ent = pci_match_id(pci_tbl, pdev);
13896d63c02SMichael Buesch if (ent)
13996d63c02SMichael Buesch goto found;
14096d63c02SMichael Buesch }
14196d63c02SMichael Buesch /* Device not found. */
1427bad2cc0SCorentin LABBE return -ENODEV;
14396d63c02SMichael Buesch
14496d63c02SMichael Buesch found:
14596d63c02SMichael Buesch err = pci_read_config_dword(pdev, 0x58, &pmbase);
146*e91173e3SIlpo Järvinen if (err) {
147*e91173e3SIlpo Järvinen err = pcibios_err_to_errno(err);
148ecadb5b0SXiongfeng Wang goto put_dev;
149*e91173e3SIlpo Järvinen }
1507bad2cc0SCorentin LABBE
15196d63c02SMichael Buesch pmbase &= 0x0000FF00;
152ecadb5b0SXiongfeng Wang if (pmbase == 0) {
153ecadb5b0SXiongfeng Wang err = -EIO;
154ecadb5b0SXiongfeng Wang goto put_dev;
155ecadb5b0SXiongfeng Wang }
1567bad2cc0SCorentin LABBE
15769db7009SPrarit Bhargava priv = kzalloc(sizeof(*priv), GFP_KERNEL);
158ecadb5b0SXiongfeng Wang if (!priv) {
159ecadb5b0SXiongfeng Wang err = -ENOMEM;
160ecadb5b0SXiongfeng Wang goto put_dev;
161ecadb5b0SXiongfeng Wang }
1627bad2cc0SCorentin LABBE
16369db7009SPrarit Bhargava if (!request_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE, DRV_NAME)) {
164f8169bfbSCorentin LABBE dev_err(&pdev->dev, DRV_NAME " region 0x%x already in use!\n",
165bd68ccb3SDmitry Eremin-Solenikov pmbase + 0xF0);
16669db7009SPrarit Bhargava err = -EBUSY;
16769db7009SPrarit Bhargava goto out;
168bd68ccb3SDmitry Eremin-Solenikov }
1693c343a37SCorentin LABBE
17069db7009SPrarit Bhargava priv->iobase = ioport_map(pmbase + PMBASE_OFFSET, PMBASE_SIZE);
171f7eca278SWei Yongjun if (!priv->iobase) {
1723c343a37SCorentin LABBE pr_err(DRV_NAME "Cannot map ioport\n");
17369db7009SPrarit Bhargava err = -EINVAL;
17469db7009SPrarit Bhargava goto err_iomap;
1753c343a37SCorentin LABBE }
1763c343a37SCorentin LABBE
1777bad2cc0SCorentin LABBE amd_rng.priv = (unsigned long)priv;
17869db7009SPrarit Bhargava priv->pmbase = pmbase;
1797bad2cc0SCorentin LABBE priv->pcidev = pdev;
18096d63c02SMichael Buesch
181f8169bfbSCorentin LABBE pr_info(DRV_NAME " detected\n");
18269db7009SPrarit Bhargava err = hwrng_register(&amd_rng);
18369db7009SPrarit Bhargava if (err) {
18469db7009SPrarit Bhargava pr_err(DRV_NAME " registering failed (%d)\n", err);
18569db7009SPrarit Bhargava goto err_hwrng;
18669db7009SPrarit Bhargava }
18769db7009SPrarit Bhargava return 0;
18869db7009SPrarit Bhargava
18969db7009SPrarit Bhargava err_hwrng:
19069db7009SPrarit Bhargava ioport_unmap(priv->iobase);
19169db7009SPrarit Bhargava err_iomap:
19269db7009SPrarit Bhargava release_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE);
19369db7009SPrarit Bhargava out:
19469db7009SPrarit Bhargava kfree(priv);
195ecadb5b0SXiongfeng Wang put_dev:
196ecadb5b0SXiongfeng Wang pci_dev_put(pdev);
19769db7009SPrarit Bhargava return err;
19896d63c02SMichael Buesch }
19996d63c02SMichael Buesch
amd_rng_mod_exit(void)200f0d9ff8cSRandy Dunlap static void __exit amd_rng_mod_exit(void)
20196d63c02SMichael Buesch {
20269db7009SPrarit Bhargava struct amd768_priv *priv;
20369db7009SPrarit Bhargava
20469db7009SPrarit Bhargava priv = (struct amd768_priv *)amd_rng.priv;
20569db7009SPrarit Bhargava
20669db7009SPrarit Bhargava hwrng_unregister(&amd_rng);
20769db7009SPrarit Bhargava
20869db7009SPrarit Bhargava ioport_unmap(priv->iobase);
20969db7009SPrarit Bhargava
21069db7009SPrarit Bhargava release_region(priv->pmbase + PMBASE_OFFSET, PMBASE_SIZE);
21169db7009SPrarit Bhargava
212ecadb5b0SXiongfeng Wang pci_dev_put(priv->pcidev);
213ecadb5b0SXiongfeng Wang
21469db7009SPrarit Bhargava kfree(priv);
21596d63c02SMichael Buesch }
21696d63c02SMichael Buesch
217f0d9ff8cSRandy Dunlap module_init(amd_rng_mod_init);
218f0d9ff8cSRandy Dunlap module_exit(amd_rng_mod_exit);
21996d63c02SMichael Buesch
22096d63c02SMichael Buesch MODULE_AUTHOR("The Linux Kernel team");
22196d63c02SMichael Buesch MODULE_DESCRIPTION("H/W RNG driver for AMD chipsets");
22296d63c02SMichael Buesch MODULE_LICENSE("GPL");
223