1 /* 2 * ALSA SoC Synopsys PIO PCM for I2S driver 3 * 4 * sound/soc/dwc/designware_pcm.c 5 * 6 * Copyright (C) 2016 Synopsys 7 * Jose Abreu <joabreu@synopsys.com> 8 * 9 * This file is licensed under the terms of the GNU General Public 10 * License version 2. This program is licensed "as is" without any 11 * warranty of any kind, whether express or implied. 12 */ 13 14 #include <linux/io.h> 15 #include <linux/rcupdate.h> 16 #include <sound/pcm.h> 17 #include <sound/pcm_params.h> 18 #include "local.h" 19 20 #define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) 21 #define PERIOD_BYTES_MIN 4096 22 #define PERIODS_MIN 2 23 24 #define dw_pcm_tx_fn(sample_bits) \ 25 static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \ 26 struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \ 27 bool *period_elapsed) \ 28 { \ 29 const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ 30 unsigned int period_pos = tx_ptr % runtime->period_size; \ 31 int i; \ 32 \ 33 for (i = 0; i < dev->fifo_th; i++) { \ 34 iowrite32(p[tx_ptr][0], dev->i2s_base + dev->l_reg); \ 35 iowrite32(p[tx_ptr][1], dev->i2s_base + dev->r_reg); \ 36 period_pos++; \ 37 if (++tx_ptr >= runtime->buffer_size) \ 38 tx_ptr = 0; \ 39 } \ 40 *period_elapsed = period_pos >= runtime->period_size; \ 41 return tx_ptr; \ 42 } 43 44 #define dw_pcm_rx_fn(sample_bits) \ 45 static unsigned int dw_pcm_rx_##sample_bits(struct dw_i2s_dev *dev, \ 46 struct snd_pcm_runtime *runtime, unsigned int rx_ptr, \ 47 bool *period_elapsed) \ 48 { \ 49 u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ 50 unsigned int period_pos = rx_ptr % runtime->period_size; \ 51 int i; \ 52 \ 53 for (i = 0; i < dev->fifo_th; i++) { \ 54 p[rx_ptr][0] = ioread32(dev->i2s_base + dev->l_reg); \ 55 p[rx_ptr][1] = ioread32(dev->i2s_base + dev->r_reg); \ 56 period_pos++; \ 57 if (++rx_ptr >= runtime->buffer_size) \ 58 rx_ptr = 0; \ 59 } \ 60 *period_elapsed = period_pos >= runtime->period_size; \ 61 return rx_ptr; \ 62 } 63 64 dw_pcm_tx_fn(16); 65 dw_pcm_tx_fn(32); 66 dw_pcm_rx_fn(16); 67 dw_pcm_rx_fn(32); 68 69 #undef dw_pcm_tx_fn 70 #undef dw_pcm_rx_fn 71 72 static const struct snd_pcm_hardware dw_pcm_hardware = { 73 .info = SNDRV_PCM_INFO_INTERLEAVED | 74 SNDRV_PCM_INFO_MMAP | 75 SNDRV_PCM_INFO_MMAP_VALID | 76 SNDRV_PCM_INFO_BLOCK_TRANSFER, 77 .rates = SNDRV_PCM_RATE_32000 | 78 SNDRV_PCM_RATE_44100 | 79 SNDRV_PCM_RATE_48000, 80 .rate_min = 32000, 81 .rate_max = 48000, 82 .formats = SNDRV_PCM_FMTBIT_S16_LE | 83 SNDRV_PCM_FMTBIT_S24_LE | 84 SNDRV_PCM_FMTBIT_S32_LE, 85 .channels_min = 2, 86 .channels_max = 2, 87 .buffer_bytes_max = BUFFER_BYTES_MAX, 88 .period_bytes_min = PERIOD_BYTES_MIN, 89 .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, 90 .periods_min = PERIODS_MIN, 91 .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, 92 .fifo_size = 16, 93 }; 94 95 static void dw_pcm_transfer(struct dw_i2s_dev *dev, bool push) 96 { 97 struct snd_pcm_substream *substream; 98 bool active, period_elapsed; 99 100 rcu_read_lock(); 101 if (push) 102 substream = rcu_dereference(dev->tx_substream); 103 else 104 substream = rcu_dereference(dev->rx_substream); 105 active = substream && snd_pcm_running(substream); 106 if (active) { 107 unsigned int ptr; 108 unsigned int new_ptr; 109 110 if (push) { 111 ptr = READ_ONCE(dev->tx_ptr); 112 new_ptr = dev->tx_fn(dev, substream->runtime, ptr, 113 &period_elapsed); 114 cmpxchg(&dev->tx_ptr, ptr, new_ptr); 115 } else { 116 ptr = READ_ONCE(dev->rx_ptr); 117 new_ptr = dev->rx_fn(dev, substream->runtime, ptr, 118 &period_elapsed); 119 cmpxchg(&dev->rx_ptr, ptr, new_ptr); 120 } 121 122 if (period_elapsed) 123 snd_pcm_period_elapsed(substream); 124 } 125 rcu_read_unlock(); 126 } 127 128 void dw_pcm_push_tx(struct dw_i2s_dev *dev) 129 { 130 dw_pcm_transfer(dev, true); 131 } 132 133 void dw_pcm_pop_rx(struct dw_i2s_dev *dev) 134 { 135 dw_pcm_transfer(dev, false); 136 } 137 138 static int dw_pcm_open(struct snd_soc_component *component, 139 struct snd_pcm_substream *substream) 140 { 141 struct snd_pcm_runtime *runtime = substream->runtime; 142 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 143 struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); 144 145 snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware); 146 snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); 147 runtime->private_data = dev; 148 149 return 0; 150 } 151 152 static int dw_pcm_close(struct snd_soc_component *component, 153 struct snd_pcm_substream *substream) 154 { 155 synchronize_rcu(); 156 return 0; 157 } 158 159 static int dw_pcm_hw_params(struct snd_soc_component *component, 160 struct snd_pcm_substream *substream, 161 struct snd_pcm_hw_params *hw_params) 162 { 163 struct snd_pcm_runtime *runtime = substream->runtime; 164 struct dw_i2s_dev *dev = runtime->private_data; 165 166 switch (params_channels(hw_params)) { 167 case 2: 168 break; 169 default: 170 dev_err(dev->dev, "invalid channels number\n"); 171 return -EINVAL; 172 } 173 174 switch (params_format(hw_params)) { 175 case SNDRV_PCM_FORMAT_S16_LE: 176 dev->tx_fn = dw_pcm_tx_16; 177 dev->rx_fn = dw_pcm_rx_16; 178 break; 179 case SNDRV_PCM_FORMAT_S24_LE: 180 case SNDRV_PCM_FORMAT_S32_LE: 181 dev->tx_fn = dw_pcm_tx_32; 182 dev->rx_fn = dw_pcm_rx_32; 183 break; 184 default: 185 dev_err(dev->dev, "invalid format\n"); 186 return -EINVAL; 187 } 188 189 return 0; 190 } 191 192 static int dw_pcm_trigger(struct snd_soc_component *component, 193 struct snd_pcm_substream *substream, int cmd) 194 { 195 struct snd_pcm_runtime *runtime = substream->runtime; 196 struct dw_i2s_dev *dev = runtime->private_data; 197 int ret = 0; 198 199 switch (cmd) { 200 case SNDRV_PCM_TRIGGER_START: 201 case SNDRV_PCM_TRIGGER_RESUME: 202 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 203 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 204 WRITE_ONCE(dev->tx_ptr, 0); 205 rcu_assign_pointer(dev->tx_substream, substream); 206 } else { 207 WRITE_ONCE(dev->rx_ptr, 0); 208 rcu_assign_pointer(dev->rx_substream, substream); 209 } 210 break; 211 case SNDRV_PCM_TRIGGER_STOP: 212 case SNDRV_PCM_TRIGGER_SUSPEND: 213 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 214 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 215 rcu_assign_pointer(dev->tx_substream, NULL); 216 else 217 rcu_assign_pointer(dev->rx_substream, NULL); 218 break; 219 default: 220 ret = -EINVAL; 221 break; 222 } 223 224 return ret; 225 } 226 227 static snd_pcm_uframes_t dw_pcm_pointer(struct snd_soc_component *component, 228 struct snd_pcm_substream *substream) 229 { 230 struct snd_pcm_runtime *runtime = substream->runtime; 231 struct dw_i2s_dev *dev = runtime->private_data; 232 snd_pcm_uframes_t pos; 233 234 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 235 pos = READ_ONCE(dev->tx_ptr); 236 else 237 pos = READ_ONCE(dev->rx_ptr); 238 239 return pos < runtime->buffer_size ? pos : 0; 240 } 241 242 static int dw_pcm_new(struct snd_soc_component *component, 243 struct snd_soc_pcm_runtime *rtd) 244 { 245 size_t size = dw_pcm_hardware.buffer_bytes_max; 246 247 snd_pcm_set_managed_buffer_all(rtd->pcm, 248 SNDRV_DMA_TYPE_CONTINUOUS, 249 NULL, size, size); 250 return 0; 251 } 252 253 static const struct snd_soc_component_driver dw_pcm_component = { 254 .open = dw_pcm_open, 255 .close = dw_pcm_close, 256 .hw_params = dw_pcm_hw_params, 257 .trigger = dw_pcm_trigger, 258 .pointer = dw_pcm_pointer, 259 .pcm_construct = dw_pcm_new, 260 }; 261 262 int dw_pcm_register(struct platform_device *pdev) 263 { 264 return devm_snd_soc_register_component(&pdev->dev, &dw_pcm_component, 265 NULL, 0); 266 } 267
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.