diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath9k/rng.c')
-rw-r--r-- | drivers/net/wireless/ath/ath9k/rng.c | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/drivers/net/wireless/ath/ath9k/rng.c b/drivers/net/wireless/ath/ath9k/rng.c new file mode 100644 index 000000000..58c0ab017 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/rng.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2015 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/hw_random.h> +#include <linux/kthread.h> + +#include "ath9k.h" +#include "hw.h" +#include "ar9003_phy.h" + +static int ath9k_rng_data_read(struct ath_softc *sc, u32 *buf, u32 buf_size) +{ + int i, j; + u32 v1, v2, rng_last = sc->rng_last; + struct ath_hw *ah = sc->sc_ah; + + ath9k_ps_wakeup(sc); + + REG_RMW_FIELD(ah, AR_PHY_TEST, AR_PHY_TEST_BBB_OBS_SEL, 1); + REG_CLR_BIT(ah, AR_PHY_TEST, AR_PHY_TEST_RX_OBS_SEL_BIT5); + REG_RMW_FIELD(ah, AR_PHY_TEST_CTL_STATUS, AR_PHY_TEST_CTL_RX_OBS_SEL, 0); + + for (i = 0, j = 0; i < buf_size; i++) { + v1 = REG_READ(ah, AR_PHY_TST_ADC) & 0xffff; + v2 = REG_READ(ah, AR_PHY_TST_ADC) & 0xffff; + + /* wait for data ready */ + if (v1 && v2 && rng_last != v1 && v1 != v2 && v1 != 0xffff && + v2 != 0xffff) + buf[j++] = (v1 << 16) | v2; + + rng_last = v2; + } + + ath9k_ps_restore(sc); + + sc->rng_last = rng_last; + + return j << 2; +} + +static u32 ath9k_rng_delay_get(u32 fail_stats) +{ + u32 delay; + + if (fail_stats < 100) + delay = 10; + else if (fail_stats < 105) + delay = 1000; + else + delay = 10000; + + return delay; +} + +static int ath9k_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + struct ath_softc *sc = container_of(rng, struct ath_softc, rng_ops); + u32 fail_stats = 0, word; + int bytes_read = 0; + + for (;;) { + if (max & ~3UL) + bytes_read = ath9k_rng_data_read(sc, buf, max >> 2); + if ((max & 3UL) && ath9k_rng_data_read(sc, &word, 1)) { + memcpy(buf + bytes_read, &word, max & 3UL); + bytes_read += max & 3UL; + memzero_explicit(&word, sizeof(word)); + } + if (!wait || !max || likely(bytes_read) || fail_stats > 110) + break; + + if (hwrng_msleep(rng, ath9k_rng_delay_get(++fail_stats))) + break; + } + + if (wait && !bytes_read && max) + bytes_read = -EIO; + return bytes_read; +} + +void ath9k_rng_start(struct ath_softc *sc) +{ + static atomic_t serial = ATOMIC_INIT(0); + struct ath_hw *ah = sc->sc_ah; + + if (sc->rng_ops.read) + return; + + if (!AR_SREV_9300_20_OR_LATER(ah)) + return; + + snprintf(sc->rng_name, sizeof(sc->rng_name), "ath9k_%u", + (atomic_inc_return(&serial) - 1) & U16_MAX); + sc->rng_ops.name = sc->rng_name; + sc->rng_ops.read = ath9k_rng_read; + sc->rng_ops.quality = 320; + + if (devm_hwrng_register(sc->dev, &sc->rng_ops)) + sc->rng_ops.read = NULL; +} + +void ath9k_rng_stop(struct ath_softc *sc) +{ + if (sc->rng_ops.read) { + devm_hwrng_unregister(sc->dev, &sc->rng_ops); + sc->rng_ops.read = NULL; + } +} |