diff options
| author | Manuel Lauss <mano@roarinelk.homelinux.net> | 2008-07-09 10:27:56 -0400 |
|---|---|---|
| committer | Jaroslav Kysela <perex@perex.cz> | 2008-07-10 03:33:07 -0400 |
| commit | 4a161d235b68eb7234f40106560c488a1bdb3851 (patch) | |
| tree | ef88570d98c10f1bfeef56bfeddbe8009d1fcaa5 /sound/soc/au1x/dbdma2.c | |
| parent | bf41534506a0572c06c8f34d12aa489be4c8780e (diff) | |
ALSA: ASoC: Au12x0/Au1550 PSC Audio support
Audio for Au12x0/Au1550 PSCs in AC97 and I2S mode, for ASoC v1 framework.
- DBDMA, AC97 and I2S drivers
- sample AC97 machine code (Db1200)
Signed-off-by: Manuel Lauss <mano@roarinelk.homelinux.net>
Signed-off-by: Liam Girdwood <lg@opensource.wolfsonmicro.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Diffstat (limited to 'sound/soc/au1x/dbdma2.c')
| -rw-r--r-- | sound/soc/au1x/dbdma2.c | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/sound/soc/au1x/dbdma2.c b/sound/soc/au1x/dbdma2.c new file mode 100644 index 000000000000..1466d9328800 --- /dev/null +++ b/sound/soc/au1x/dbdma2.c | |||
| @@ -0,0 +1,421 @@ | |||
| 1 | /* | ||
| 2 | * Au12x0/Au1550 PSC ALSA ASoC audio support. | ||
| 3 | * | ||
| 4 | * (c) 2007-2008 MSC Vertriebsges.m.b.H., | ||
| 5 | * Manuel Lauss <mano@roarinelk.homelinux.net> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify | ||
| 8 | * it under the terms of the GNU General Public License version 2 as | ||
| 9 | * published by the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * DMA glue for Au1x-PSC audio. | ||
| 12 | * | ||
| 13 | * NOTE: all of these drivers can only work with a SINGLE instance | ||
| 14 | * of a PSC. Multiple independent audio devices are impossible | ||
| 15 | * with ASoC v1. | ||
| 16 | */ | ||
| 17 | |||
| 18 | |||
| 19 | #include <linux/module.h> | ||
| 20 | #include <linux/init.h> | ||
| 21 | #include <linux/platform_device.h> | ||
| 22 | #include <linux/slab.h> | ||
| 23 | #include <linux/dma-mapping.h> | ||
| 24 | |||
| 25 | #include <sound/core.h> | ||
| 26 | #include <sound/pcm.h> | ||
| 27 | #include <sound/pcm_params.h> | ||
| 28 | #include <sound/soc.h> | ||
| 29 | |||
| 30 | #include <asm/mach-au1x00/au1000.h> | ||
| 31 | #include <asm/mach-au1x00/au1xxx_dbdma.h> | ||
| 32 | #include <asm/mach-au1x00/au1xxx_psc.h> | ||
| 33 | |||
| 34 | #include "psc.h" | ||
| 35 | |||
| 36 | /*#define PCM_DEBUG*/ | ||
| 37 | |||
| 38 | #define MSG(x...) printk(KERN_INFO "au1xpsc_pcm: " x) | ||
| 39 | #ifdef PCM_DEBUG | ||
| 40 | #define DBG MSG | ||
| 41 | #else | ||
| 42 | #define DBG(x...) do {} while (0) | ||
| 43 | #endif | ||
| 44 | |||
| 45 | struct au1xpsc_audio_dmadata { | ||
| 46 | /* DDMA control data */ | ||
| 47 | unsigned int ddma_id; /* DDMA direction ID for this PSC */ | ||
| 48 | u32 ddma_chan; /* DDMA context */ | ||
| 49 | |||
| 50 | /* PCM context (for irq handlers) */ | ||
| 51 | struct snd_pcm_substream *substream; | ||
| 52 | unsigned long curr_period; /* current segment DDMA is working on */ | ||
| 53 | unsigned long q_period; /* queue period(s) */ | ||
| 54 | unsigned long dma_area; /* address of queued DMA area */ | ||
| 55 | unsigned long dma_area_s; /* start address of DMA area */ | ||
| 56 | unsigned long pos; /* current byte position being played */ | ||
| 57 | unsigned long periods; /* number of SG segments in total */ | ||
| 58 | unsigned long period_bytes; /* size in bytes of one SG segment */ | ||
| 59 | |||
| 60 | /* runtime data */ | ||
| 61 | int msbits; | ||
| 62 | }; | ||
| 63 | |||
| 64 | /* instance data. There can be only one, MacLeod!!!! */ | ||
| 65 | static struct au1xpsc_audio_dmadata *au1xpsc_audio_pcmdma[2]; | ||
| 66 | |||
| 67 | /* | ||
| 68 | * These settings are somewhat okay, at least on my machine audio plays | ||
| 69 | * almost skip-free. Especially the 64kB buffer seems to help a LOT. | ||
| 70 | */ | ||
| 71 | #define AU1XPSC_PERIOD_MIN_BYTES 1024 | ||
| 72 | #define AU1XPSC_BUFFER_MIN_BYTES 65536 | ||
| 73 | |||
| 74 | #define AU1XPSC_PCM_FMTS \ | ||
| 75 | (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ | ||
| 76 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ | ||
| 77 | SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \ | ||
| 78 | SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE | \ | ||
| 79 | SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE | \ | ||
| 80 | 0) | ||
| 81 | |||
| 82 | /* PCM hardware DMA capabilities - platform specific */ | ||
| 83 | static const struct snd_pcm_hardware au1xpsc_pcm_hardware = { | ||
| 84 | .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | | ||
| 85 | SNDRV_PCM_INFO_INTERLEAVED, | ||
| 86 | .formats = AU1XPSC_PCM_FMTS, | ||
| 87 | .period_bytes_min = AU1XPSC_PERIOD_MIN_BYTES, | ||
| 88 | .period_bytes_max = 4096 * 1024 - 1, | ||
| 89 | .periods_min = 2, | ||
| 90 | .periods_max = 4096, /* 2 to as-much-as-you-like */ | ||
| 91 | .buffer_bytes_max = 4096 * 1024 - 1, | ||
| 92 | .fifo_size = 16, /* fifo entries of AC97/I2S PSC */ | ||
| 93 | }; | ||
| 94 | |||
| 95 | static void au1x_pcm_queue_tx(struct au1xpsc_audio_dmadata *cd) | ||
| 96 | { | ||
| 97 | au1xxx_dbdma_put_source_flags(cd->ddma_chan, | ||
| 98 | (void *)phys_to_virt(cd->dma_area), | ||
| 99 | cd->period_bytes, DDMA_FLAGS_IE); | ||
| 100 | |||
| 101 | /* update next-to-queue period */ | ||
| 102 | ++cd->q_period; | ||
| 103 | cd->dma_area += cd->period_bytes; | ||
| 104 | if (cd->q_period >= cd->periods) { | ||
| 105 | cd->q_period = 0; | ||
| 106 | cd->dma_area = cd->dma_area_s; | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | static void au1x_pcm_queue_rx(struct au1xpsc_audio_dmadata *cd) | ||
| 111 | { | ||
| 112 | au1xxx_dbdma_put_dest_flags(cd->ddma_chan, | ||
| 113 | (void *)phys_to_virt(cd->dma_area), | ||
| 114 | cd->period_bytes, DDMA_FLAGS_IE); | ||
| 115 | |||
| 116 | /* update next-to-queue period */ | ||
| 117 | ++cd->q_period; | ||
| 118 | cd->dma_area += cd->period_bytes; | ||
| 119 | if (cd->q_period >= cd->periods) { | ||
| 120 | cd->q_period = 0; | ||
| 121 | cd->dma_area = cd->dma_area_s; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | static void au1x_pcm_dmatx_cb(int irq, void *dev_id) | ||
| 126 | { | ||
| 127 | struct au1xpsc_audio_dmadata *cd = dev_id; | ||
| 128 | |||
| 129 | cd->pos += cd->period_bytes; | ||
| 130 | if (++cd->curr_period >= cd->periods) { | ||
| 131 | cd->pos = 0; | ||
| 132 | cd->curr_period = 0; | ||
| 133 | } | ||
| 134 | snd_pcm_period_elapsed(cd->substream); | ||
| 135 | au1x_pcm_queue_tx(cd); | ||
| 136 | } | ||
| 137 | |||
| 138 | static void au1x_pcm_dmarx_cb(int irq, void *dev_id) | ||
| 139 | { | ||
| 140 | struct au1xpsc_audio_dmadata *cd = dev_id; | ||
| 141 | |||
| 142 | cd->pos += cd->period_bytes; | ||
| 143 | if (++cd->curr_period >= cd->periods) { | ||
| 144 | cd->pos = 0; | ||
| 145 | cd->curr_period = 0; | ||
| 146 | } | ||
| 147 | snd_pcm_period_elapsed(cd->substream); | ||
| 148 | au1x_pcm_queue_rx(cd); | ||
| 149 | } | ||
| 150 | |||
| 151 | static void au1x_pcm_dbdma_free(struct au1xpsc_audio_dmadata *pcd) | ||
| 152 | { | ||
| 153 | if (pcd->ddma_chan) { | ||
| 154 | au1xxx_dbdma_stop(pcd->ddma_chan); | ||
| 155 | au1xxx_dbdma_reset(pcd->ddma_chan); | ||
| 156 | au1xxx_dbdma_chan_free(pcd->ddma_chan); | ||
| 157 | pcd->ddma_chan = 0; | ||
| 158 | pcd->msbits = 0; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | /* in case of missing DMA ring or changed TX-source / RX-dest bit widths, | ||
| 163 | * allocate (or reallocate) a 2-descriptor DMA ring with bit depth according | ||
| 164 | * to ALSA-supplied sample depth. This is due to limitations in the dbdma api | ||
| 165 | * (cannot adjust source/dest widths of already allocated descriptor ring). | ||
| 166 | */ | ||
| 167 | static int au1x_pcm_dbdma_realloc(struct au1xpsc_audio_dmadata *pcd, | ||
| 168 | int stype, int msbits) | ||
| 169 | { | ||
| 170 | /* DMA only in 8/16/32 bit widths */ | ||
| 171 | if (msbits == 24) | ||
| 172 | msbits = 32; | ||
| 173 | |||
| 174 | /* check current config: correct bits and descriptors allocated? */ | ||
| 175 | if ((pcd->ddma_chan) && (msbits == pcd->msbits)) | ||
| 176 | goto out; /* all ok! */ | ||
| 177 | |||
| 178 | au1x_pcm_dbdma_free(pcd); | ||
| 179 | |||
| 180 | if (stype == PCM_RX) | ||
| 181 | pcd->ddma_chan = au1xxx_dbdma_chan_alloc(pcd->ddma_id, | ||
| 182 | DSCR_CMD0_ALWAYS, | ||
| 183 | au1x_pcm_dmarx_cb, (void *)pcd); | ||
| 184 | else | ||
| 185 | pcd->ddma_chan = au1xxx_dbdma_chan_alloc(DSCR_CMD0_ALWAYS, | ||
| 186 | pcd->ddma_id, | ||
| 187 | au1x_pcm_dmatx_cb, (void *)pcd); | ||
| 188 | |||
| 189 | if (!pcd->ddma_chan) | ||
| 190 | return -ENOMEM;; | ||
| 191 | |||
| 192 | au1xxx_dbdma_set_devwidth(pcd->ddma_chan, msbits); | ||
| 193 | au1xxx_dbdma_ring_alloc(pcd->ddma_chan, 2); | ||
| 194 | |||
| 195 | pcd->msbits = msbits; | ||
| 196 | |||
| 197 | au1xxx_dbdma_stop(pcd->ddma_chan); | ||
| 198 | au1xxx_dbdma_reset(pcd->ddma_chan); | ||
| 199 | |||
| 200 | out: | ||
| 201 | return 0; | ||
| 202 | } | ||
| 203 | |||
| 204 | static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream, | ||
| 205 | struct snd_pcm_hw_params *params) | ||
| 206 | { | ||
| 207 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 208 | struct au1xpsc_audio_dmadata *pcd; | ||
| 209 | int stype, ret; | ||
| 210 | |||
| 211 | ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); | ||
| 212 | if (ret < 0) | ||
| 213 | goto out; | ||
| 214 | |||
| 215 | stype = SUBSTREAM_TYPE(substream); | ||
| 216 | pcd = au1xpsc_audio_pcmdma[stype]; | ||
| 217 | |||
| 218 | DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %d " | ||
| 219 | "runtime->min_align %d\n", | ||
| 220 | (unsigned long)runtime->dma_area, | ||
| 221 | (unsigned long)runtime->dma_addr, runtime->dma_bytes, | ||
| 222 | runtime->min_align); | ||
| 223 | |||
| 224 | DBG("bits %d frags %d frag_bytes %d is_rx %d\n", params->msbits, | ||
| 225 | params_periods(params), params_period_bytes(params), stype); | ||
| 226 | |||
| 227 | ret = au1x_pcm_dbdma_realloc(pcd, stype, params->msbits); | ||
| 228 | if (ret) { | ||
| 229 | MSG("DDMA channel (re)alloc failed!\n"); | ||
| 230 | goto out; | ||
| 231 | } | ||
| 232 | |||
| 233 | pcd->substream = substream; | ||
| 234 | pcd->period_bytes = params_period_bytes(params); | ||
| 235 | pcd->periods = params_periods(params); | ||
| 236 | pcd->dma_area_s = pcd->dma_area = (unsigned long)runtime->dma_addr; | ||
| 237 | pcd->q_period = 0; | ||
| 238 | pcd->curr_period = 0; | ||
| 239 | pcd->pos = 0; | ||
| 240 | |||
| 241 | ret = 0; | ||
| 242 | out: | ||
| 243 | return ret; | ||
| 244 | } | ||
| 245 | |||
| 246 | static int au1xpsc_pcm_hw_free(struct snd_pcm_substream *substream) | ||
| 247 | { | ||
| 248 | snd_pcm_lib_free_pages(substream); | ||
| 249 | return 0; | ||
| 250 | } | ||
| 251 | |||
| 252 | static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream) | ||
| 253 | { | ||
| 254 | struct au1xpsc_audio_dmadata *pcd = | ||
| 255 | au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]; | ||
| 256 | |||
| 257 | au1xxx_dbdma_reset(pcd->ddma_chan); | ||
| 258 | |||
| 259 | if (SUBSTREAM_TYPE(substream) == PCM_RX) { | ||
| 260 | au1x_pcm_queue_rx(pcd); | ||
| 261 | au1x_pcm_queue_rx(pcd); | ||
| 262 | } else { | ||
| 263 | au1x_pcm_queue_tx(pcd); | ||
| 264 | au1x_pcm_queue_tx(pcd); | ||
| 265 | } | ||
| 266 | |||
| 267 | return 0; | ||
| 268 | } | ||
| 269 | |||
| 270 | static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
| 271 | { | ||
| 272 | u32 c = au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]->ddma_chan; | ||
| 273 | |||
| 274 | switch (cmd) { | ||
| 275 | case SNDRV_PCM_TRIGGER_START: | ||
| 276 | case SNDRV_PCM_TRIGGER_RESUME: | ||
| 277 | au1xxx_dbdma_start(c); | ||
| 278 | break; | ||
| 279 | case SNDRV_PCM_TRIGGER_STOP: | ||
| 280 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
| 281 | au1xxx_dbdma_stop(c); | ||
| 282 | break; | ||
| 283 | default: | ||
| 284 | return -EINVAL; | ||
| 285 | } | ||
| 286 | return 0; | ||
| 287 | } | ||
| 288 | |||
| 289 | static snd_pcm_uframes_t | ||
| 290 | au1xpsc_pcm_pointer(struct snd_pcm_substream *substream) | ||
| 291 | { | ||
| 292 | return bytes_to_frames(substream->runtime, | ||
| 293 | au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]->pos); | ||
| 294 | } | ||
| 295 | |||
| 296 | static int au1xpsc_pcm_open(struct snd_pcm_substream *substream) | ||
| 297 | { | ||
| 298 | snd_soc_set_runtime_hwparams(substream, &au1xpsc_pcm_hardware); | ||
| 299 | return 0; | ||
| 300 | } | ||
| 301 | |||
| 302 | static int au1xpsc_pcm_close(struct snd_pcm_substream *substream) | ||
| 303 | { | ||
| 304 | au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]); | ||
| 305 | return 0; | ||
| 306 | } | ||
| 307 | |||
| 308 | struct snd_pcm_ops au1xpsc_pcm_ops = { | ||
| 309 | .open = au1xpsc_pcm_open, | ||
| 310 | .close = au1xpsc_pcm_close, | ||
| 311 | .ioctl = snd_pcm_lib_ioctl, | ||
| 312 | .hw_params = au1xpsc_pcm_hw_params, | ||
| 313 | .hw_free = au1xpsc_pcm_hw_free, | ||
| 314 | .prepare = au1xpsc_pcm_prepare, | ||
| 315 | .trigger = au1xpsc_pcm_trigger, | ||
| 316 | .pointer = au1xpsc_pcm_pointer, | ||
| 317 | }; | ||
| 318 | |||
| 319 | static void au1xpsc_pcm_free_dma_buffers(struct snd_pcm *pcm) | ||
| 320 | { | ||
| 321 | snd_pcm_lib_preallocate_free_for_all(pcm); | ||
| 322 | } | ||
| 323 | |||
| 324 | static int au1xpsc_pcm_new(struct snd_card *card, | ||
| 325 | struct snd_soc_dai *dai, | ||
| 326 | struct snd_pcm *pcm) | ||
| 327 | { | ||
| 328 | snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, | ||
| 329 | card->dev, AU1XPSC_BUFFER_MIN_BYTES, (4096 * 1024) - 1); | ||
| 330 | |||
| 331 | return 0; | ||
| 332 | } | ||
| 333 | |||
| 334 | static int au1xpsc_pcm_probe(struct platform_device *pdev) | ||
| 335 | { | ||
| 336 | struct resource *r; | ||
| 337 | int ret; | ||
| 338 | |||
| 339 | if (au1xpsc_audio_pcmdma[PCM_TX] || au1xpsc_audio_pcmdma[PCM_RX]) | ||
| 340 | return -EBUSY; | ||
| 341 | |||
| 342 | /* TX DMA */ | ||
| 343 | au1xpsc_audio_pcmdma[PCM_TX] | ||
| 344 | = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL); | ||
| 345 | if (!au1xpsc_audio_pcmdma[PCM_TX]) | ||
| 346 | return -ENOMEM; | ||
| 347 | |||
| 348 | r = platform_get_resource(pdev, IORESOURCE_DMA, 0); | ||
| 349 | if (!r) { | ||
| 350 | ret = -ENODEV; | ||
| 351 | goto out1; | ||
| 352 | } | ||
| 353 | (au1xpsc_audio_pcmdma[PCM_TX])->ddma_id = r->start; | ||
| 354 | |||
| 355 | /* RX DMA */ | ||
| 356 | au1xpsc_audio_pcmdma[PCM_RX] | ||
| 357 | = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL); | ||
| 358 | if (!au1xpsc_audio_pcmdma[PCM_RX]) | ||
| 359 | return -ENOMEM; | ||
| 360 | |||
| 361 | r = platform_get_resource(pdev, IORESOURCE_DMA, 1); | ||
| 362 | if (!r) { | ||
| 363 | ret = -ENODEV; | ||
| 364 | goto out2; | ||
| 365 | } | ||
| 366 | (au1xpsc_audio_pcmdma[PCM_RX])->ddma_id = r->start; | ||
| 367 | |||
| 368 | return 0; | ||
| 369 | |||
| 370 | out2: | ||
| 371 | kfree(au1xpsc_audio_pcmdma[PCM_RX]); | ||
| 372 | au1xpsc_audio_pcmdma[PCM_RX] = NULL; | ||
| 373 | out1: | ||
| 374 | kfree(au1xpsc_audio_pcmdma[PCM_TX]); | ||
| 375 | au1xpsc_audio_pcmdma[PCM_TX] = NULL; | ||
| 376 | return ret; | ||
| 377 | } | ||
| 378 | |||
| 379 | static int au1xpsc_pcm_remove(struct platform_device *pdev) | ||
| 380 | { | ||
| 381 | int i; | ||
| 382 | |||
| 383 | for (i = 0; i < 2; i++) { | ||
| 384 | if (au1xpsc_audio_pcmdma[i]) { | ||
| 385 | au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[i]); | ||
| 386 | kfree(au1xpsc_audio_pcmdma[i]); | ||
| 387 | au1xpsc_audio_pcmdma[i] = NULL; | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 391 | return 0; | ||
| 392 | } | ||
| 393 | |||
| 394 | /* au1xpsc audio platform */ | ||
| 395 | struct snd_soc_platform au1xpsc_soc_platform = { | ||
| 396 | .name = "au1xpsc-pcm-dbdma", | ||
| 397 | .probe = au1xpsc_pcm_probe, | ||
| 398 | .remove = au1xpsc_pcm_remove, | ||
| 399 | .pcm_ops = &au1xpsc_pcm_ops, | ||
| 400 | .pcm_new = au1xpsc_pcm_new, | ||
| 401 | .pcm_free = au1xpsc_pcm_free_dma_buffers, | ||
| 402 | }; | ||
| 403 | EXPORT_SYMBOL_GPL(au1xpsc_soc_platform); | ||
| 404 | |||
| 405 | static int __init au1xpsc_audio_dbdma_init(void) | ||
| 406 | { | ||
| 407 | au1xpsc_audio_pcmdma[PCM_TX] = NULL; | ||
| 408 | au1xpsc_audio_pcmdma[PCM_RX] = NULL; | ||
| 409 | return 0; | ||
| 410 | } | ||
| 411 | |||
| 412 | static void __exit au1xpsc_audio_dbdma_exit(void) | ||
| 413 | { | ||
| 414 | } | ||
| 415 | |||
| 416 | module_init(au1xpsc_audio_dbdma_init); | ||
| 417 | module_exit(au1xpsc_audio_dbdma_exit); | ||
| 418 | |||
| 419 | MODULE_LICENSE("GPL"); | ||
| 420 | MODULE_DESCRIPTION("Au12x0/Au1550 PSC Audio DMA driver"); | ||
| 421 | MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); | ||
