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 + LRBR_LTHR(0)); \ 35 iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \ 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 + LRBR_LTHR(0)); \ 55 p[rx_ptr][1] = ioread32(dev->i2s_base + RRBR_RTHR(0)); \ 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_pcm_substream *substream) 139 { 140 struct snd_pcm_runtime *runtime = substream->runtime; 141 struct snd_soc_pcm_runtime *rtd = substream->private_data; 142 struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); 143 144 snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware); 145 snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); 146 runtime->private_data = dev; 147 148 return 0; 149 } 150 151 static int dw_pcm_close(struct snd_pcm_substream *substream) 152 { 153 synchronize_rcu(); 154 return 0; 155 } 156 157 static int dw_pcm_hw_params(struct snd_pcm_substream *substream, 158 struct snd_pcm_hw_params *hw_params) 159 { 160 struct snd_pcm_runtime *runtime = substream->runtime; 161 struct dw_i2s_dev *dev = runtime->private_data; 162 int ret; 163 164 switch (params_channels(hw_params)) { 165 case 2: 166 break; 167 default: 168 dev_err(dev->dev, "invalid channels number\n"); 169 return -EINVAL; 170 } 171 172 switch (params_format(hw_params)) { 173 case SNDRV_PCM_FORMAT_S16_LE: 174 dev->tx_fn = dw_pcm_tx_16; 175 dev->rx_fn = dw_pcm_rx_16; 176 break; 177 case SNDRV_PCM_FORMAT_S24_LE: 178 case SNDRV_PCM_FORMAT_S32_LE: 179 dev->tx_fn = dw_pcm_tx_32; 180 dev->rx_fn = dw_pcm_rx_32; 181 break; 182 default: 183 dev_err(dev->dev, "invalid format\n"); 184 return -EINVAL; 185 } 186 187 ret = snd_pcm_lib_malloc_pages(substream, 188 params_buffer_bytes(hw_params)); 189 if (ret < 0) 190 return ret; 191 else 192 return 0; 193 } 194 195 static int dw_pcm_hw_free(struct snd_pcm_substream *substream) 196 { 197 return snd_pcm_lib_free_pages(substream); 198 } 199 200 static int dw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 201 { 202 struct snd_pcm_runtime *runtime = substream->runtime; 203 struct dw_i2s_dev *dev = runtime->private_data; 204 int ret = 0; 205 206 switch (cmd) { 207 case SNDRV_PCM_TRIGGER_START: 208 case SNDRV_PCM_TRIGGER_RESUME: 209 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 210 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 211 WRITE_ONCE(dev->tx_ptr, 0); 212 rcu_assign_pointer(dev->tx_substream, substream); 213 } else { 214 WRITE_ONCE(dev->rx_ptr, 0); 215 rcu_assign_pointer(dev->rx_substream, substream); 216 } 217 break; 218 case SNDRV_PCM_TRIGGER_STOP: 219 case SNDRV_PCM_TRIGGER_SUSPEND: 220 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 221 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 222 rcu_assign_pointer(dev->tx_substream, NULL); 223 else 224 rcu_assign_pointer(dev->rx_substream, NULL); 225 break; 226 default: 227 ret = -EINVAL; 228 break; 229 } 230 231 return ret; 232 } 233 234 static snd_pcm_uframes_t dw_pcm_pointer(struct snd_pcm_substream *substream) 235 { 236 struct snd_pcm_runtime *runtime = substream->runtime; 237 struct dw_i2s_dev *dev = runtime->private_data; 238 snd_pcm_uframes_t pos; 239 240 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 241 pos = READ_ONCE(dev->tx_ptr); 242 else 243 pos = READ_ONCE(dev->rx_ptr); 244 245 return pos < runtime->buffer_size ? pos : 0; 246 } 247 248 static int dw_pcm_new(struct snd_soc_pcm_runtime *rtd) 249 { 250 size_t size = dw_pcm_hardware.buffer_bytes_max; 251 252 return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, 253 SNDRV_DMA_TYPE_CONTINUOUS, 254 snd_dma_continuous_data(GFP_KERNEL), size, size); 255 } 256 257 static void dw_pcm_free(struct snd_pcm *pcm) 258 { 259 snd_pcm_lib_preallocate_free_for_all(pcm); 260 } 261 262 static const struct snd_pcm_ops dw_pcm_ops = { 263 .open = dw_pcm_open, 264 .close = dw_pcm_close, 265 .ioctl = snd_pcm_lib_ioctl, 266 .hw_params = dw_pcm_hw_params, 267 .hw_free = dw_pcm_hw_free, 268 .trigger = dw_pcm_trigger, 269 .pointer = dw_pcm_pointer, 270 }; 271 272 static const struct snd_soc_platform_driver dw_pcm_platform = { 273 .pcm_new = dw_pcm_new, 274 .pcm_free = dw_pcm_free, 275 .ops = &dw_pcm_ops, 276 }; 277 278 int dw_pcm_register(struct platform_device *pdev) 279 { 280 return devm_snd_soc_register_platform(&pdev->dev, &dw_pcm_platform); 281 } 282