cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

snd-n64.c (9013B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 *   Sound driver for Nintendo 64.
      4 *
      5 *   Copyright 2021 Lauri Kasanen
      6 */
      7
      8#include <linux/dma-mapping.h>
      9#include <linux/init.h>
     10#include <linux/interrupt.h>
     11#include <linux/io.h>
     12#include <linux/log2.h>
     13#include <linux/module.h>
     14#include <linux/platform_device.h>
     15#include <linux/spinlock.h>
     16
     17#include <sound/control.h>
     18#include <sound/core.h>
     19#include <sound/initval.h>
     20#include <sound/pcm.h>
     21#include <sound/pcm_params.h>
     22
     23MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>");
     24MODULE_DESCRIPTION("N64 Audio");
     25MODULE_LICENSE("GPL");
     26
     27#define AI_NTSC_DACRATE 48681812
     28#define AI_STATUS_BUSY  (1 << 30)
     29#define AI_STATUS_FULL  (1 << 31)
     30
     31#define AI_ADDR_REG 0
     32#define AI_LEN_REG 1
     33#define AI_CONTROL_REG 2
     34#define AI_STATUS_REG 3
     35#define AI_RATE_REG 4
     36#define AI_BITCLOCK_REG 5
     37
     38#define MI_INTR_REG 2
     39#define MI_MASK_REG 3
     40
     41#define MI_INTR_AI 0x04
     42
     43#define MI_MASK_CLR_AI 0x0010
     44#define MI_MASK_SET_AI 0x0020
     45
     46
     47struct n64audio {
     48	u32 __iomem *ai_reg_base;
     49	u32 __iomem *mi_reg_base;
     50
     51	void *ring_base;
     52	dma_addr_t ring_base_dma;
     53
     54	struct snd_card *card;
     55
     56	struct {
     57		struct snd_pcm_substream *substream;
     58		int pos, nextpos;
     59		u32 writesize;
     60		u32 bufsize;
     61		spinlock_t lock;
     62	} chan;
     63};
     64
     65static void n64audio_write_reg(struct n64audio *priv, const u8 reg, const u32 value)
     66{
     67	writel(value, priv->ai_reg_base + reg);
     68}
     69
     70static void n64mi_write_reg(struct n64audio *priv, const u8 reg, const u32 value)
     71{
     72	writel(value, priv->mi_reg_base + reg);
     73}
     74
     75static u32 n64mi_read_reg(struct n64audio *priv, const u8 reg)
     76{
     77	return readl(priv->mi_reg_base + reg);
     78}
     79
     80static void n64audio_push(struct n64audio *priv)
     81{
     82	struct snd_pcm_runtime *runtime = priv->chan.substream->runtime;
     83	unsigned long flags;
     84	u32 count;
     85
     86	spin_lock_irqsave(&priv->chan.lock, flags);
     87
     88	count = priv->chan.writesize;
     89
     90	memcpy(priv->ring_base + priv->chan.nextpos,
     91	       runtime->dma_area + priv->chan.nextpos, count);
     92
     93	/*
     94	 * The hw registers are double-buffered, and the IRQ fires essentially
     95	 * one period behind. The core only allows one period's distance, so we
     96	 * keep a private DMA buffer to afford two.
     97	 */
     98	n64audio_write_reg(priv, AI_ADDR_REG, priv->ring_base_dma + priv->chan.nextpos);
     99	barrier();
    100	n64audio_write_reg(priv, AI_LEN_REG, count);
    101
    102	priv->chan.nextpos += count;
    103	priv->chan.nextpos %= priv->chan.bufsize;
    104
    105	runtime->delay = runtime->period_size;
    106
    107	spin_unlock_irqrestore(&priv->chan.lock, flags);
    108}
    109
    110static irqreturn_t n64audio_isr(int irq, void *dev_id)
    111{
    112	struct n64audio *priv = dev_id;
    113	const u32 intrs = n64mi_read_reg(priv, MI_INTR_REG);
    114	unsigned long flags;
    115
    116	// Check it's ours
    117	if (!(intrs & MI_INTR_AI))
    118		return IRQ_NONE;
    119
    120	n64audio_write_reg(priv, AI_STATUS_REG, 1);
    121
    122	if (priv->chan.substream && snd_pcm_running(priv->chan.substream)) {
    123		spin_lock_irqsave(&priv->chan.lock, flags);
    124
    125		priv->chan.pos = priv->chan.nextpos;
    126
    127		spin_unlock_irqrestore(&priv->chan.lock, flags);
    128
    129		snd_pcm_period_elapsed(priv->chan.substream);
    130		if (priv->chan.substream && snd_pcm_running(priv->chan.substream))
    131			n64audio_push(priv);
    132	}
    133
    134	return IRQ_HANDLED;
    135}
    136
    137static const struct snd_pcm_hardware n64audio_pcm_hw = {
    138	.info = (SNDRV_PCM_INFO_MMAP |
    139		 SNDRV_PCM_INFO_MMAP_VALID |
    140		 SNDRV_PCM_INFO_INTERLEAVED |
    141		 SNDRV_PCM_INFO_BLOCK_TRANSFER),
    142	.formats =          SNDRV_PCM_FMTBIT_S16_BE,
    143	.rates =            SNDRV_PCM_RATE_8000_48000,
    144	.rate_min =         8000,
    145	.rate_max =         48000,
    146	.channels_min =     2,
    147	.channels_max =     2,
    148	.buffer_bytes_max = 32768,
    149	.period_bytes_min = 1024,
    150	.period_bytes_max = 32768,
    151	.periods_min =      3,
    152	// 3 periods lets the double-buffering hw read one buffer behind safely
    153	.periods_max =      128,
    154};
    155
    156static int hw_rule_period_size(struct snd_pcm_hw_params *params,
    157			       struct snd_pcm_hw_rule *rule)
    158{
    159	struct snd_interval *c = hw_param_interval(params,
    160						   SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
    161	int changed = 0;
    162
    163	/*
    164	 * The DMA unit has errata on (start + len) & 0x3fff == 0x2000.
    165	 * This constraint makes sure that the period size is not a power of two,
    166	 * which combined with dma_alloc_coherent aligning the buffer to the largest
    167	 * PoT <= size guarantees it won't be hit.
    168	 */
    169
    170	if (is_power_of_2(c->min)) {
    171		c->min += 2;
    172		changed = 1;
    173	}
    174	if (is_power_of_2(c->max)) {
    175		c->max -= 2;
    176		changed = 1;
    177	}
    178	if (snd_interval_checkempty(c)) {
    179		c->empty = 1;
    180		return -EINVAL;
    181	}
    182
    183	return changed;
    184}
    185
    186static int n64audio_pcm_open(struct snd_pcm_substream *substream)
    187{
    188	struct snd_pcm_runtime *runtime = substream->runtime;
    189	int err;
    190
    191	runtime->hw = n64audio_pcm_hw;
    192	err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
    193	if (err < 0)
    194		return err;
    195
    196	err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
    197	if (err < 0)
    198		return err;
    199
    200	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
    201			    hw_rule_period_size, NULL, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
    202	if (err < 0)
    203		return err;
    204
    205	return 0;
    206}
    207
    208static int n64audio_pcm_prepare(struct snd_pcm_substream *substream)
    209{
    210	struct snd_pcm_runtime *runtime = substream->runtime;
    211	struct n64audio *priv = substream->pcm->private_data;
    212	u32 rate;
    213
    214	rate = ((2 * AI_NTSC_DACRATE / runtime->rate) + 1) / 2 - 1;
    215
    216	n64audio_write_reg(priv, AI_RATE_REG, rate);
    217
    218	rate /= 66;
    219	if (rate > 16)
    220		rate = 16;
    221	n64audio_write_reg(priv, AI_BITCLOCK_REG, rate - 1);
    222
    223	spin_lock_irq(&priv->chan.lock);
    224
    225	/* Setup the pseudo-dma transfer pointers.  */
    226	priv->chan.pos = 0;
    227	priv->chan.nextpos = 0;
    228	priv->chan.substream = substream;
    229	priv->chan.writesize = snd_pcm_lib_period_bytes(substream);
    230	priv->chan.bufsize = snd_pcm_lib_buffer_bytes(substream);
    231
    232	spin_unlock_irq(&priv->chan.lock);
    233	return 0;
    234}
    235
    236static int n64audio_pcm_trigger(struct snd_pcm_substream *substream,
    237				int cmd)
    238{
    239	struct n64audio *priv = substream->pcm->private_data;
    240
    241	switch (cmd) {
    242	case SNDRV_PCM_TRIGGER_START:
    243		n64audio_push(substream->pcm->private_data);
    244		n64audio_write_reg(priv, AI_CONTROL_REG, 1);
    245		n64mi_write_reg(priv, MI_MASK_REG, MI_MASK_SET_AI);
    246		break;
    247	case SNDRV_PCM_TRIGGER_STOP:
    248		n64audio_write_reg(priv, AI_CONTROL_REG, 0);
    249		n64mi_write_reg(priv, MI_MASK_REG, MI_MASK_CLR_AI);
    250		break;
    251	default:
    252		return -EINVAL;
    253	}
    254	return 0;
    255}
    256
    257static snd_pcm_uframes_t n64audio_pcm_pointer(struct snd_pcm_substream *substream)
    258{
    259	struct n64audio *priv = substream->pcm->private_data;
    260
    261	return bytes_to_frames(substream->runtime,
    262			       priv->chan.pos);
    263}
    264
    265static int n64audio_pcm_close(struct snd_pcm_substream *substream)
    266{
    267	struct n64audio *priv = substream->pcm->private_data;
    268
    269	priv->chan.substream = NULL;
    270
    271	return 0;
    272}
    273
    274static const struct snd_pcm_ops n64audio_pcm_ops = {
    275	.open =		n64audio_pcm_open,
    276	.prepare =	n64audio_pcm_prepare,
    277	.trigger =	n64audio_pcm_trigger,
    278	.pointer =	n64audio_pcm_pointer,
    279	.close =	n64audio_pcm_close,
    280};
    281
    282/*
    283 * The target device is embedded and RAM-constrained. We save RAM
    284 * by initializing in __init code that gets dropped late in boot.
    285 * For the same reason there is no module or unloading support.
    286 */
    287static int __init n64audio_probe(struct platform_device *pdev)
    288{
    289	struct snd_card *card;
    290	struct snd_pcm *pcm;
    291	struct n64audio *priv;
    292	int err, irq;
    293
    294	err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1,
    295			   SNDRV_DEFAULT_STR1,
    296			   THIS_MODULE, sizeof(*priv), &card);
    297	if (err < 0)
    298		return err;
    299
    300	priv = card->private_data;
    301
    302	spin_lock_init(&priv->chan.lock);
    303
    304	priv->card = card;
    305
    306	priv->ring_base = dma_alloc_coherent(card->dev, 32 * 1024, &priv->ring_base_dma,
    307					     GFP_DMA|GFP_KERNEL);
    308	if (!priv->ring_base) {
    309		err = -ENOMEM;
    310		goto fail_card;
    311	}
    312
    313	priv->mi_reg_base = devm_platform_ioremap_resource(pdev, 0);
    314	if (IS_ERR(priv->mi_reg_base)) {
    315		err = PTR_ERR(priv->mi_reg_base);
    316		goto fail_dma_alloc;
    317	}
    318
    319	priv->ai_reg_base = devm_platform_ioremap_resource(pdev, 1);
    320	if (IS_ERR(priv->ai_reg_base)) {
    321		err = PTR_ERR(priv->ai_reg_base);
    322		goto fail_dma_alloc;
    323	}
    324
    325	err = snd_pcm_new(card, "N64 Audio", 0, 1, 0, &pcm);
    326	if (err < 0)
    327		goto fail_dma_alloc;
    328
    329	pcm->private_data = priv;
    330	strcpy(pcm->name, "N64 Audio");
    331
    332	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &n64audio_pcm_ops);
    333	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, card->dev, 0, 0);
    334
    335	strcpy(card->driver, "N64 Audio");
    336	strcpy(card->shortname, "N64 Audio");
    337	strcpy(card->longname, "N64 Audio");
    338
    339	irq = platform_get_irq(pdev, 0);
    340	if (irq < 0) {
    341		err = -EINVAL;
    342		goto fail_dma_alloc;
    343	}
    344	if (devm_request_irq(&pdev->dev, irq, n64audio_isr,
    345				IRQF_SHARED, "N64 Audio", priv)) {
    346		err = -EBUSY;
    347		goto fail_dma_alloc;
    348	}
    349
    350	err = snd_card_register(card);
    351	if (err < 0)
    352		goto fail_dma_alloc;
    353
    354	return 0;
    355
    356fail_dma_alloc:
    357	dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, priv->ring_base_dma);
    358
    359fail_card:
    360	snd_card_free(card);
    361	return err;
    362}
    363
    364static struct platform_driver n64audio_driver = {
    365	.driver = {
    366		.name = "n64audio",
    367	},
    368};
    369
    370static int __init n64audio_init(void)
    371{
    372	return platform_driver_probe(&n64audio_driver, n64audio_probe);
    373}
    374
    375module_init(n64audio_init);