Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Yingkun Meng | 1540 | 99.16% | 1 | 20.00% |
Binbin Zhou | 5 | 0.32% | 1 | 20.00% |
tangbin | 3 | 0.19% | 1 | 20.00% |
Krzysztof Kozlowski | 3 | 0.19% | 1 | 20.00% |
Kuninori Morimoto | 2 | 0.13% | 1 | 20.00% |
Total | 1553 | 5 |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
// SPDX-License-Identifier: GPL-2.0 // // Loongson ALSA SoC Platform (DMA) driver // // Copyright (C) 2023 Loongson Technology Corporation Limited // Author: Yingkun Meng <mengyingkun@loongson.cn> // #include <linux/module.h> #include <linux/io-64-nonatomic-lo-hi.h> #include <linux/delay.h> #include <linux/pm_runtime.h> #include <linux/dma-mapping.h> #include <sound/soc.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include "loongson_i2s.h" /* DMA dma_order Register */ #define DMA_ORDER_STOP BIT(4) /* DMA stop */ #define DMA_ORDER_START BIT(3) /* DMA start */ #define DMA_ORDER_ASK_VALID BIT(2) /* DMA ask valid flag */ #define DMA_ORDER_AXI_UNCO BIT(1) /* Uncache access */ #define DMA_ORDER_ADDR_64 BIT(0) /* 64bits address support */ #define DMA_ORDER_ASK_MASK (~0x1fUL) /* Ask addr mask */ #define DMA_ORDER_CTRL_MASK (0x0fUL) /* Control mask */ /* * DMA registers descriptor. */ struct loongson_dma_desc { u32 order; /* Next descriptor address register */ u32 saddr; /* Source address register */ u32 daddr; /* Device address register */ u32 length; /* Total length register */ u32 step_length; /* Memory stride register */ u32 step_times; /* Repeat time register */ u32 cmd; /* Command register */ u32 stats; /* Status register */ u32 order_hi; /* Next descriptor high address register */ u32 saddr_hi; /* High source address register */ u32 res[6]; /* Reserved */ } __packed; struct loongson_runtime_data { struct loongson_dma_data *dma_data; struct loongson_dma_desc *dma_desc_arr; dma_addr_t dma_desc_arr_phy; int dma_desc_arr_size; struct loongson_dma_desc *dma_pos_desc; dma_addr_t dma_pos_desc_phy; }; static const struct snd_pcm_hardware ls_pcm_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE, .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE), .period_bytes_min = 128, .period_bytes_max = 128 * 1024, .periods_min = 1, .periods_max = PAGE_SIZE / sizeof(struct loongson_dma_desc), .buffer_bytes_max = 1024 * 1024, }; static struct loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd) { void __iomem *order_reg = prtd->dma_data->order_addr; u64 val; val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK; val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); val |= DMA_ORDER_ASK_VALID; writeq(val, order_reg); while (readl(order_reg) & DMA_ORDER_ASK_VALID) udelay(2); return prtd->dma_pos_desc; } static int loongson_pcm_trigger(struct snd_soc_component *component, struct snd_pcm_substream *substream, int cmd) { struct loongson_runtime_data *prtd = substream->runtime->private_data; struct device *dev = substream->pcm->card->dev; void __iomem *order_reg = prtd->dma_data->order_addr; u64 val; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK; if (dev->coherent_dma_mask == DMA_BIT_MASK(64)) val |= DMA_ORDER_ADDR_64; else val &= ~DMA_ORDER_ADDR_64; val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); val |= DMA_ORDER_START; writeq(val, order_reg); while ((readl(order_reg) & DMA_ORDER_START)) udelay(2); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: dma_desc_save(prtd); /* dma stop */ val = readq(order_reg) | DMA_ORDER_STOP; writeq(val, order_reg); udelay(1000); break; default: dev_err(dev, "Invalid pcm trigger operation\n"); return -EINVAL; } return 0; } static int loongson_pcm_hw_params(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime = substream->runtime; struct device *dev = substream->pcm->card->dev; struct loongson_runtime_data *prtd = runtime->private_data; size_t buf_len = params_buffer_bytes(params); size_t period_len = params_period_bytes(params); dma_addr_t order_addr, mem_addr; struct loongson_dma_desc *desc; u32 num_periods; int i; if (buf_len % period_len) { dev_err(dev, "buf len not multiply of period len\n"); return -EINVAL; } num_periods = buf_len / period_len; if (!num_periods || num_periods > prtd->dma_desc_arr_size) { dev_err(dev, "dma data too small or too big\n"); return -EINVAL; } snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); runtime->dma_bytes = buf_len; /* initialize dma descriptor array */ mem_addr = runtime->dma_addr; order_addr = prtd->dma_desc_arr_phy; for (i = 0; i < num_periods; i++) { desc = &prtd->dma_desc_arr[i]; /* next descriptor physical address */ order_addr += sizeof(*desc); desc->order = lower_32_bits(order_addr | BIT(0)); desc->order_hi = upper_32_bits(order_addr); desc->saddr = lower_32_bits(mem_addr); desc->saddr_hi = upper_32_bits(mem_addr); desc->daddr = prtd->dma_data->dev_addr; desc->cmd = BIT(0); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) desc->cmd |= BIT(12); desc->length = period_len >> 2; desc->step_length = 0; desc->step_times = 1; mem_addr += period_len; } desc = &prtd->dma_desc_arr[num_periods - 1]; desc->order = lower_32_bits(prtd->dma_desc_arr_phy | BIT(0)); desc->order_hi = upper_32_bits(prtd->dma_desc_arr_phy); /* init position descriptor */ *prtd->dma_pos_desc = *prtd->dma_desc_arr; return 0; } static snd_pcm_uframes_t loongson_pcm_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct loongson_runtime_data *prtd = runtime->private_data; struct loongson_dma_desc *desc; snd_pcm_uframes_t x; u64 addr; desc = dma_desc_save(prtd); addr = ((u64)desc->saddr_hi << 32) | desc->saddr; x = bytes_to_frames(runtime, addr - runtime->dma_addr); if (x == runtime->buffer_size) x = 0; return x; } static irqreturn_t loongson_pcm_dma_irq(int irq, void *devid) { struct snd_pcm_substream *substream = devid; snd_pcm_period_elapsed(substream); return IRQ_HANDLED; } static int loongson_pcm_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_card *card = substream->pcm->card; struct loongson_runtime_data *prtd; struct loongson_dma_data *dma_data; /* * For mysterious reasons (and despite what the manual says) * playback samples are lost if the DMA count is not a multiple * of the DMA burst size. Let's add a rule to enforce that. */ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128); snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128); snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); snd_soc_set_runtime_hwparams(substream, &ls_pcm_hardware); prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); if (!prtd) return -ENOMEM; prtd->dma_desc_arr = dma_alloc_coherent(card->dev, PAGE_SIZE, &prtd->dma_desc_arr_phy, GFP_KERNEL); if (!prtd->dma_desc_arr) goto desc_err; prtd->dma_desc_arr_size = PAGE_SIZE / sizeof(*prtd->dma_desc_arr); prtd->dma_pos_desc = dma_alloc_coherent(card->dev, sizeof(*prtd->dma_pos_desc), &prtd->dma_pos_desc_phy, GFP_KERNEL); if (!prtd->dma_pos_desc) goto pos_err; dma_data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream); prtd->dma_data = dma_data; substream->runtime->private_data = prtd; return 0; pos_err: dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr, prtd->dma_desc_arr_phy); desc_err: kfree(prtd); return -ENOMEM; } static int loongson_pcm_close(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_card *card = substream->pcm->card; struct loongson_runtime_data *prtd = substream->runtime->private_data; dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr, prtd->dma_desc_arr_phy); dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc), prtd->dma_pos_desc, prtd->dma_pos_desc_phy); kfree(prtd); return 0; } static int loongson_pcm_mmap(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct vm_area_struct *vma) { return remap_pfn_range(vma, vma->vm_start, substream->dma_buffer.addr >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot); } static int loongson_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { struct snd_card *card = rtd->card->snd_card; struct snd_pcm_substream *substream; struct loongson_dma_data *dma_data; unsigned int i; int ret; for_each_pcm_streams(i) { substream = rtd->pcm->streams[i].substream; if (!substream) continue; dma_data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream); ret = devm_request_irq(card->dev, dma_data->irq, loongson_pcm_dma_irq, IRQF_TRIGGER_HIGH, LS_I2S_DRVNAME, substream); if (ret < 0) { dev_err(card->dev, "request irq for DMA failed\n"); return ret; } } return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, card->dev, ls_pcm_hardware.buffer_bytes_max); } const struct snd_soc_component_driver loongson_i2s_component = { .name = LS_I2S_DRVNAME, .open = loongson_pcm_open, .close = loongson_pcm_close, .hw_params = loongson_pcm_hw_params, .trigger = loongson_pcm_trigger, .pointer = loongson_pcm_pointer, .mmap = loongson_pcm_mmap, .pcm_construct = loongson_pcm_new, };
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1