mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-28 14:58:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
134
sound/Kconfig
Normal file
134
sound/Kconfig
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
menuconfig SOUND
|
||||
tristate "Sound card support"
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
If you have a sound card in your computer, i.e. if it can say more
|
||||
than an occasional beep, say Y. Be sure to have all the information
|
||||
about your sound card and its configuration down (I/O port,
|
||||
interrupt and DMA channel), because you will be asked for it.
|
||||
|
||||
You want to read the Sound-HOWTO, available from
|
||||
<http://www.tldp.org/docs.html#howto>. General information about
|
||||
the modular sound system is contained in the files
|
||||
<file:Documentation/sound/oss/Introduction>. The file
|
||||
<file:Documentation/sound/oss/README.OSS> contains some slightly
|
||||
outdated but still useful information as well. Newer sound
|
||||
driver documentation is found in <file:Documentation/sound/alsa/*>.
|
||||
|
||||
If you have a PnP sound card and you want to configure it at boot
|
||||
time using the ISA PnP tools (read
|
||||
<http://www.roestock.demon.co.uk/isapnptools/>), then you need to
|
||||
compile the sound card support as a module and load that module
|
||||
after the PnP configuration is finished. To do this, choose M here
|
||||
and read <file:Documentation/sound/oss/README.modules>; the module
|
||||
will be called soundcore.
|
||||
|
||||
if SOUND
|
||||
|
||||
config SOUND_OSS_CORE
|
||||
bool
|
||||
default n
|
||||
|
||||
config SOUND_OSS_CORE_PRECLAIM
|
||||
bool "Preclaim OSS device numbers"
|
||||
depends on SOUND_OSS_CORE
|
||||
default y
|
||||
help
|
||||
With this option enabled, the kernel will claim all OSS device
|
||||
numbers if any OSS support (native or emulation) is enabled
|
||||
whether the respective module is loaded or not and try to load the
|
||||
appropriate module using sound-slot/service-* and char-major-*
|
||||
module aliases when one of the device numbers is opened. With
|
||||
this option disabled, kernel will only claim actually in-use
|
||||
device numbers and opening a missing device will generate only the
|
||||
standard char-major-* aliases.
|
||||
|
||||
The only visible difference is use of additional module aliases
|
||||
and whether OSS sound devices appear multiple times in
|
||||
/proc/devices. sound-slot/service-* module aliases are scheduled
|
||||
to be removed (ie. PRECLAIM won't be available) and this option is
|
||||
to make the transition easier. This option can be overridden
|
||||
during boot using the kernel parameter soundcore.preclaim_oss.
|
||||
|
||||
Disabling this allows alternative OSS implementations.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
source "sound/oss/dmasound/Kconfig"
|
||||
|
||||
if !M68K && !UML
|
||||
|
||||
menuconfig SND
|
||||
tristate "Advanced Linux Sound Architecture"
|
||||
help
|
||||
Say 'Y' or 'M' to enable ALSA (Advanced Linux Sound Architecture),
|
||||
the new base sound system.
|
||||
|
||||
For more information, see <http://www.alsa-project.org/>
|
||||
|
||||
if SND
|
||||
|
||||
source "sound/core/Kconfig"
|
||||
|
||||
source "sound/drivers/Kconfig"
|
||||
|
||||
source "sound/isa/Kconfig"
|
||||
|
||||
source "sound/pci/Kconfig"
|
||||
|
||||
source "sound/ppc/Kconfig"
|
||||
|
||||
source "sound/aoa/Kconfig"
|
||||
|
||||
source "sound/arm/Kconfig"
|
||||
|
||||
source "sound/atmel/Kconfig"
|
||||
|
||||
source "sound/spi/Kconfig"
|
||||
|
||||
source "sound/mips/Kconfig"
|
||||
|
||||
source "sound/sh/Kconfig"
|
||||
|
||||
# the following will depend on the order of config.
|
||||
# here assuming USB is defined before ALSA
|
||||
source "sound/usb/Kconfig"
|
||||
|
||||
source "sound/firewire/Kconfig"
|
||||
|
||||
# the following will depend on the order of config.
|
||||
# here assuming PCMCIA is defined before ALSA
|
||||
source "sound/pcmcia/Kconfig"
|
||||
|
||||
source "sound/sparc/Kconfig"
|
||||
|
||||
source "sound/parisc/Kconfig"
|
||||
|
||||
source "sound/soc/Kconfig"
|
||||
|
||||
endif # SND
|
||||
|
||||
menuconfig SOUND_PRIME
|
||||
tristate "Open Sound System (DEPRECATED)"
|
||||
select SOUND_OSS_CORE
|
||||
help
|
||||
Say 'Y' or 'M' to enable Open Sound System drivers.
|
||||
|
||||
if SOUND_PRIME
|
||||
|
||||
source "sound/oss/Kconfig"
|
||||
|
||||
endif # SOUND_PRIME
|
||||
|
||||
endif # !M68K
|
||||
|
||||
endif # SOUND
|
||||
|
||||
# AC97_BUS is used from both sound and ucb1400
|
||||
config AC97_BUS
|
||||
tristate
|
||||
help
|
||||
This is used to avoid config and link hard dependencies between the
|
||||
sound subsystem and other function drivers completely unrelated to
|
||||
sound although they're sharing the AC97 bus. Concerned drivers
|
||||
should "select" this.
|
||||
19
sound/Makefile
Normal file
19
sound/Makefile
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Makefile for the Linux sound card driver
|
||||
#
|
||||
|
||||
obj-$(CONFIG_SOUND) += soundcore.o
|
||||
obj-$(CONFIG_SOUND_PRIME) += sound_firmware.o
|
||||
obj-$(CONFIG_SOUND_PRIME) += oss/
|
||||
obj-$(CONFIG_DMASOUND) += oss/
|
||||
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
|
||||
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/
|
||||
obj-$(CONFIG_SND_AOA) += aoa/
|
||||
|
||||
# This one must be compilable even if sound is configured out
|
||||
obj-$(CONFIG_AC97_BUS) += ac97_bus.o
|
||||
|
||||
ifeq ($(CONFIG_SND),y)
|
||||
obj-y += last.o
|
||||
endif
|
||||
|
||||
soundcore-objs := sound_core.o
|
||||
77
sound/ac97_bus.c
Normal file
77
sound/ac97_bus.c
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Linux driver model AC97 bus interface
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: Jan 14, 2005
|
||||
* Copyright: (C) MontaVista Software Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/string.h>
|
||||
#include <sound/ac97_codec.h>
|
||||
|
||||
/*
|
||||
* Let drivers decide whether they want to support given codec from their
|
||||
* probe method. Drivers have direct access to the struct snd_ac97
|
||||
* structure and may decide based on the id field amongst other things.
|
||||
*/
|
||||
static int ac97_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ac97_bus_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && dev->driver->suspend)
|
||||
ret = dev->driver->suspend(dev, state);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ac97_bus_resume(struct device *dev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && dev->driver->resume)
|
||||
ret = dev->driver->resume(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
struct bus_type ac97_bus_type = {
|
||||
.name = "ac97",
|
||||
.match = ac97_bus_match,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = ac97_bus_suspend,
|
||||
.resume = ac97_bus_resume,
|
||||
#endif /* CONFIG_PM */
|
||||
};
|
||||
|
||||
static int __init ac97_bus_init(void)
|
||||
{
|
||||
return bus_register(&ac97_bus_type);
|
||||
}
|
||||
|
||||
subsys_initcall(ac97_bus_init);
|
||||
|
||||
static void __exit ac97_bus_exit(void)
|
||||
{
|
||||
bus_unregister(&ac97_bus_type);
|
||||
}
|
||||
|
||||
module_exit(ac97_bus_exit);
|
||||
|
||||
EXPORT_SYMBOL(ac97_bus_type);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
17
sound/aoa/Kconfig
Normal file
17
sound/aoa/Kconfig
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
menuconfig SND_AOA
|
||||
tristate "Apple Onboard Audio driver"
|
||||
depends on PPC_PMAC
|
||||
select SND_PCM
|
||||
---help---
|
||||
This option enables the new driver for the various
|
||||
Apple Onboard Audio components.
|
||||
|
||||
if SND_AOA
|
||||
|
||||
source "sound/aoa/fabrics/Kconfig"
|
||||
|
||||
source "sound/aoa/codecs/Kconfig"
|
||||
|
||||
source "sound/aoa/soundbus/Kconfig"
|
||||
|
||||
endif # SND_AOA
|
||||
4
sound/aoa/Makefile
Normal file
4
sound/aoa/Makefile
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
obj-$(CONFIG_SND_AOA) += core/
|
||||
obj-$(CONFIG_SND_AOA_SOUNDBUS) += soundbus/
|
||||
obj-$(CONFIG_SND_AOA) += fabrics/
|
||||
obj-$(CONFIG_SND_AOA) += codecs/
|
||||
83
sound/aoa/aoa-gpio.h
Normal file
83
sound/aoa/aoa-gpio.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Apple Onboard Audio GPIO definitions
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
|
||||
#ifndef __AOA_GPIO_H
|
||||
#define __AOA_GPIO_H
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <asm/prom.h>
|
||||
|
||||
typedef void (*notify_func_t)(void *data);
|
||||
|
||||
enum notify_type {
|
||||
AOA_NOTIFY_HEADPHONE,
|
||||
AOA_NOTIFY_LINE_IN,
|
||||
AOA_NOTIFY_LINE_OUT,
|
||||
};
|
||||
|
||||
struct gpio_runtime;
|
||||
struct gpio_methods {
|
||||
/* for initialisation/de-initialisation of the GPIO layer */
|
||||
void (*init)(struct gpio_runtime *rt);
|
||||
void (*exit)(struct gpio_runtime *rt);
|
||||
|
||||
/* turn off headphone, speakers, lineout */
|
||||
void (*all_amps_off)(struct gpio_runtime *rt);
|
||||
/* turn headphone, speakers, lineout back to previous setting */
|
||||
void (*all_amps_restore)(struct gpio_runtime *rt);
|
||||
|
||||
void (*set_headphone)(struct gpio_runtime *rt, int on);
|
||||
void (*set_speakers)(struct gpio_runtime *rt, int on);
|
||||
void (*set_lineout)(struct gpio_runtime *rt, int on);
|
||||
void (*set_master)(struct gpio_runtime *rt, int on);
|
||||
|
||||
int (*get_headphone)(struct gpio_runtime *rt);
|
||||
int (*get_speakers)(struct gpio_runtime *rt);
|
||||
int (*get_lineout)(struct gpio_runtime *rt);
|
||||
int (*get_master)(struct gpio_runtime *rt);
|
||||
|
||||
void (*set_hw_reset)(struct gpio_runtime *rt, int on);
|
||||
|
||||
/* use this to be notified of any events. The notification
|
||||
* function is passed the data, and is called in process
|
||||
* context by the use of schedule_work.
|
||||
* The interface for it is that setting a function to NULL
|
||||
* removes it, and they return 0 if the operation succeeded,
|
||||
* and -EBUSY if the notification is already assigned by
|
||||
* someone else. */
|
||||
int (*set_notify)(struct gpio_runtime *rt,
|
||||
enum notify_type type,
|
||||
notify_func_t notify,
|
||||
void *data);
|
||||
/* returns 0 if not plugged in, 1 if plugged in
|
||||
* or a negative error code */
|
||||
int (*get_detect)(struct gpio_runtime *rt,
|
||||
enum notify_type type);
|
||||
};
|
||||
|
||||
struct gpio_notification {
|
||||
struct delayed_work work;
|
||||
notify_func_t notify;
|
||||
void *data;
|
||||
void *gpio_private;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
struct gpio_runtime {
|
||||
/* to be assigned by fabric */
|
||||
struct device_node *node;
|
||||
/* since everyone needs this pointer anyway... */
|
||||
struct gpio_methods *methods;
|
||||
/* to be used by the gpio implementation */
|
||||
int implementation_private;
|
||||
struct gpio_notification headphone_notify;
|
||||
struct gpio_notification line_in_notify;
|
||||
struct gpio_notification line_out_notify;
|
||||
};
|
||||
|
||||
#endif /* __AOA_GPIO_H */
|
||||
129
sound/aoa/aoa.h
Normal file
129
sound/aoa/aoa.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Apple Onboard Audio definitions
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
|
||||
#ifndef __AOA_H
|
||||
#define __AOA_H
|
||||
#include <asm/prom.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/asound.h>
|
||||
#include <sound/control.h>
|
||||
#include "aoa-gpio.h"
|
||||
#include "soundbus/soundbus.h"
|
||||
|
||||
#define MAX_CODEC_NAME_LEN 32
|
||||
|
||||
struct aoa_codec {
|
||||
char name[MAX_CODEC_NAME_LEN];
|
||||
|
||||
struct module *owner;
|
||||
|
||||
/* called when the fabric wants to init this codec.
|
||||
* Do alsa card manipulations from here. */
|
||||
int (*init)(struct aoa_codec *codec);
|
||||
|
||||
/* called when the fabric is done with the codec.
|
||||
* The alsa card will be cleaned up so don't bother. */
|
||||
void (*exit)(struct aoa_codec *codec);
|
||||
|
||||
/* May be NULL, but can be used by the fabric.
|
||||
* Refcounting is the codec driver's responsibility */
|
||||
struct device_node *node;
|
||||
|
||||
/* assigned by fabric before init() is called, points
|
||||
* to the soundbus device. Cannot be NULL. */
|
||||
struct soundbus_dev *soundbus_dev;
|
||||
|
||||
/* assigned by the fabric before init() is called, points
|
||||
* to the fabric's gpio runtime record for the relevant
|
||||
* device. */
|
||||
struct gpio_runtime *gpio;
|
||||
|
||||
/* assigned by the fabric before init() is called, contains
|
||||
* a codec specific bitmask of what outputs and inputs are
|
||||
* actually connected */
|
||||
u32 connected;
|
||||
|
||||
/* data the fabric can associate with this structure */
|
||||
void *fabric_data;
|
||||
|
||||
/* private! */
|
||||
struct list_head list;
|
||||
struct aoa_fabric *fabric;
|
||||
};
|
||||
|
||||
/* return 0 on success */
|
||||
extern int
|
||||
aoa_codec_register(struct aoa_codec *codec);
|
||||
extern void
|
||||
aoa_codec_unregister(struct aoa_codec *codec);
|
||||
|
||||
#define MAX_LAYOUT_NAME_LEN 32
|
||||
|
||||
struct aoa_fabric {
|
||||
char name[MAX_LAYOUT_NAME_LEN];
|
||||
|
||||
struct module *owner;
|
||||
|
||||
/* once codecs register, they are passed here after.
|
||||
* They are of course not initialised, since the
|
||||
* fabric is responsible for initialising some fields
|
||||
* in the codec structure! */
|
||||
int (*found_codec)(struct aoa_codec *codec);
|
||||
/* called for each codec when it is removed,
|
||||
* also in the case that aoa_fabric_unregister
|
||||
* is called and all codecs are removed
|
||||
* from this fabric.
|
||||
* Also called if found_codec returned 0 but
|
||||
* the codec couldn't initialise. */
|
||||
void (*remove_codec)(struct aoa_codec *codec);
|
||||
/* If found_codec returned 0, and the codec
|
||||
* could be initialised, this is called. */
|
||||
void (*attached_codec)(struct aoa_codec *codec);
|
||||
};
|
||||
|
||||
/* return 0 on success, -EEXIST if another fabric is
|
||||
* registered, -EALREADY if the same fabric is registered.
|
||||
* Passing NULL can be used to test for the presence
|
||||
* of another fabric, if -EALREADY is returned there is
|
||||
* no other fabric present.
|
||||
* In the case that the function returns -EALREADY
|
||||
* and the fabric passed is not NULL, all codecs
|
||||
* that are not assigned yet are passed to the fabric
|
||||
* again for reconsideration. */
|
||||
extern int
|
||||
aoa_fabric_register(struct aoa_fabric *fabric, struct device *dev);
|
||||
|
||||
/* it is vital to call this when the fabric exits!
|
||||
* When calling, the remove_codec will be called
|
||||
* for all codecs, unless it is NULL. */
|
||||
extern void
|
||||
aoa_fabric_unregister(struct aoa_fabric *fabric);
|
||||
|
||||
/* if for some reason you want to get rid of a codec
|
||||
* before the fabric is removed, use this.
|
||||
* Note that remove_codec is called for it! */
|
||||
extern void
|
||||
aoa_fabric_unlink_codec(struct aoa_codec *codec);
|
||||
|
||||
/* alsa help methods */
|
||||
struct aoa_card {
|
||||
struct snd_card *alsa_card;
|
||||
};
|
||||
|
||||
extern int aoa_snd_device_new(enum snd_device_type type,
|
||||
void * device_data, struct snd_device_ops * ops);
|
||||
extern struct snd_card *aoa_get_card(void);
|
||||
extern int aoa_snd_ctl_add(struct snd_kcontrol* control);
|
||||
|
||||
/* GPIO stuff */
|
||||
extern struct gpio_methods *pmf_gpio_methods;
|
||||
extern struct gpio_methods *ftr_gpio_methods;
|
||||
/* extern struct gpio_methods *map_gpio_methods; */
|
||||
|
||||
#endif /* __AOA_H */
|
||||
24
sound/aoa/codecs/Kconfig
Normal file
24
sound/aoa/codecs/Kconfig
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
config SND_AOA_ONYX
|
||||
tristate "support Onyx chip"
|
||||
select I2C
|
||||
select I2C_POWERMAC
|
||||
---help---
|
||||
This option enables support for the Onyx (pcm3052)
|
||||
codec chip found in the latest Apple machines
|
||||
(most of those with digital audio output).
|
||||
|
||||
config SND_AOA_TAS
|
||||
tristate "support TAS chips"
|
||||
select I2C
|
||||
select I2C_POWERMAC
|
||||
---help---
|
||||
This option enables support for the tas chips
|
||||
found in a lot of Apple Machines, especially
|
||||
iBooks and PowerBooks without digital.
|
||||
|
||||
config SND_AOA_TOONIE
|
||||
tristate "support Toonie chip"
|
||||
---help---
|
||||
This option enables support for the toonie codec
|
||||
found in the Mac Mini. If you have a Mac Mini and
|
||||
want to hear sound, select this option.
|
||||
7
sound/aoa/codecs/Makefile
Normal file
7
sound/aoa/codecs/Makefile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
snd-aoa-codec-onyx-objs := onyx.o
|
||||
snd-aoa-codec-tas-objs := tas.o
|
||||
snd-aoa-codec-toonie-objs := toonie.o
|
||||
|
||||
obj-$(CONFIG_SND_AOA_ONYX) += snd-aoa-codec-onyx.o
|
||||
obj-$(CONFIG_SND_AOA_TAS) += snd-aoa-codec-tas.o
|
||||
obj-$(CONFIG_SND_AOA_TOONIE) += snd-aoa-codec-toonie.o
|
||||
1066
sound/aoa/codecs/onyx.c
Normal file
1066
sound/aoa/codecs/onyx.c
Normal file
File diff suppressed because it is too large
Load diff
75
sound/aoa/codecs/onyx.h
Normal file
75
sound/aoa/codecs/onyx.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Apple Onboard Audio driver for Onyx codec (header)
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
#ifndef __SND_AOA_CODEC_ONYX_H
|
||||
#define __SND_AOA_CODEC_ONYX_H
|
||||
#include <stddef.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <asm/pmac_low_i2c.h>
|
||||
#include <asm/prom.h>
|
||||
|
||||
/* PCM3052 register definitions */
|
||||
|
||||
/* the attenuation registers take values from
|
||||
* -1 (0dB) to -127 (-63.0 dB) or others (muted) */
|
||||
#define ONYX_REG_DAC_ATTEN_LEFT 65
|
||||
#define FIRSTREGISTER ONYX_REG_DAC_ATTEN_LEFT
|
||||
#define ONYX_REG_DAC_ATTEN_RIGHT 66
|
||||
|
||||
#define ONYX_REG_CONTROL 67
|
||||
# define ONYX_MRST (1<<7)
|
||||
# define ONYX_SRST (1<<6)
|
||||
# define ONYX_ADPSV (1<<5)
|
||||
# define ONYX_DAPSV (1<<4)
|
||||
# define ONYX_SILICONVERSION (1<<0)
|
||||
/* all others reserved */
|
||||
|
||||
#define ONYX_REG_DAC_CONTROL 68
|
||||
# define ONYX_OVR1 (1<<6)
|
||||
# define ONYX_MUTE_RIGHT (1<<1)
|
||||
# define ONYX_MUTE_LEFT (1<<0)
|
||||
|
||||
#define ONYX_REG_DAC_DEEMPH 69
|
||||
# define ONYX_DIGDEEMPH_SHIFT 5
|
||||
# define ONYX_DIGDEEMPH_MASK (3<<ONYX_DIGDEEMPH_SHIFT)
|
||||
# define ONYX_DIGDEEMPH_CTRL (1<<4)
|
||||
|
||||
#define ONYX_REG_DAC_FILTER 70
|
||||
# define ONYX_ROLLOFF_FAST (1<<5)
|
||||
# define ONYX_DAC_FILTER_ALWAYS (1<<2)
|
||||
|
||||
#define ONYX_REG_DAC_OUTPHASE 71
|
||||
# define ONYX_OUTPHASE_INVERTED (1<<0)
|
||||
|
||||
#define ONYX_REG_ADC_CONTROL 72
|
||||
# define ONYX_ADC_INPUT_MIC (1<<5)
|
||||
/* 8 + input gain in dB, valid range for input gain is -4 .. 20 dB */
|
||||
# define ONYX_ADC_PGA_GAIN_MASK 0x1f
|
||||
|
||||
#define ONYX_REG_ADC_HPF_BYPASS 75
|
||||
# define ONYX_HPF_DISABLE (1<<3)
|
||||
# define ONYX_ADC_HPF_ALWAYS (1<<2)
|
||||
|
||||
#define ONYX_REG_DIG_INFO1 77
|
||||
# define ONYX_MASK_DIN_TO_BPZ (1<<7)
|
||||
/* bits 1-5 control channel bits 1-5 */
|
||||
# define ONYX_DIGOUT_DISABLE (1<<0)
|
||||
|
||||
#define ONYX_REG_DIG_INFO2 78
|
||||
/* controls channel bits 8-15 */
|
||||
|
||||
#define ONYX_REG_DIG_INFO3 79
|
||||
/* control channel bits 24-29, high 2 bits reserved */
|
||||
|
||||
#define ONYX_REG_DIG_INFO4 80
|
||||
# define ONYX_VALIDL (1<<7)
|
||||
# define ONYX_VALIDR (1<<6)
|
||||
# define ONYX_SPDIF_ENABLE (1<<5)
|
||||
/* lower 4 bits control bits 32-35 of channel control and word length */
|
||||
# define ONYX_WORDLEN_MASK (0xF)
|
||||
|
||||
#endif /* __SND_AOA_CODEC_ONYX_H */
|
||||
134
sound/aoa/codecs/tas-basstreble.h
Normal file
134
sound/aoa/codecs/tas-basstreble.h
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* This file is only included exactly once!
|
||||
*
|
||||
* The tables here are derived from the tas3004 datasheet,
|
||||
* modulo typo corrections and some smoothing...
|
||||
*/
|
||||
|
||||
#define TAS3004_TREBLE_MIN 0
|
||||
#define TAS3004_TREBLE_MAX 72
|
||||
#define TAS3004_BASS_MIN 0
|
||||
#define TAS3004_BASS_MAX 72
|
||||
#define TAS3004_TREBLE_ZERO 36
|
||||
#define TAS3004_BASS_ZERO 36
|
||||
|
||||
static u8 tas3004_treble_table[] = {
|
||||
150, /* -18 dB */
|
||||
149,
|
||||
148,
|
||||
147,
|
||||
146,
|
||||
145,
|
||||
144,
|
||||
143,
|
||||
142,
|
||||
141,
|
||||
140,
|
||||
139,
|
||||
138,
|
||||
137,
|
||||
136,
|
||||
135,
|
||||
134,
|
||||
133,
|
||||
132,
|
||||
131,
|
||||
130,
|
||||
129,
|
||||
128,
|
||||
127,
|
||||
126,
|
||||
125,
|
||||
124,
|
||||
123,
|
||||
122,
|
||||
121,
|
||||
120,
|
||||
119,
|
||||
118,
|
||||
117,
|
||||
116,
|
||||
115,
|
||||
114, /* 0 dB */
|
||||
113,
|
||||
112,
|
||||
111,
|
||||
109,
|
||||
108,
|
||||
107,
|
||||
105,
|
||||
104,
|
||||
103,
|
||||
101,
|
||||
99,
|
||||
98,
|
||||
96,
|
||||
93,
|
||||
91,
|
||||
89,
|
||||
86,
|
||||
83,
|
||||
81,
|
||||
77,
|
||||
74,
|
||||
71,
|
||||
67,
|
||||
63,
|
||||
59,
|
||||
54,
|
||||
49,
|
||||
44,
|
||||
38,
|
||||
32,
|
||||
26,
|
||||
19,
|
||||
10,
|
||||
4,
|
||||
2,
|
||||
1, /* +18 dB */
|
||||
};
|
||||
|
||||
static inline u8 tas3004_treble(int idx)
|
||||
{
|
||||
return tas3004_treble_table[idx];
|
||||
}
|
||||
|
||||
/* I only save the difference here to the treble table
|
||||
* so that the binary is smaller...
|
||||
* I have also ignored completely differences of
|
||||
* +/- 1
|
||||
*/
|
||||
static s8 tas3004_bass_diff_to_treble[] = {
|
||||
2, /* 7 dB, offset 50 */
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
14,
|
||||
13,
|
||||
8,
|
||||
1, /* 18 dB */
|
||||
};
|
||||
|
||||
static inline u8 tas3004_bass(int idx)
|
||||
{
|
||||
u8 result = tas3004_treble_table[idx];
|
||||
|
||||
if (idx >= 50)
|
||||
result += tas3004_bass_diff_to_treble[idx-50];
|
||||
return result;
|
||||
}
|
||||
209
sound/aoa/codecs/tas-gain-table.h
Normal file
209
sound/aoa/codecs/tas-gain-table.h
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
This is the program used to generate below table.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
int main() {
|
||||
int dB2;
|
||||
printf("/" "* This file is only included exactly once!\n");
|
||||
printf(" *\n");
|
||||
printf(" * If they'd only tell us that generating this table was\n");
|
||||
printf(" * as easy as calculating\n");
|
||||
printf(" * hwvalue = 1048576.0*exp(0.057564628*dB*2)\n");
|
||||
printf(" * :) *" "/\n");
|
||||
printf("static int tas_gaintable[] = {\n");
|
||||
printf(" 0x000000, /" "* -infinity dB *" "/\n");
|
||||
for (dB2=-140;dB2<=36;dB2++)
|
||||
printf(" 0x%.6x, /" "* %-02.1f dB *" "/\n", (int)(1048576.0*exp(0.057564628*dB2)), dB2/2.0);
|
||||
printf("};\n\n");
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
/* This file is only included exactly once!
|
||||
*
|
||||
* If they'd only tell us that generating this table was
|
||||
* as easy as calculating
|
||||
* hwvalue = 1048576.0*exp(0.057564628*dB*2)
|
||||
* :) */
|
||||
static int tas_gaintable[] = {
|
||||
0x000000, /* -infinity dB */
|
||||
0x00014b, /* -70.0 dB */
|
||||
0x00015f, /* -69.5 dB */
|
||||
0x000174, /* -69.0 dB */
|
||||
0x00018a, /* -68.5 dB */
|
||||
0x0001a1, /* -68.0 dB */
|
||||
0x0001ba, /* -67.5 dB */
|
||||
0x0001d4, /* -67.0 dB */
|
||||
0x0001f0, /* -66.5 dB */
|
||||
0x00020d, /* -66.0 dB */
|
||||
0x00022c, /* -65.5 dB */
|
||||
0x00024d, /* -65.0 dB */
|
||||
0x000270, /* -64.5 dB */
|
||||
0x000295, /* -64.0 dB */
|
||||
0x0002bc, /* -63.5 dB */
|
||||
0x0002e6, /* -63.0 dB */
|
||||
0x000312, /* -62.5 dB */
|
||||
0x000340, /* -62.0 dB */
|
||||
0x000372, /* -61.5 dB */
|
||||
0x0003a6, /* -61.0 dB */
|
||||
0x0003dd, /* -60.5 dB */
|
||||
0x000418, /* -60.0 dB */
|
||||
0x000456, /* -59.5 dB */
|
||||
0x000498, /* -59.0 dB */
|
||||
0x0004de, /* -58.5 dB */
|
||||
0x000528, /* -58.0 dB */
|
||||
0x000576, /* -57.5 dB */
|
||||
0x0005c9, /* -57.0 dB */
|
||||
0x000620, /* -56.5 dB */
|
||||
0x00067d, /* -56.0 dB */
|
||||
0x0006e0, /* -55.5 dB */
|
||||
0x000748, /* -55.0 dB */
|
||||
0x0007b7, /* -54.5 dB */
|
||||
0x00082c, /* -54.0 dB */
|
||||
0x0008a8, /* -53.5 dB */
|
||||
0x00092b, /* -53.0 dB */
|
||||
0x0009b6, /* -52.5 dB */
|
||||
0x000a49, /* -52.0 dB */
|
||||
0x000ae5, /* -51.5 dB */
|
||||
0x000b8b, /* -51.0 dB */
|
||||
0x000c3a, /* -50.5 dB */
|
||||
0x000cf3, /* -50.0 dB */
|
||||
0x000db8, /* -49.5 dB */
|
||||
0x000e88, /* -49.0 dB */
|
||||
0x000f64, /* -48.5 dB */
|
||||
0x00104e, /* -48.0 dB */
|
||||
0x001145, /* -47.5 dB */
|
||||
0x00124b, /* -47.0 dB */
|
||||
0x001361, /* -46.5 dB */
|
||||
0x001487, /* -46.0 dB */
|
||||
0x0015be, /* -45.5 dB */
|
||||
0x001708, /* -45.0 dB */
|
||||
0x001865, /* -44.5 dB */
|
||||
0x0019d8, /* -44.0 dB */
|
||||
0x001b60, /* -43.5 dB */
|
||||
0x001cff, /* -43.0 dB */
|
||||
0x001eb7, /* -42.5 dB */
|
||||
0x002089, /* -42.0 dB */
|
||||
0x002276, /* -41.5 dB */
|
||||
0x002481, /* -41.0 dB */
|
||||
0x0026ab, /* -40.5 dB */
|
||||
0x0028f5, /* -40.0 dB */
|
||||
0x002b63, /* -39.5 dB */
|
||||
0x002df5, /* -39.0 dB */
|
||||
0x0030ae, /* -38.5 dB */
|
||||
0x003390, /* -38.0 dB */
|
||||
0x00369e, /* -37.5 dB */
|
||||
0x0039db, /* -37.0 dB */
|
||||
0x003d49, /* -36.5 dB */
|
||||
0x0040ea, /* -36.0 dB */
|
||||
0x0044c3, /* -35.5 dB */
|
||||
0x0048d6, /* -35.0 dB */
|
||||
0x004d27, /* -34.5 dB */
|
||||
0x0051b9, /* -34.0 dB */
|
||||
0x005691, /* -33.5 dB */
|
||||
0x005bb2, /* -33.0 dB */
|
||||
0x006121, /* -32.5 dB */
|
||||
0x0066e3, /* -32.0 dB */
|
||||
0x006cfb, /* -31.5 dB */
|
||||
0x007370, /* -31.0 dB */
|
||||
0x007a48, /* -30.5 dB */
|
||||
0x008186, /* -30.0 dB */
|
||||
0x008933, /* -29.5 dB */
|
||||
0x009154, /* -29.0 dB */
|
||||
0x0099f1, /* -28.5 dB */
|
||||
0x00a310, /* -28.0 dB */
|
||||
0x00acba, /* -27.5 dB */
|
||||
0x00b6f6, /* -27.0 dB */
|
||||
0x00c1cd, /* -26.5 dB */
|
||||
0x00cd49, /* -26.0 dB */
|
||||
0x00d973, /* -25.5 dB */
|
||||
0x00e655, /* -25.0 dB */
|
||||
0x00f3fb, /* -24.5 dB */
|
||||
0x010270, /* -24.0 dB */
|
||||
0x0111c0, /* -23.5 dB */
|
||||
0x0121f9, /* -23.0 dB */
|
||||
0x013328, /* -22.5 dB */
|
||||
0x01455b, /* -22.0 dB */
|
||||
0x0158a2, /* -21.5 dB */
|
||||
0x016d0e, /* -21.0 dB */
|
||||
0x0182af, /* -20.5 dB */
|
||||
0x019999, /* -20.0 dB */
|
||||
0x01b1de, /* -19.5 dB */
|
||||
0x01cb94, /* -19.0 dB */
|
||||
0x01e6cf, /* -18.5 dB */
|
||||
0x0203a7, /* -18.0 dB */
|
||||
0x022235, /* -17.5 dB */
|
||||
0x024293, /* -17.0 dB */
|
||||
0x0264db, /* -16.5 dB */
|
||||
0x02892c, /* -16.0 dB */
|
||||
0x02afa3, /* -15.5 dB */
|
||||
0x02d862, /* -15.0 dB */
|
||||
0x03038a, /* -14.5 dB */
|
||||
0x033142, /* -14.0 dB */
|
||||
0x0361af, /* -13.5 dB */
|
||||
0x0394fa, /* -13.0 dB */
|
||||
0x03cb50, /* -12.5 dB */
|
||||
0x0404de, /* -12.0 dB */
|
||||
0x0441d5, /* -11.5 dB */
|
||||
0x048268, /* -11.0 dB */
|
||||
0x04c6d0, /* -10.5 dB */
|
||||
0x050f44, /* -10.0 dB */
|
||||
0x055c04, /* -9.5 dB */
|
||||
0x05ad50, /* -9.0 dB */
|
||||
0x06036e, /* -8.5 dB */
|
||||
0x065ea5, /* -8.0 dB */
|
||||
0x06bf44, /* -7.5 dB */
|
||||
0x07259d, /* -7.0 dB */
|
||||
0x079207, /* -6.5 dB */
|
||||
0x0804dc, /* -6.0 dB */
|
||||
0x087e80, /* -5.5 dB */
|
||||
0x08ff59, /* -5.0 dB */
|
||||
0x0987d5, /* -4.5 dB */
|
||||
0x0a1866, /* -4.0 dB */
|
||||
0x0ab189, /* -3.5 dB */
|
||||
0x0b53be, /* -3.0 dB */
|
||||
0x0bff91, /* -2.5 dB */
|
||||
0x0cb591, /* -2.0 dB */
|
||||
0x0d765a, /* -1.5 dB */
|
||||
0x0e4290, /* -1.0 dB */
|
||||
0x0f1adf, /* -0.5 dB */
|
||||
0x100000, /* 0.0 dB */
|
||||
0x10f2b4, /* 0.5 dB */
|
||||
0x11f3c9, /* 1.0 dB */
|
||||
0x13041a, /* 1.5 dB */
|
||||
0x14248e, /* 2.0 dB */
|
||||
0x15561a, /* 2.5 dB */
|
||||
0x1699c0, /* 3.0 dB */
|
||||
0x17f094, /* 3.5 dB */
|
||||
0x195bb8, /* 4.0 dB */
|
||||
0x1adc61, /* 4.5 dB */
|
||||
0x1c73d5, /* 5.0 dB */
|
||||
0x1e236d, /* 5.5 dB */
|
||||
0x1fec98, /* 6.0 dB */
|
||||
0x21d0d9, /* 6.5 dB */
|
||||
0x23d1cd, /* 7.0 dB */
|
||||
0x25f125, /* 7.5 dB */
|
||||
0x2830af, /* 8.0 dB */
|
||||
0x2a9254, /* 8.5 dB */
|
||||
0x2d1818, /* 9.0 dB */
|
||||
0x2fc420, /* 9.5 dB */
|
||||
0x3298b0, /* 10.0 dB */
|
||||
0x35982f, /* 10.5 dB */
|
||||
0x38c528, /* 11.0 dB */
|
||||
0x3c224c, /* 11.5 dB */
|
||||
0x3fb278, /* 12.0 dB */
|
||||
0x4378b0, /* 12.5 dB */
|
||||
0x477829, /* 13.0 dB */
|
||||
0x4bb446, /* 13.5 dB */
|
||||
0x5030a1, /* 14.0 dB */
|
||||
0x54f106, /* 14.5 dB */
|
||||
0x59f980, /* 15.0 dB */
|
||||
0x5f4e52, /* 15.5 dB */
|
||||
0x64f403, /* 16.0 dB */
|
||||
0x6aef5e, /* 16.5 dB */
|
||||
0x714575, /* 17.0 dB */
|
||||
0x77fbaa, /* 17.5 dB */
|
||||
0x7f17af, /* 18.0 dB */
|
||||
};
|
||||
|
||||
955
sound/aoa/codecs/tas.c
Normal file
955
sound/aoa/codecs/tas.c
Normal file
|
|
@ -0,0 +1,955 @@
|
|||
/*
|
||||
* Apple Onboard Audio driver for tas codec
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*
|
||||
* Open questions:
|
||||
* - How to distinguish between 3004 and versions?
|
||||
*
|
||||
* FIXMEs:
|
||||
* - This codec driver doesn't honour the 'connected'
|
||||
* property of the aoa_codec struct, hence if
|
||||
* it is used in machines where not everything is
|
||||
* connected it will display wrong mixer elements.
|
||||
* - Driver assumes that the microphone is always
|
||||
* monaureal and connected to the right channel of
|
||||
* the input. This should also be a codec-dependent
|
||||
* flag, maybe the codec should have 3 different
|
||||
* bits for the three different possibilities how
|
||||
* it can be hooked up...
|
||||
* But as long as I don't see any hardware hooked
|
||||
* up that way...
|
||||
* - As Apple notes in their code, the tas3004 seems
|
||||
* to delay the right channel by one sample. You can
|
||||
* see this when for example recording stereo in
|
||||
* audacity, or recording the tas output via cable
|
||||
* on another machine (use a sinus generator or so).
|
||||
* I tried programming the BiQuads but couldn't
|
||||
* make the delay work, maybe someone can read the
|
||||
* datasheet and fix it. The relevant Apple comment
|
||||
* is in AppleTAS3004Audio.cpp lines 1637 ff. Note
|
||||
* that their comment describing how they program
|
||||
* the filters sucks...
|
||||
*
|
||||
* Other things:
|
||||
* - this should actually register *two* aoa_codec
|
||||
* structs since it has two inputs. Then it must
|
||||
* use the prepare callback to forbid running the
|
||||
* secondary output on a different clock.
|
||||
* Also, whatever bus knows how to do this must
|
||||
* provide two soundbus_dev devices and the fabric
|
||||
* must be able to link them correctly.
|
||||
*
|
||||
* I don't even know if Apple ever uses the second
|
||||
* port on the tas3004 though, I don't think their
|
||||
* i2s controllers can even do it. OTOH, they all
|
||||
* derive the clocks from common clocks, so it
|
||||
* might just be possible. The framework allows the
|
||||
* codec to refine the transfer_info items in the
|
||||
* usable callback, so we can simply remove the
|
||||
* rates the second instance is not using when it
|
||||
* actually is in use.
|
||||
* Maybe we'll need to make the sound busses have
|
||||
* a 'clock group id' value so the codec can
|
||||
* determine if the two outputs can be driven at
|
||||
* the same time. But that is likely overkill, up
|
||||
* to the fabric to not link them up incorrectly,
|
||||
* and up to the hardware designer to not wire
|
||||
* them up in some weird unusable way.
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <asm/pmac_low_i2c.h>
|
||||
#include <asm/prom.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("tas codec driver for snd-aoa");
|
||||
|
||||
#include "tas.h"
|
||||
#include "tas-gain-table.h"
|
||||
#include "tas-basstreble.h"
|
||||
#include "../aoa.h"
|
||||
#include "../soundbus/soundbus.h"
|
||||
|
||||
#define PFX "snd-aoa-codec-tas: "
|
||||
|
||||
|
||||
struct tas {
|
||||
struct aoa_codec codec;
|
||||
struct i2c_client *i2c;
|
||||
u32 mute_l:1, mute_r:1 ,
|
||||
controls_created:1 ,
|
||||
drc_enabled:1,
|
||||
hw_enabled:1;
|
||||
u8 cached_volume_l, cached_volume_r;
|
||||
u8 mixer_l[3], mixer_r[3];
|
||||
u8 bass, treble;
|
||||
u8 acr;
|
||||
int drc_range;
|
||||
/* protects hardware access against concurrency from
|
||||
* userspace when hitting controls and during
|
||||
* codec init/suspend/resume */
|
||||
struct mutex mtx;
|
||||
};
|
||||
|
||||
static int tas_reset_init(struct tas *tas);
|
||||
|
||||
static struct tas *codec_to_tas(struct aoa_codec *codec)
|
||||
{
|
||||
return container_of(codec, struct tas, codec);
|
||||
}
|
||||
|
||||
static inline int tas_write_reg(struct tas *tas, u8 reg, u8 len, u8 *data)
|
||||
{
|
||||
if (len == 1)
|
||||
return i2c_smbus_write_byte_data(tas->i2c, reg, *data);
|
||||
else
|
||||
return i2c_smbus_write_i2c_block_data(tas->i2c, reg, len, data);
|
||||
}
|
||||
|
||||
static void tas3004_set_drc(struct tas *tas)
|
||||
{
|
||||
unsigned char val[6];
|
||||
|
||||
if (tas->drc_enabled)
|
||||
val[0] = 0x50; /* 3:1 above threshold */
|
||||
else
|
||||
val[0] = 0x51; /* disabled */
|
||||
val[1] = 0x02; /* 1:1 below threshold */
|
||||
if (tas->drc_range > 0xef)
|
||||
val[2] = 0xef;
|
||||
else if (tas->drc_range < 0)
|
||||
val[2] = 0x00;
|
||||
else
|
||||
val[2] = tas->drc_range;
|
||||
val[3] = 0xb0;
|
||||
val[4] = 0x60;
|
||||
val[5] = 0xa0;
|
||||
|
||||
tas_write_reg(tas, TAS_REG_DRC, 6, val);
|
||||
}
|
||||
|
||||
static void tas_set_treble(struct tas *tas)
|
||||
{
|
||||
u8 tmp;
|
||||
|
||||
tmp = tas3004_treble(tas->treble);
|
||||
tas_write_reg(tas, TAS_REG_TREBLE, 1, &tmp);
|
||||
}
|
||||
|
||||
static void tas_set_bass(struct tas *tas)
|
||||
{
|
||||
u8 tmp;
|
||||
|
||||
tmp = tas3004_bass(tas->bass);
|
||||
tas_write_reg(tas, TAS_REG_BASS, 1, &tmp);
|
||||
}
|
||||
|
||||
static void tas_set_volume(struct tas *tas)
|
||||
{
|
||||
u8 block[6];
|
||||
int tmp;
|
||||
u8 left, right;
|
||||
|
||||
left = tas->cached_volume_l;
|
||||
right = tas->cached_volume_r;
|
||||
|
||||
if (left > 177) left = 177;
|
||||
if (right > 177) right = 177;
|
||||
|
||||
if (tas->mute_l) left = 0;
|
||||
if (tas->mute_r) right = 0;
|
||||
|
||||
/* analysing the volume and mixer tables shows
|
||||
* that they are similar enough when we shift
|
||||
* the mixer table down by 4 bits. The error
|
||||
* is miniscule, in just one item the error
|
||||
* is 1, at a value of 0x07f17b (mixer table
|
||||
* value is 0x07f17a) */
|
||||
tmp = tas_gaintable[left];
|
||||
block[0] = tmp>>20;
|
||||
block[1] = tmp>>12;
|
||||
block[2] = tmp>>4;
|
||||
tmp = tas_gaintable[right];
|
||||
block[3] = tmp>>20;
|
||||
block[4] = tmp>>12;
|
||||
block[5] = tmp>>4;
|
||||
tas_write_reg(tas, TAS_REG_VOL, 6, block);
|
||||
}
|
||||
|
||||
static void tas_set_mixer(struct tas *tas)
|
||||
{
|
||||
u8 block[9];
|
||||
int tmp, i;
|
||||
u8 val;
|
||||
|
||||
for (i=0;i<3;i++) {
|
||||
val = tas->mixer_l[i];
|
||||
if (val > 177) val = 177;
|
||||
tmp = tas_gaintable[val];
|
||||
block[3*i+0] = tmp>>16;
|
||||
block[3*i+1] = tmp>>8;
|
||||
block[3*i+2] = tmp;
|
||||
}
|
||||
tas_write_reg(tas, TAS_REG_LMIX, 9, block);
|
||||
|
||||
for (i=0;i<3;i++) {
|
||||
val = tas->mixer_r[i];
|
||||
if (val > 177) val = 177;
|
||||
tmp = tas_gaintable[val];
|
||||
block[3*i+0] = tmp>>16;
|
||||
block[3*i+1] = tmp>>8;
|
||||
block[3*i+2] = tmp;
|
||||
}
|
||||
tas_write_reg(tas, TAS_REG_RMIX, 9, block);
|
||||
}
|
||||
|
||||
/* alsa stuff */
|
||||
|
||||
static int tas_dev_register(struct snd_device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_device_ops ops = {
|
||||
.dev_register = tas_dev_register,
|
||||
};
|
||||
|
||||
static int tas_snd_vol_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 2;
|
||||
uinfo->value.integer.min = 0;
|
||||
uinfo->value.integer.max = 177;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_vol_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
ucontrol->value.integer.value[0] = tas->cached_volume_l;
|
||||
ucontrol->value.integer.value[1] = tas->cached_volume_r;
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_vol_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (ucontrol->value.integer.value[0] < 0 ||
|
||||
ucontrol->value.integer.value[0] > 177)
|
||||
return -EINVAL;
|
||||
if (ucontrol->value.integer.value[1] < 0 ||
|
||||
ucontrol->value.integer.value[1] > 177)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
if (tas->cached_volume_l == ucontrol->value.integer.value[0]
|
||||
&& tas->cached_volume_r == ucontrol->value.integer.value[1]) {
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tas->cached_volume_l = ucontrol->value.integer.value[0];
|
||||
tas->cached_volume_r = ucontrol->value.integer.value[1];
|
||||
if (tas->hw_enabled)
|
||||
tas_set_volume(tas);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new volume_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Master Playback Volume",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.info = tas_snd_vol_info,
|
||||
.get = tas_snd_vol_get,
|
||||
.put = tas_snd_vol_put,
|
||||
};
|
||||
|
||||
#define tas_snd_mute_info snd_ctl_boolean_stereo_info
|
||||
|
||||
static int tas_snd_mute_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
ucontrol->value.integer.value[0] = !tas->mute_l;
|
||||
ucontrol->value.integer.value[1] = !tas->mute_r;
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_mute_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
if (tas->mute_l == !ucontrol->value.integer.value[0]
|
||||
&& tas->mute_r == !ucontrol->value.integer.value[1]) {
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tas->mute_l = !ucontrol->value.integer.value[0];
|
||||
tas->mute_r = !ucontrol->value.integer.value[1];
|
||||
if (tas->hw_enabled)
|
||||
tas_set_volume(tas);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new mute_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Master Playback Switch",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.info = tas_snd_mute_info,
|
||||
.get = tas_snd_mute_get,
|
||||
.put = tas_snd_mute_put,
|
||||
};
|
||||
|
||||
static int tas_snd_mixer_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 2;
|
||||
uinfo->value.integer.min = 0;
|
||||
uinfo->value.integer.max = 177;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_mixer_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
int idx = kcontrol->private_value;
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
ucontrol->value.integer.value[0] = tas->mixer_l[idx];
|
||||
ucontrol->value.integer.value[1] = tas->mixer_r[idx];
|
||||
mutex_unlock(&tas->mtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_mixer_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
int idx = kcontrol->private_value;
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
if (tas->mixer_l[idx] == ucontrol->value.integer.value[0]
|
||||
&& tas->mixer_r[idx] == ucontrol->value.integer.value[1]) {
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tas->mixer_l[idx] = ucontrol->value.integer.value[0];
|
||||
tas->mixer_r[idx] = ucontrol->value.integer.value[1];
|
||||
|
||||
if (tas->hw_enabled)
|
||||
tas_set_mixer(tas);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define MIXER_CONTROL(n,descr,idx) \
|
||||
static struct snd_kcontrol_new n##_control = { \
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
|
||||
.name = descr " Playback Volume", \
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
|
||||
.info = tas_snd_mixer_info, \
|
||||
.get = tas_snd_mixer_get, \
|
||||
.put = tas_snd_mixer_put, \
|
||||
.private_value = idx, \
|
||||
}
|
||||
|
||||
MIXER_CONTROL(pcm1, "PCM", 0);
|
||||
MIXER_CONTROL(monitor, "Monitor", 2);
|
||||
|
||||
static int tas_snd_drc_range_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.integer.min = 0;
|
||||
uinfo->value.integer.max = TAS3004_DRC_MAX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_drc_range_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
ucontrol->value.integer.value[0] = tas->drc_range;
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_drc_range_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (ucontrol->value.integer.value[0] < 0 ||
|
||||
ucontrol->value.integer.value[0] > TAS3004_DRC_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
if (tas->drc_range == ucontrol->value.integer.value[0]) {
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tas->drc_range = ucontrol->value.integer.value[0];
|
||||
if (tas->hw_enabled)
|
||||
tas3004_set_drc(tas);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new drc_range_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "DRC Range",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.info = tas_snd_drc_range_info,
|
||||
.get = tas_snd_drc_range_get,
|
||||
.put = tas_snd_drc_range_put,
|
||||
};
|
||||
|
||||
#define tas_snd_drc_switch_info snd_ctl_boolean_mono_info
|
||||
|
||||
static int tas_snd_drc_switch_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
ucontrol->value.integer.value[0] = tas->drc_enabled;
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_drc_switch_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
if (tas->drc_enabled == ucontrol->value.integer.value[0]) {
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tas->drc_enabled = !!ucontrol->value.integer.value[0];
|
||||
if (tas->hw_enabled)
|
||||
tas3004_set_drc(tas);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new drc_switch_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "DRC Range Switch",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.info = tas_snd_drc_switch_info,
|
||||
.get = tas_snd_drc_switch_get,
|
||||
.put = tas_snd_drc_switch_put,
|
||||
};
|
||||
|
||||
static int tas_snd_capture_source_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static char *texts[] = { "Line-In", "Microphone" };
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 2;
|
||||
if (uinfo->value.enumerated.item > 1)
|
||||
uinfo->value.enumerated.item = 1;
|
||||
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_capture_source_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
ucontrol->value.enumerated.item[0] = !!(tas->acr & TAS_ACR_INPUT_B);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_capture_source_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
int oldacr;
|
||||
|
||||
if (ucontrol->value.enumerated.item[0] > 1)
|
||||
return -EINVAL;
|
||||
mutex_lock(&tas->mtx);
|
||||
oldacr = tas->acr;
|
||||
|
||||
/*
|
||||
* Despite what the data sheet says in one place, the
|
||||
* TAS_ACR_B_MONAUREAL bit forces mono output even when
|
||||
* input A (line in) is selected.
|
||||
*/
|
||||
tas->acr &= ~(TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL);
|
||||
if (ucontrol->value.enumerated.item[0])
|
||||
tas->acr |= TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL |
|
||||
TAS_ACR_B_MON_SEL_RIGHT;
|
||||
if (oldacr == tas->acr) {
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
if (tas->hw_enabled)
|
||||
tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new capture_source_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
/* If we name this 'Input Source', it properly shows up in
|
||||
* alsamixer as a selection, * but it's shown under the
|
||||
* 'Playback' category.
|
||||
* If I name it 'Capture Source', it shows up in strange
|
||||
* ways (two bools of which one can be selected at a
|
||||
* time) but at least it's shown in the 'Capture'
|
||||
* category.
|
||||
* I was told that this was due to backward compatibility,
|
||||
* but I don't understand then why the mangling is *not*
|
||||
* done when I name it "Input Source".....
|
||||
*/
|
||||
.name = "Capture Source",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.info = tas_snd_capture_source_info,
|
||||
.get = tas_snd_capture_source_get,
|
||||
.put = tas_snd_capture_source_put,
|
||||
};
|
||||
|
||||
static int tas_snd_treble_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.integer.min = TAS3004_TREBLE_MIN;
|
||||
uinfo->value.integer.max = TAS3004_TREBLE_MAX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_treble_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
ucontrol->value.integer.value[0] = tas->treble;
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_treble_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (ucontrol->value.integer.value[0] < TAS3004_TREBLE_MIN ||
|
||||
ucontrol->value.integer.value[0] > TAS3004_TREBLE_MAX)
|
||||
return -EINVAL;
|
||||
mutex_lock(&tas->mtx);
|
||||
if (tas->treble == ucontrol->value.integer.value[0]) {
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tas->treble = ucontrol->value.integer.value[0];
|
||||
if (tas->hw_enabled)
|
||||
tas_set_treble(tas);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new treble_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Treble",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.info = tas_snd_treble_info,
|
||||
.get = tas_snd_treble_get,
|
||||
.put = tas_snd_treble_put,
|
||||
};
|
||||
|
||||
static int tas_snd_bass_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.integer.min = TAS3004_BASS_MIN;
|
||||
uinfo->value.integer.max = TAS3004_BASS_MAX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_bass_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
ucontrol->value.integer.value[0] = tas->bass;
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_snd_bass_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct tas *tas = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (ucontrol->value.integer.value[0] < TAS3004_BASS_MIN ||
|
||||
ucontrol->value.integer.value[0] > TAS3004_BASS_MAX)
|
||||
return -EINVAL;
|
||||
mutex_lock(&tas->mtx);
|
||||
if (tas->bass == ucontrol->value.integer.value[0]) {
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tas->bass = ucontrol->value.integer.value[0];
|
||||
if (tas->hw_enabled)
|
||||
tas_set_bass(tas);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new bass_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Bass",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.info = tas_snd_bass_info,
|
||||
.get = tas_snd_bass_get,
|
||||
.put = tas_snd_bass_put,
|
||||
};
|
||||
|
||||
static struct transfer_info tas_transfers[] = {
|
||||
{
|
||||
/* input */
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE,
|
||||
.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
|
||||
.transfer_in = 1,
|
||||
},
|
||||
{
|
||||
/* output */
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE,
|
||||
.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
|
||||
.transfer_in = 0,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static int tas_usable(struct codec_info_item *cii,
|
||||
struct transfer_info *ti,
|
||||
struct transfer_info *out)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int tas_reset_init(struct tas *tas)
|
||||
{
|
||||
u8 tmp;
|
||||
|
||||
tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
|
||||
msleep(5);
|
||||
tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
|
||||
msleep(5);
|
||||
tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 1);
|
||||
msleep(20);
|
||||
tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
|
||||
msleep(10);
|
||||
tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
|
||||
|
||||
tmp = TAS_MCS_SCLK64 | TAS_MCS_SPORT_MODE_I2S | TAS_MCS_SPORT_WL_24BIT;
|
||||
if (tas_write_reg(tas, TAS_REG_MCS, 1, &tmp))
|
||||
goto outerr;
|
||||
|
||||
tas->acr |= TAS_ACR_ANALOG_PDOWN;
|
||||
if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
|
||||
goto outerr;
|
||||
|
||||
tmp = 0;
|
||||
if (tas_write_reg(tas, TAS_REG_MCS2, 1, &tmp))
|
||||
goto outerr;
|
||||
|
||||
tas3004_set_drc(tas);
|
||||
|
||||
/* Set treble & bass to 0dB */
|
||||
tas->treble = TAS3004_TREBLE_ZERO;
|
||||
tas->bass = TAS3004_BASS_ZERO;
|
||||
tas_set_treble(tas);
|
||||
tas_set_bass(tas);
|
||||
|
||||
tas->acr &= ~TAS_ACR_ANALOG_PDOWN;
|
||||
if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
|
||||
goto outerr;
|
||||
|
||||
return 0;
|
||||
outerr:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int tas_switch_clock(struct codec_info_item *cii, enum clock_switch clock)
|
||||
{
|
||||
struct tas *tas = cii->codec_data;
|
||||
|
||||
switch(clock) {
|
||||
case CLOCK_SWITCH_PREPARE_SLAVE:
|
||||
/* Clocks are going away, mute mute mute */
|
||||
tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
|
||||
tas->hw_enabled = 0;
|
||||
break;
|
||||
case CLOCK_SWITCH_SLAVE:
|
||||
/* Clocks are back, re-init the codec */
|
||||
mutex_lock(&tas->mtx);
|
||||
tas_reset_init(tas);
|
||||
tas_set_volume(tas);
|
||||
tas_set_mixer(tas);
|
||||
tas->hw_enabled = 1;
|
||||
tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
|
||||
mutex_unlock(&tas->mtx);
|
||||
break;
|
||||
default:
|
||||
/* doesn't happen as of now */
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
/* we are controlled via i2c and assume that is always up
|
||||
* If that wasn't the case, we'd have to suspend once
|
||||
* our i2c device is suspended, and then take note of that! */
|
||||
static int tas_suspend(struct tas *tas)
|
||||
{
|
||||
mutex_lock(&tas->mtx);
|
||||
tas->hw_enabled = 0;
|
||||
tas->acr |= TAS_ACR_ANALOG_PDOWN;
|
||||
tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tas_resume(struct tas *tas)
|
||||
{
|
||||
/* reset codec */
|
||||
mutex_lock(&tas->mtx);
|
||||
tas_reset_init(tas);
|
||||
tas_set_volume(tas);
|
||||
tas_set_mixer(tas);
|
||||
tas->hw_enabled = 1;
|
||||
mutex_unlock(&tas->mtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _tas_suspend(struct codec_info_item *cii, pm_message_t state)
|
||||
{
|
||||
return tas_suspend(cii->codec_data);
|
||||
}
|
||||
|
||||
static int _tas_resume(struct codec_info_item *cii)
|
||||
{
|
||||
return tas_resume(cii->codec_data);
|
||||
}
|
||||
#else /* CONFIG_PM */
|
||||
#define _tas_suspend NULL
|
||||
#define _tas_resume NULL
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static struct codec_info tas_codec_info = {
|
||||
.transfers = tas_transfers,
|
||||
/* in theory, we can drive it at 512 too...
|
||||
* but so far the framework doesn't allow
|
||||
* for that and I don't see much point in it. */
|
||||
.sysclock_factor = 256,
|
||||
/* same here, could be 32 for just one 16 bit format */
|
||||
.bus_factor = 64,
|
||||
.owner = THIS_MODULE,
|
||||
.usable = tas_usable,
|
||||
.switch_clock = tas_switch_clock,
|
||||
.suspend = _tas_suspend,
|
||||
.resume = _tas_resume,
|
||||
};
|
||||
|
||||
static int tas_init_codec(struct aoa_codec *codec)
|
||||
{
|
||||
struct tas *tas = codec_to_tas(codec);
|
||||
int err;
|
||||
|
||||
if (!tas->codec.gpio || !tas->codec.gpio->methods) {
|
||||
printk(KERN_ERR PFX "gpios not assigned!!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&tas->mtx);
|
||||
if (tas_reset_init(tas)) {
|
||||
printk(KERN_ERR PFX "tas failed to initialise\n");
|
||||
mutex_unlock(&tas->mtx);
|
||||
return -ENXIO;
|
||||
}
|
||||
tas->hw_enabled = 1;
|
||||
mutex_unlock(&tas->mtx);
|
||||
|
||||
if (tas->codec.soundbus_dev->attach_codec(tas->codec.soundbus_dev,
|
||||
aoa_get_card(),
|
||||
&tas_codec_info, tas)) {
|
||||
printk(KERN_ERR PFX "error attaching tas to soundbus\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (aoa_snd_device_new(SNDRV_DEV_CODEC, tas, &ops)) {
|
||||
printk(KERN_ERR PFX "failed to create tas snd device!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
err = aoa_snd_ctl_add(snd_ctl_new1(&volume_control, tas));
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
err = aoa_snd_ctl_add(snd_ctl_new1(&mute_control, tas));
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
err = aoa_snd_ctl_add(snd_ctl_new1(&pcm1_control, tas));
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
err = aoa_snd_ctl_add(snd_ctl_new1(&monitor_control, tas));
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
err = aoa_snd_ctl_add(snd_ctl_new1(&capture_source_control, tas));
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
err = aoa_snd_ctl_add(snd_ctl_new1(&drc_range_control, tas));
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
err = aoa_snd_ctl_add(snd_ctl_new1(&drc_switch_control, tas));
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
err = aoa_snd_ctl_add(snd_ctl_new1(&treble_control, tas));
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
err = aoa_snd_ctl_add(snd_ctl_new1(&bass_control, tas));
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
error:
|
||||
tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas);
|
||||
snd_device_free(aoa_get_card(), tas);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void tas_exit_codec(struct aoa_codec *codec)
|
||||
{
|
||||
struct tas *tas = codec_to_tas(codec);
|
||||
|
||||
if (!tas->codec.soundbus_dev)
|
||||
return;
|
||||
tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas);
|
||||
}
|
||||
|
||||
|
||||
static int tas_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device_node *node = client->dev.of_node;
|
||||
struct tas *tas;
|
||||
|
||||
tas = kzalloc(sizeof(struct tas), GFP_KERNEL);
|
||||
|
||||
if (!tas)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&tas->mtx);
|
||||
tas->i2c = client;
|
||||
i2c_set_clientdata(client, tas);
|
||||
|
||||
/* seems that half is a saner default */
|
||||
tas->drc_range = TAS3004_DRC_MAX / 2;
|
||||
|
||||
strlcpy(tas->codec.name, "tas", MAX_CODEC_NAME_LEN);
|
||||
tas->codec.owner = THIS_MODULE;
|
||||
tas->codec.init = tas_init_codec;
|
||||
tas->codec.exit = tas_exit_codec;
|
||||
tas->codec.node = of_node_get(node);
|
||||
|
||||
if (aoa_codec_register(&tas->codec)) {
|
||||
goto fail;
|
||||
}
|
||||
printk(KERN_DEBUG
|
||||
"snd-aoa-codec-tas: tas found, addr 0x%02x on %s\n",
|
||||
(unsigned int)client->addr, node->full_name);
|
||||
return 0;
|
||||
fail:
|
||||
mutex_destroy(&tas->mtx);
|
||||
kfree(tas);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int tas_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct tas *tas = i2c_get_clientdata(client);
|
||||
u8 tmp = TAS_ACR_ANALOG_PDOWN;
|
||||
|
||||
aoa_codec_unregister(&tas->codec);
|
||||
of_node_put(tas->codec.node);
|
||||
|
||||
/* power down codec chip */
|
||||
tas_write_reg(tas, TAS_REG_ACR, 1, &tmp);
|
||||
|
||||
mutex_destroy(&tas->mtx);
|
||||
kfree(tas);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id tas_i2c_id[] = {
|
||||
{ "MAC,tas3004", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c,tas_i2c_id);
|
||||
|
||||
static struct i2c_driver tas_driver = {
|
||||
.driver = {
|
||||
.name = "aoa_codec_tas",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = tas_i2c_probe,
|
||||
.remove = tas_i2c_remove,
|
||||
.id_table = tas_i2c_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(tas_driver);
|
||||
55
sound/aoa/codecs/tas.h
Normal file
55
sound/aoa/codecs/tas.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Apple Onboard Audio driver for tas codec (header)
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
#ifndef __SND_AOA_CODECTASH
|
||||
#define __SND_AOA_CODECTASH
|
||||
|
||||
#define TAS_REG_MCS 0x01 /* main control */
|
||||
# define TAS_MCS_FASTLOAD (1<<7)
|
||||
# define TAS_MCS_SCLK64 (1<<6)
|
||||
# define TAS_MCS_SPORT_MODE_MASK (3<<4)
|
||||
# define TAS_MCS_SPORT_MODE_I2S (2<<4)
|
||||
# define TAS_MCS_SPORT_MODE_RJ (1<<4)
|
||||
# define TAS_MCS_SPORT_MODE_LJ (0<<4)
|
||||
# define TAS_MCS_SPORT_WL_MASK (3<<0)
|
||||
# define TAS_MCS_SPORT_WL_16BIT (0<<0)
|
||||
# define TAS_MCS_SPORT_WL_18BIT (1<<0)
|
||||
# define TAS_MCS_SPORT_WL_20BIT (2<<0)
|
||||
# define TAS_MCS_SPORT_WL_24BIT (3<<0)
|
||||
|
||||
#define TAS_REG_DRC 0x02
|
||||
#define TAS_REG_VOL 0x04
|
||||
#define TAS_REG_TREBLE 0x05
|
||||
#define TAS_REG_BASS 0x06
|
||||
#define TAS_REG_LMIX 0x07
|
||||
#define TAS_REG_RMIX 0x08
|
||||
|
||||
#define TAS_REG_ACR 0x40 /* analog control */
|
||||
# define TAS_ACR_B_MONAUREAL (1<<7)
|
||||
# define TAS_ACR_B_MON_SEL_RIGHT (1<<6)
|
||||
# define TAS_ACR_DEEMPH_MASK (3<<2)
|
||||
# define TAS_ACR_DEEMPH_OFF (0<<2)
|
||||
# define TAS_ACR_DEEMPH_48KHz (1<<2)
|
||||
# define TAS_ACR_DEEMPH_44KHz (2<<2)
|
||||
# define TAS_ACR_INPUT_B (1<<1)
|
||||
# define TAS_ACR_ANALOG_PDOWN (1<<0)
|
||||
|
||||
#define TAS_REG_MCS2 0x43 /* main control 2 */
|
||||
# define TAS_MCS2_ALLPASS (1<<1)
|
||||
|
||||
#define TAS_REG_LEFT_BIQUAD6 0x10
|
||||
#define TAS_REG_RIGHT_BIQUAD6 0x19
|
||||
|
||||
#define TAS_REG_LEFT_LOUDNESS 0x21
|
||||
#define TAS_REG_RIGHT_LOUDNESS 0x22
|
||||
#define TAS_REG_LEFT_LOUDNESS_GAIN 0x23
|
||||
#define TAS_REG_RIGHT_LOUDNESS_GAIN 0x24
|
||||
|
||||
#define TAS3001_DRC_MAX 0x5f
|
||||
#define TAS3004_DRC_MAX 0xef
|
||||
|
||||
#endif /* __SND_AOA_CODECTASH */
|
||||
151
sound/aoa/codecs/toonie.c
Normal file
151
sound/aoa/codecs/toonie.c
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Apple Onboard Audio driver for Toonie codec
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*
|
||||
*
|
||||
* This is a driver for the toonie codec chip. This chip is present
|
||||
* on the Mac Mini and is nothing but a DAC.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("toonie codec driver for snd-aoa");
|
||||
|
||||
#include "../aoa.h"
|
||||
#include "../soundbus/soundbus.h"
|
||||
|
||||
|
||||
#define PFX "snd-aoa-codec-toonie: "
|
||||
|
||||
struct toonie {
|
||||
struct aoa_codec codec;
|
||||
};
|
||||
#define codec_to_toonie(c) container_of(c, struct toonie, codec)
|
||||
|
||||
static int toonie_dev_register(struct snd_device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_device_ops ops = {
|
||||
.dev_register = toonie_dev_register,
|
||||
};
|
||||
|
||||
static struct transfer_info toonie_transfers[] = {
|
||||
/* This thing *only* has analog output,
|
||||
* the rates are taken from Info.plist
|
||||
* from Darwin. */
|
||||
{
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_BE |
|
||||
SNDRV_PCM_FMTBIT_S24_BE,
|
||||
.rates = SNDRV_PCM_RATE_32000 |
|
||||
SNDRV_PCM_RATE_44100 |
|
||||
SNDRV_PCM_RATE_48000 |
|
||||
SNDRV_PCM_RATE_88200 |
|
||||
SNDRV_PCM_RATE_96000,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static int toonie_usable(struct codec_info_item *cii,
|
||||
struct transfer_info *ti,
|
||||
struct transfer_info *out)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int toonie_suspend(struct codec_info_item *cii, pm_message_t state)
|
||||
{
|
||||
/* can we turn it off somehow? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int toonie_resume(struct codec_info_item *cii)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static struct codec_info toonie_codec_info = {
|
||||
.transfers = toonie_transfers,
|
||||
.sysclock_factor = 256,
|
||||
.bus_factor = 64,
|
||||
.owner = THIS_MODULE,
|
||||
.usable = toonie_usable,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = toonie_suspend,
|
||||
.resume = toonie_resume,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int toonie_init_codec(struct aoa_codec *codec)
|
||||
{
|
||||
struct toonie *toonie = codec_to_toonie(codec);
|
||||
|
||||
/* nothing connected? what a joke! */
|
||||
if (toonie->codec.connected != 1)
|
||||
return -ENOTCONN;
|
||||
|
||||
if (aoa_snd_device_new(SNDRV_DEV_CODEC, toonie, &ops)) {
|
||||
printk(KERN_ERR PFX "failed to create toonie snd device!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (toonie->codec.soundbus_dev->attach_codec(toonie->codec.soundbus_dev,
|
||||
aoa_get_card(),
|
||||
&toonie_codec_info, toonie)) {
|
||||
printk(KERN_ERR PFX "error creating toonie pcm\n");
|
||||
snd_device_free(aoa_get_card(), toonie);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void toonie_exit_codec(struct aoa_codec *codec)
|
||||
{
|
||||
struct toonie *toonie = codec_to_toonie(codec);
|
||||
|
||||
if (!toonie->codec.soundbus_dev) {
|
||||
printk(KERN_ERR PFX "toonie_exit_codec called without soundbus_dev!\n");
|
||||
return;
|
||||
}
|
||||
toonie->codec.soundbus_dev->detach_codec(toonie->codec.soundbus_dev, toonie);
|
||||
}
|
||||
|
||||
static struct toonie *toonie;
|
||||
|
||||
static int __init toonie_init(void)
|
||||
{
|
||||
toonie = kzalloc(sizeof(struct toonie), GFP_KERNEL);
|
||||
|
||||
if (!toonie)
|
||||
return -ENOMEM;
|
||||
|
||||
strlcpy(toonie->codec.name, "toonie", sizeof(toonie->codec.name));
|
||||
toonie->codec.owner = THIS_MODULE;
|
||||
toonie->codec.init = toonie_init_codec;
|
||||
toonie->codec.exit = toonie_exit_codec;
|
||||
|
||||
if (aoa_codec_register(&toonie->codec)) {
|
||||
kfree(toonie);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit toonie_exit(void)
|
||||
{
|
||||
aoa_codec_unregister(&toonie->codec);
|
||||
kfree(toonie);
|
||||
}
|
||||
|
||||
module_init(toonie_init);
|
||||
module_exit(toonie_exit);
|
||||
5
sound/aoa/core/Makefile
Normal file
5
sound/aoa/core/Makefile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
obj-$(CONFIG_SND_AOA) += snd-aoa.o
|
||||
snd-aoa-objs := core.o \
|
||||
alsa.o \
|
||||
gpio-pmf.o \
|
||||
gpio-feature.o
|
||||
99
sound/aoa/core/alsa.c
Normal file
99
sound/aoa/core/alsa.c
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Apple Onboard Audio Alsa helpers
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include "alsa.h"
|
||||
|
||||
static int index = -1;
|
||||
module_param(index, int, 0444);
|
||||
MODULE_PARM_DESC(index, "index for AOA sound card.");
|
||||
|
||||
static struct aoa_card *aoa_card;
|
||||
|
||||
int aoa_alsa_init(char *name, struct module *mod, struct device *dev)
|
||||
{
|
||||
struct snd_card *alsa_card;
|
||||
int err;
|
||||
|
||||
if (aoa_card)
|
||||
/* cannot be EEXIST due to usage in aoa_fabric_register */
|
||||
return -EBUSY;
|
||||
|
||||
err = snd_card_new(dev, index, name, mod, sizeof(struct aoa_card),
|
||||
&alsa_card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
aoa_card = alsa_card->private_data;
|
||||
aoa_card->alsa_card = alsa_card;
|
||||
strlcpy(alsa_card->driver, "AppleOnbdAudio", sizeof(alsa_card->driver));
|
||||
strlcpy(alsa_card->shortname, name, sizeof(alsa_card->shortname));
|
||||
strlcpy(alsa_card->longname, name, sizeof(alsa_card->longname));
|
||||
strlcpy(alsa_card->mixername, name, sizeof(alsa_card->mixername));
|
||||
err = snd_card_register(aoa_card->alsa_card);
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "snd-aoa: couldn't register alsa card\n");
|
||||
snd_card_free(aoa_card->alsa_card);
|
||||
aoa_card = NULL;
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_card *aoa_get_card(void)
|
||||
{
|
||||
if (aoa_card)
|
||||
return aoa_card->alsa_card;
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(aoa_get_card);
|
||||
|
||||
void aoa_alsa_cleanup(void)
|
||||
{
|
||||
if (aoa_card) {
|
||||
snd_card_free(aoa_card->alsa_card);
|
||||
aoa_card = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int aoa_snd_device_new(enum snd_device_type type,
|
||||
void * device_data, struct snd_device_ops * ops)
|
||||
{
|
||||
struct snd_card *card = aoa_get_card();
|
||||
int err;
|
||||
|
||||
if (!card) return -ENOMEM;
|
||||
|
||||
err = snd_device_new(card, type, device_data, ops);
|
||||
if (err) {
|
||||
printk(KERN_ERR "snd-aoa: failed to create snd device (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
err = snd_device_register(card, device_data);
|
||||
if (err) {
|
||||
printk(KERN_ERR "snd-aoa: failed to register "
|
||||
"snd device (%d)\n", err);
|
||||
printk(KERN_ERR "snd-aoa: have you forgotten the "
|
||||
"dev_register callback?\n");
|
||||
snd_device_free(card, device_data);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(aoa_snd_device_new);
|
||||
|
||||
int aoa_snd_ctl_add(struct snd_kcontrol* control)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!aoa_card) return -ENODEV;
|
||||
|
||||
err = snd_ctl_add(aoa_card->alsa_card, control);
|
||||
if (err)
|
||||
printk(KERN_ERR "snd-aoa: failed to add alsa control (%d)\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(aoa_snd_ctl_add);
|
||||
16
sound/aoa/core/alsa.h
Normal file
16
sound/aoa/core/alsa.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Apple Onboard Audio Alsa private helpers
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
|
||||
#ifndef __SND_AOA_ALSA_H
|
||||
#define __SND_AOA_ALSA_H
|
||||
#include "../aoa.h"
|
||||
|
||||
extern int aoa_alsa_init(char *name, struct module *mod, struct device *dev);
|
||||
extern void aoa_alsa_cleanup(void);
|
||||
|
||||
#endif /* __SND_AOA_ALSA_H */
|
||||
162
sound/aoa/core/core.c
Normal file
162
sound/aoa/core/core.c
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Apple Onboard Audio driver core
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/list.h>
|
||||
#include "../aoa.h"
|
||||
#include "alsa.h"
|
||||
|
||||
MODULE_DESCRIPTION("Apple Onboard Audio Sound Driver");
|
||||
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* We allow only one fabric. This simplifies things,
|
||||
* and more don't really make that much sense */
|
||||
static struct aoa_fabric *fabric;
|
||||
static LIST_HEAD(codec_list);
|
||||
|
||||
static int attach_codec_to_fabric(struct aoa_codec *c)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!try_module_get(c->owner))
|
||||
return -EBUSY;
|
||||
/* found_codec has to be assigned */
|
||||
err = -ENOENT;
|
||||
if (fabric->found_codec)
|
||||
err = fabric->found_codec(c);
|
||||
if (err) {
|
||||
module_put(c->owner);
|
||||
printk(KERN_ERR "snd-aoa: fabric didn't like codec %s\n",
|
||||
c->name);
|
||||
return err;
|
||||
}
|
||||
c->fabric = fabric;
|
||||
|
||||
err = 0;
|
||||
if (c->init)
|
||||
err = c->init(c);
|
||||
if (err) {
|
||||
printk(KERN_ERR "snd-aoa: codec %s didn't init\n", c->name);
|
||||
c->fabric = NULL;
|
||||
if (fabric->remove_codec)
|
||||
fabric->remove_codec(c);
|
||||
module_put(c->owner);
|
||||
return err;
|
||||
}
|
||||
if (fabric->attached_codec)
|
||||
fabric->attached_codec(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int aoa_codec_register(struct aoa_codec *codec)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
/* if there's a fabric already, we can tell if we
|
||||
* will want to have this codec, so propagate error
|
||||
* through. Otherwise, this will happen later... */
|
||||
if (fabric)
|
||||
err = attach_codec_to_fabric(codec);
|
||||
if (!err)
|
||||
list_add(&codec->list, &codec_list);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(aoa_codec_register);
|
||||
|
||||
void aoa_codec_unregister(struct aoa_codec *codec)
|
||||
{
|
||||
list_del(&codec->list);
|
||||
if (codec->fabric && codec->exit)
|
||||
codec->exit(codec);
|
||||
if (fabric && fabric->remove_codec)
|
||||
fabric->remove_codec(codec);
|
||||
codec->fabric = NULL;
|
||||
module_put(codec->owner);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(aoa_codec_unregister);
|
||||
|
||||
int aoa_fabric_register(struct aoa_fabric *new_fabric, struct device *dev)
|
||||
{
|
||||
struct aoa_codec *c;
|
||||
int err;
|
||||
|
||||
/* allow querying for presence of fabric
|
||||
* (i.e. do this test first!) */
|
||||
if (new_fabric == fabric) {
|
||||
err = -EALREADY;
|
||||
goto attach;
|
||||
}
|
||||
if (fabric)
|
||||
return -EEXIST;
|
||||
if (!new_fabric)
|
||||
return -EINVAL;
|
||||
|
||||
err = aoa_alsa_init(new_fabric->name, new_fabric->owner, dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
fabric = new_fabric;
|
||||
|
||||
attach:
|
||||
list_for_each_entry(c, &codec_list, list) {
|
||||
if (c->fabric != fabric)
|
||||
attach_codec_to_fabric(c);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(aoa_fabric_register);
|
||||
|
||||
void aoa_fabric_unregister(struct aoa_fabric *old_fabric)
|
||||
{
|
||||
struct aoa_codec *c;
|
||||
|
||||
if (fabric != old_fabric)
|
||||
return;
|
||||
|
||||
list_for_each_entry(c, &codec_list, list) {
|
||||
if (c->fabric)
|
||||
aoa_fabric_unlink_codec(c);
|
||||
}
|
||||
|
||||
aoa_alsa_cleanup();
|
||||
|
||||
fabric = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(aoa_fabric_unregister);
|
||||
|
||||
void aoa_fabric_unlink_codec(struct aoa_codec *codec)
|
||||
{
|
||||
if (!codec->fabric) {
|
||||
printk(KERN_ERR "snd-aoa: fabric unassigned "
|
||||
"in aoa_fabric_unlink_codec\n");
|
||||
dump_stack();
|
||||
return;
|
||||
}
|
||||
if (codec->exit)
|
||||
codec->exit(codec);
|
||||
if (codec->fabric->remove_codec)
|
||||
codec->fabric->remove_codec(codec);
|
||||
codec->fabric = NULL;
|
||||
module_put(codec->owner);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(aoa_fabric_unlink_codec);
|
||||
|
||||
static int __init aoa_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit aoa_exit(void)
|
||||
{
|
||||
aoa_alsa_cleanup();
|
||||
}
|
||||
|
||||
module_init(aoa_init);
|
||||
module_exit(aoa_exit);
|
||||
423
sound/aoa/core/gpio-feature.c
Normal file
423
sound/aoa/core/gpio-feature.c
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
* Apple Onboard Audio feature call GPIO control
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*
|
||||
* This file contains the GPIO control routines for
|
||||
* direct (through feature calls) access to the GPIO
|
||||
* registers.
|
||||
*/
|
||||
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <asm/pmac_feature.h>
|
||||
#include "../aoa.h"
|
||||
|
||||
/* TODO: these are lots of global variables
|
||||
* that aren't used on most machines...
|
||||
* Move them into a dynamically allocated
|
||||
* structure and use that.
|
||||
*/
|
||||
|
||||
/* these are the GPIO numbers (register addresses as offsets into
|
||||
* the GPIO space) */
|
||||
static int headphone_mute_gpio;
|
||||
static int master_mute_gpio;
|
||||
static int amp_mute_gpio;
|
||||
static int lineout_mute_gpio;
|
||||
static int hw_reset_gpio;
|
||||
static int lineout_detect_gpio;
|
||||
static int headphone_detect_gpio;
|
||||
static int linein_detect_gpio;
|
||||
|
||||
/* see the SWITCH_GPIO macro */
|
||||
static int headphone_mute_gpio_activestate;
|
||||
static int master_mute_gpio_activestate;
|
||||
static int amp_mute_gpio_activestate;
|
||||
static int lineout_mute_gpio_activestate;
|
||||
static int hw_reset_gpio_activestate;
|
||||
static int lineout_detect_gpio_activestate;
|
||||
static int headphone_detect_gpio_activestate;
|
||||
static int linein_detect_gpio_activestate;
|
||||
|
||||
/* node pointers that we save when getting the GPIO number
|
||||
* to get the interrupt later */
|
||||
static struct device_node *lineout_detect_node;
|
||||
static struct device_node *linein_detect_node;
|
||||
static struct device_node *headphone_detect_node;
|
||||
|
||||
static int lineout_detect_irq;
|
||||
static int linein_detect_irq;
|
||||
static int headphone_detect_irq;
|
||||
|
||||
static struct device_node *get_gpio(char *name,
|
||||
char *altname,
|
||||
int *gpioptr,
|
||||
int *gpioactiveptr)
|
||||
{
|
||||
struct device_node *np, *gpio;
|
||||
const u32 *reg;
|
||||
const char *audio_gpio;
|
||||
|
||||
*gpioptr = -1;
|
||||
|
||||
/* check if we can get it the easy way ... */
|
||||
np = of_find_node_by_name(NULL, name);
|
||||
if (!np) {
|
||||
/* some machines have only gpioX/extint-gpioX nodes,
|
||||
* and an audio-gpio property saying what it is ...
|
||||
* So what we have to do is enumerate all children
|
||||
* of the gpio node and check them all. */
|
||||
gpio = of_find_node_by_name(NULL, "gpio");
|
||||
if (!gpio)
|
||||
return NULL;
|
||||
while ((np = of_get_next_child(gpio, np))) {
|
||||
audio_gpio = of_get_property(np, "audio-gpio", NULL);
|
||||
if (!audio_gpio)
|
||||
continue;
|
||||
if (strcmp(audio_gpio, name) == 0)
|
||||
break;
|
||||
if (altname && (strcmp(audio_gpio, altname) == 0))
|
||||
break;
|
||||
}
|
||||
/* still not found, assume not there */
|
||||
if (!np)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
reg = of_get_property(np, "reg", NULL);
|
||||
if (!reg)
|
||||
return NULL;
|
||||
|
||||
*gpioptr = *reg;
|
||||
|
||||
/* this is a hack, usually the GPIOs 'reg' property
|
||||
* should have the offset based from the GPIO space
|
||||
* which is at 0x50, but apparently not always... */
|
||||
if (*gpioptr < 0x50)
|
||||
*gpioptr += 0x50;
|
||||
|
||||
reg = of_get_property(np, "audio-gpio-active-state", NULL);
|
||||
if (!reg)
|
||||
/* Apple seems to default to 1, but
|
||||
* that doesn't seem right at least on most
|
||||
* machines. So until proven that the opposite
|
||||
* is necessary, we default to 0
|
||||
* (which, incidentally, snd-powermac also does...) */
|
||||
*gpioactiveptr = 0;
|
||||
else
|
||||
*gpioactiveptr = *reg;
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
static void get_irq(struct device_node * np, int *irqptr)
|
||||
{
|
||||
if (np)
|
||||
*irqptr = irq_of_parse_and_map(np, 0);
|
||||
else
|
||||
*irqptr = NO_IRQ;
|
||||
}
|
||||
|
||||
/* 0x4 is outenable, 0x1 is out, thus 4 or 5 */
|
||||
#define SWITCH_GPIO(name, v, on) \
|
||||
(((v)&~1) | ((on)? \
|
||||
(name##_gpio_activestate==0?4:5): \
|
||||
(name##_gpio_activestate==0?5:4)))
|
||||
|
||||
#define FTR_GPIO(name, bit) \
|
||||
static void ftr_gpio_set_##name(struct gpio_runtime *rt, int on)\
|
||||
{ \
|
||||
int v; \
|
||||
\
|
||||
if (unlikely(!rt)) return; \
|
||||
\
|
||||
if (name##_mute_gpio < 0) \
|
||||
return; \
|
||||
\
|
||||
v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, \
|
||||
name##_mute_gpio, \
|
||||
0); \
|
||||
\
|
||||
/* muted = !on... */ \
|
||||
v = SWITCH_GPIO(name##_mute, v, !on); \
|
||||
\
|
||||
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, \
|
||||
name##_mute_gpio, v); \
|
||||
\
|
||||
rt->implementation_private &= ~(1<<bit); \
|
||||
rt->implementation_private |= (!!on << bit); \
|
||||
} \
|
||||
static int ftr_gpio_get_##name(struct gpio_runtime *rt) \
|
||||
{ \
|
||||
if (unlikely(!rt)) return 0; \
|
||||
return (rt->implementation_private>>bit)&1; \
|
||||
}
|
||||
|
||||
FTR_GPIO(headphone, 0);
|
||||
FTR_GPIO(amp, 1);
|
||||
FTR_GPIO(lineout, 2);
|
||||
FTR_GPIO(master, 3);
|
||||
|
||||
static void ftr_gpio_set_hw_reset(struct gpio_runtime *rt, int on)
|
||||
{
|
||||
int v;
|
||||
|
||||
if (unlikely(!rt)) return;
|
||||
if (hw_reset_gpio < 0)
|
||||
return;
|
||||
|
||||
v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL,
|
||||
hw_reset_gpio, 0);
|
||||
v = SWITCH_GPIO(hw_reset, v, on);
|
||||
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL,
|
||||
hw_reset_gpio, v);
|
||||
}
|
||||
|
||||
static struct gpio_methods methods;
|
||||
|
||||
static void ftr_gpio_all_amps_off(struct gpio_runtime *rt)
|
||||
{
|
||||
int saved;
|
||||
|
||||
if (unlikely(!rt)) return;
|
||||
saved = rt->implementation_private;
|
||||
ftr_gpio_set_headphone(rt, 0);
|
||||
ftr_gpio_set_amp(rt, 0);
|
||||
ftr_gpio_set_lineout(rt, 0);
|
||||
if (methods.set_master)
|
||||
ftr_gpio_set_master(rt, 0);
|
||||
rt->implementation_private = saved;
|
||||
}
|
||||
|
||||
static void ftr_gpio_all_amps_restore(struct gpio_runtime *rt)
|
||||
{
|
||||
int s;
|
||||
|
||||
if (unlikely(!rt)) return;
|
||||
s = rt->implementation_private;
|
||||
ftr_gpio_set_headphone(rt, (s>>0)&1);
|
||||
ftr_gpio_set_amp(rt, (s>>1)&1);
|
||||
ftr_gpio_set_lineout(rt, (s>>2)&1);
|
||||
if (methods.set_master)
|
||||
ftr_gpio_set_master(rt, (s>>3)&1);
|
||||
}
|
||||
|
||||
static void ftr_handle_notify(struct work_struct *work)
|
||||
{
|
||||
struct gpio_notification *notif =
|
||||
container_of(work, struct gpio_notification, work.work);
|
||||
|
||||
mutex_lock(¬if->mutex);
|
||||
if (notif->notify)
|
||||
notif->notify(notif->data);
|
||||
mutex_unlock(¬if->mutex);
|
||||
}
|
||||
|
||||
static void gpio_enable_dual_edge(int gpio)
|
||||
{
|
||||
int v;
|
||||
|
||||
if (gpio == -1)
|
||||
return;
|
||||
v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0);
|
||||
v |= 0x80; /* enable dual edge */
|
||||
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio, v);
|
||||
}
|
||||
|
||||
static void ftr_gpio_init(struct gpio_runtime *rt)
|
||||
{
|
||||
get_gpio("headphone-mute", NULL,
|
||||
&headphone_mute_gpio,
|
||||
&headphone_mute_gpio_activestate);
|
||||
get_gpio("amp-mute", NULL,
|
||||
&_mute_gpio,
|
||||
&_mute_gpio_activestate);
|
||||
get_gpio("lineout-mute", NULL,
|
||||
&lineout_mute_gpio,
|
||||
&lineout_mute_gpio_activestate);
|
||||
get_gpio("hw-reset", "audio-hw-reset",
|
||||
&hw_reset_gpio,
|
||||
&hw_reset_gpio_activestate);
|
||||
if (get_gpio("master-mute", NULL,
|
||||
&master_mute_gpio,
|
||||
&master_mute_gpio_activestate)) {
|
||||
methods.set_master = ftr_gpio_set_master;
|
||||
methods.get_master = ftr_gpio_get_master;
|
||||
}
|
||||
|
||||
headphone_detect_node = get_gpio("headphone-detect", NULL,
|
||||
&headphone_detect_gpio,
|
||||
&headphone_detect_gpio_activestate);
|
||||
/* go Apple, and thanks for giving these different names
|
||||
* across the board... */
|
||||
lineout_detect_node = get_gpio("lineout-detect", "line-output-detect",
|
||||
&lineout_detect_gpio,
|
||||
&lineout_detect_gpio_activestate);
|
||||
linein_detect_node = get_gpio("linein-detect", "line-input-detect",
|
||||
&linein_detect_gpio,
|
||||
&linein_detect_gpio_activestate);
|
||||
|
||||
gpio_enable_dual_edge(headphone_detect_gpio);
|
||||
gpio_enable_dual_edge(lineout_detect_gpio);
|
||||
gpio_enable_dual_edge(linein_detect_gpio);
|
||||
|
||||
get_irq(headphone_detect_node, &headphone_detect_irq);
|
||||
get_irq(lineout_detect_node, &lineout_detect_irq);
|
||||
get_irq(linein_detect_node, &linein_detect_irq);
|
||||
|
||||
ftr_gpio_all_amps_off(rt);
|
||||
rt->implementation_private = 0;
|
||||
INIT_DELAYED_WORK(&rt->headphone_notify.work, ftr_handle_notify);
|
||||
INIT_DELAYED_WORK(&rt->line_in_notify.work, ftr_handle_notify);
|
||||
INIT_DELAYED_WORK(&rt->line_out_notify.work, ftr_handle_notify);
|
||||
mutex_init(&rt->headphone_notify.mutex);
|
||||
mutex_init(&rt->line_in_notify.mutex);
|
||||
mutex_init(&rt->line_out_notify.mutex);
|
||||
}
|
||||
|
||||
static void ftr_gpio_exit(struct gpio_runtime *rt)
|
||||
{
|
||||
ftr_gpio_all_amps_off(rt);
|
||||
rt->implementation_private = 0;
|
||||
if (rt->headphone_notify.notify)
|
||||
free_irq(headphone_detect_irq, &rt->headphone_notify);
|
||||
if (rt->line_in_notify.gpio_private)
|
||||
free_irq(linein_detect_irq, &rt->line_in_notify);
|
||||
if (rt->line_out_notify.gpio_private)
|
||||
free_irq(lineout_detect_irq, &rt->line_out_notify);
|
||||
cancel_delayed_work_sync(&rt->headphone_notify.work);
|
||||
cancel_delayed_work_sync(&rt->line_in_notify.work);
|
||||
cancel_delayed_work_sync(&rt->line_out_notify.work);
|
||||
mutex_destroy(&rt->headphone_notify.mutex);
|
||||
mutex_destroy(&rt->line_in_notify.mutex);
|
||||
mutex_destroy(&rt->line_out_notify.mutex);
|
||||
}
|
||||
|
||||
static irqreturn_t ftr_handle_notify_irq(int xx, void *data)
|
||||
{
|
||||
struct gpio_notification *notif = data;
|
||||
|
||||
schedule_delayed_work(¬if->work, 0);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ftr_set_notify(struct gpio_runtime *rt,
|
||||
enum notify_type type,
|
||||
notify_func_t notify,
|
||||
void *data)
|
||||
{
|
||||
struct gpio_notification *notif;
|
||||
notify_func_t old;
|
||||
int irq;
|
||||
char *name;
|
||||
int err = -EBUSY;
|
||||
|
||||
switch (type) {
|
||||
case AOA_NOTIFY_HEADPHONE:
|
||||
notif = &rt->headphone_notify;
|
||||
name = "headphone-detect";
|
||||
irq = headphone_detect_irq;
|
||||
break;
|
||||
case AOA_NOTIFY_LINE_IN:
|
||||
notif = &rt->line_in_notify;
|
||||
name = "linein-detect";
|
||||
irq = linein_detect_irq;
|
||||
break;
|
||||
case AOA_NOTIFY_LINE_OUT:
|
||||
notif = &rt->line_out_notify;
|
||||
name = "lineout-detect";
|
||||
irq = lineout_detect_irq;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (irq == NO_IRQ)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(¬if->mutex);
|
||||
|
||||
old = notif->notify;
|
||||
|
||||
if (!old && !notify) {
|
||||
err = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (old && notify) {
|
||||
if (old == notify && notif->data == data)
|
||||
err = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (old && !notify)
|
||||
free_irq(irq, notif);
|
||||
|
||||
if (!old && notify) {
|
||||
err = request_irq(irq, ftr_handle_notify_irq, 0, name, notif);
|
||||
if (err)
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
notif->notify = notify;
|
||||
notif->data = data;
|
||||
|
||||
err = 0;
|
||||
out_unlock:
|
||||
mutex_unlock(¬if->mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ftr_get_detect(struct gpio_runtime *rt,
|
||||
enum notify_type type)
|
||||
{
|
||||
int gpio, ret, active;
|
||||
|
||||
switch (type) {
|
||||
case AOA_NOTIFY_HEADPHONE:
|
||||
gpio = headphone_detect_gpio;
|
||||
active = headphone_detect_gpio_activestate;
|
||||
break;
|
||||
case AOA_NOTIFY_LINE_IN:
|
||||
gpio = linein_detect_gpio;
|
||||
active = linein_detect_gpio_activestate;
|
||||
break;
|
||||
case AOA_NOTIFY_LINE_OUT:
|
||||
gpio = lineout_detect_gpio;
|
||||
active = lineout_detect_gpio_activestate;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (gpio == -1)
|
||||
return -ENODEV;
|
||||
|
||||
ret = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return ((ret >> 1) & 1) == active;
|
||||
}
|
||||
|
||||
static struct gpio_methods methods = {
|
||||
.init = ftr_gpio_init,
|
||||
.exit = ftr_gpio_exit,
|
||||
.all_amps_off = ftr_gpio_all_amps_off,
|
||||
.all_amps_restore = ftr_gpio_all_amps_restore,
|
||||
.set_headphone = ftr_gpio_set_headphone,
|
||||
.set_speakers = ftr_gpio_set_amp,
|
||||
.set_lineout = ftr_gpio_set_lineout,
|
||||
.set_hw_reset = ftr_gpio_set_hw_reset,
|
||||
.get_headphone = ftr_gpio_get_headphone,
|
||||
.get_speakers = ftr_gpio_get_amp,
|
||||
.get_lineout = ftr_gpio_get_lineout,
|
||||
.set_notify = ftr_set_notify,
|
||||
.get_detect = ftr_get_detect,
|
||||
};
|
||||
|
||||
struct gpio_methods *ftr_gpio_methods = &methods;
|
||||
EXPORT_SYMBOL_GPL(ftr_gpio_methods);
|
||||
253
sound/aoa/core/gpio-pmf.c
Normal file
253
sound/aoa/core/gpio-pmf.c
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Apple Onboard Audio pmf GPIOs
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <asm/pmac_feature.h>
|
||||
#include <asm/pmac_pfunc.h>
|
||||
#include "../aoa.h"
|
||||
|
||||
#define PMF_GPIO(name, bit) \
|
||||
static void pmf_gpio_set_##name(struct gpio_runtime *rt, int on)\
|
||||
{ \
|
||||
struct pmf_args args = { .count = 1, .u[0].v = !on }; \
|
||||
int rc; \
|
||||
\
|
||||
if (unlikely(!rt)) return; \
|
||||
rc = pmf_call_function(rt->node, #name "-mute", &args); \
|
||||
if (rc && rc != -ENODEV) \
|
||||
printk(KERN_WARNING "pmf_gpio_set_" #name \
|
||||
" failed, rc: %d\n", rc); \
|
||||
rt->implementation_private &= ~(1<<bit); \
|
||||
rt->implementation_private |= (!!on << bit); \
|
||||
} \
|
||||
static int pmf_gpio_get_##name(struct gpio_runtime *rt) \
|
||||
{ \
|
||||
if (unlikely(!rt)) return 0; \
|
||||
return (rt->implementation_private>>bit)&1; \
|
||||
}
|
||||
|
||||
PMF_GPIO(headphone, 0);
|
||||
PMF_GPIO(amp, 1);
|
||||
PMF_GPIO(lineout, 2);
|
||||
|
||||
static void pmf_gpio_set_hw_reset(struct gpio_runtime *rt, int on)
|
||||
{
|
||||
struct pmf_args args = { .count = 1, .u[0].v = !!on };
|
||||
int rc;
|
||||
|
||||
if (unlikely(!rt)) return;
|
||||
rc = pmf_call_function(rt->node, "hw-reset", &args);
|
||||
if (rc)
|
||||
printk(KERN_WARNING "pmf_gpio_set_hw_reset"
|
||||
" failed, rc: %d\n", rc);
|
||||
}
|
||||
|
||||
static void pmf_gpio_all_amps_off(struct gpio_runtime *rt)
|
||||
{
|
||||
int saved;
|
||||
|
||||
if (unlikely(!rt)) return;
|
||||
saved = rt->implementation_private;
|
||||
pmf_gpio_set_headphone(rt, 0);
|
||||
pmf_gpio_set_amp(rt, 0);
|
||||
pmf_gpio_set_lineout(rt, 0);
|
||||
rt->implementation_private = saved;
|
||||
}
|
||||
|
||||
static void pmf_gpio_all_amps_restore(struct gpio_runtime *rt)
|
||||
{
|
||||
int s;
|
||||
|
||||
if (unlikely(!rt)) return;
|
||||
s = rt->implementation_private;
|
||||
pmf_gpio_set_headphone(rt, (s>>0)&1);
|
||||
pmf_gpio_set_amp(rt, (s>>1)&1);
|
||||
pmf_gpio_set_lineout(rt, (s>>2)&1);
|
||||
}
|
||||
|
||||
static void pmf_handle_notify(struct work_struct *work)
|
||||
{
|
||||
struct gpio_notification *notif =
|
||||
container_of(work, struct gpio_notification, work.work);
|
||||
|
||||
mutex_lock(¬if->mutex);
|
||||
if (notif->notify)
|
||||
notif->notify(notif->data);
|
||||
mutex_unlock(¬if->mutex);
|
||||
}
|
||||
|
||||
static void pmf_gpio_init(struct gpio_runtime *rt)
|
||||
{
|
||||
pmf_gpio_all_amps_off(rt);
|
||||
rt->implementation_private = 0;
|
||||
INIT_DELAYED_WORK(&rt->headphone_notify.work, pmf_handle_notify);
|
||||
INIT_DELAYED_WORK(&rt->line_in_notify.work, pmf_handle_notify);
|
||||
INIT_DELAYED_WORK(&rt->line_out_notify.work, pmf_handle_notify);
|
||||
mutex_init(&rt->headphone_notify.mutex);
|
||||
mutex_init(&rt->line_in_notify.mutex);
|
||||
mutex_init(&rt->line_out_notify.mutex);
|
||||
}
|
||||
|
||||
static void pmf_gpio_exit(struct gpio_runtime *rt)
|
||||
{
|
||||
pmf_gpio_all_amps_off(rt);
|
||||
rt->implementation_private = 0;
|
||||
|
||||
if (rt->headphone_notify.gpio_private)
|
||||
pmf_unregister_irq_client(rt->headphone_notify.gpio_private);
|
||||
if (rt->line_in_notify.gpio_private)
|
||||
pmf_unregister_irq_client(rt->line_in_notify.gpio_private);
|
||||
if (rt->line_out_notify.gpio_private)
|
||||
pmf_unregister_irq_client(rt->line_out_notify.gpio_private);
|
||||
|
||||
/* make sure no work is pending before freeing
|
||||
* all things */
|
||||
cancel_delayed_work_sync(&rt->headphone_notify.work);
|
||||
cancel_delayed_work_sync(&rt->line_in_notify.work);
|
||||
cancel_delayed_work_sync(&rt->line_out_notify.work);
|
||||
|
||||
mutex_destroy(&rt->headphone_notify.mutex);
|
||||
mutex_destroy(&rt->line_in_notify.mutex);
|
||||
mutex_destroy(&rt->line_out_notify.mutex);
|
||||
|
||||
kfree(rt->headphone_notify.gpio_private);
|
||||
kfree(rt->line_in_notify.gpio_private);
|
||||
kfree(rt->line_out_notify.gpio_private);
|
||||
}
|
||||
|
||||
static void pmf_handle_notify_irq(void *data)
|
||||
{
|
||||
struct gpio_notification *notif = data;
|
||||
|
||||
schedule_delayed_work(¬if->work, 0);
|
||||
}
|
||||
|
||||
static int pmf_set_notify(struct gpio_runtime *rt,
|
||||
enum notify_type type,
|
||||
notify_func_t notify,
|
||||
void *data)
|
||||
{
|
||||
struct gpio_notification *notif;
|
||||
notify_func_t old;
|
||||
struct pmf_irq_client *irq_client;
|
||||
char *name;
|
||||
int err = -EBUSY;
|
||||
|
||||
switch (type) {
|
||||
case AOA_NOTIFY_HEADPHONE:
|
||||
notif = &rt->headphone_notify;
|
||||
name = "headphone-detect";
|
||||
break;
|
||||
case AOA_NOTIFY_LINE_IN:
|
||||
notif = &rt->line_in_notify;
|
||||
name = "linein-detect";
|
||||
break;
|
||||
case AOA_NOTIFY_LINE_OUT:
|
||||
notif = &rt->line_out_notify;
|
||||
name = "lineout-detect";
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(¬if->mutex);
|
||||
|
||||
old = notif->notify;
|
||||
|
||||
if (!old && !notify) {
|
||||
err = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (old && notify) {
|
||||
if (old == notify && notif->data == data)
|
||||
err = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (old && !notify) {
|
||||
irq_client = notif->gpio_private;
|
||||
pmf_unregister_irq_client(irq_client);
|
||||
kfree(irq_client);
|
||||
notif->gpio_private = NULL;
|
||||
}
|
||||
if (!old && notify) {
|
||||
irq_client = kzalloc(sizeof(struct pmf_irq_client),
|
||||
GFP_KERNEL);
|
||||
if (!irq_client) {
|
||||
err = -ENOMEM;
|
||||
goto out_unlock;
|
||||
}
|
||||
irq_client->data = notif;
|
||||
irq_client->handler = pmf_handle_notify_irq;
|
||||
irq_client->owner = THIS_MODULE;
|
||||
err = pmf_register_irq_client(rt->node,
|
||||
name,
|
||||
irq_client);
|
||||
if (err) {
|
||||
printk(KERN_ERR "snd-aoa: gpio layer failed to"
|
||||
" register %s irq (%d)\n", name, err);
|
||||
kfree(irq_client);
|
||||
goto out_unlock;
|
||||
}
|
||||
notif->gpio_private = irq_client;
|
||||
}
|
||||
notif->notify = notify;
|
||||
notif->data = data;
|
||||
|
||||
err = 0;
|
||||
out_unlock:
|
||||
mutex_unlock(¬if->mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pmf_get_detect(struct gpio_runtime *rt,
|
||||
enum notify_type type)
|
||||
{
|
||||
char *name;
|
||||
int err = -EBUSY, ret;
|
||||
struct pmf_args args = { .count = 1, .u[0].p = &ret };
|
||||
|
||||
switch (type) {
|
||||
case AOA_NOTIFY_HEADPHONE:
|
||||
name = "headphone-detect";
|
||||
break;
|
||||
case AOA_NOTIFY_LINE_IN:
|
||||
name = "linein-detect";
|
||||
break;
|
||||
case AOA_NOTIFY_LINE_OUT:
|
||||
name = "lineout-detect";
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = pmf_call_function(rt->node, name, &args);
|
||||
if (err)
|
||||
return err;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct gpio_methods methods = {
|
||||
.init = pmf_gpio_init,
|
||||
.exit = pmf_gpio_exit,
|
||||
.all_amps_off = pmf_gpio_all_amps_off,
|
||||
.all_amps_restore = pmf_gpio_all_amps_restore,
|
||||
.set_headphone = pmf_gpio_set_headphone,
|
||||
.set_speakers = pmf_gpio_set_amp,
|
||||
.set_lineout = pmf_gpio_set_lineout,
|
||||
.set_hw_reset = pmf_gpio_set_hw_reset,
|
||||
.get_headphone = pmf_gpio_get_headphone,
|
||||
.get_speakers = pmf_gpio_get_amp,
|
||||
.get_lineout = pmf_gpio_get_lineout,
|
||||
.set_notify = pmf_set_notify,
|
||||
.get_detect = pmf_get_detect,
|
||||
};
|
||||
|
||||
struct gpio_methods *pmf_gpio_methods = &methods;
|
||||
EXPORT_SYMBOL_GPL(pmf_gpio_methods);
|
||||
11
sound/aoa/fabrics/Kconfig
Normal file
11
sound/aoa/fabrics/Kconfig
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
config SND_AOA_FABRIC_LAYOUT
|
||||
tristate "layout-id fabric"
|
||||
select SND_AOA_SOUNDBUS
|
||||
select SND_AOA_SOUNDBUS_I2S
|
||||
---help---
|
||||
This enables the layout-id fabric for the Apple Onboard
|
||||
Audio driver, the module holding it all together
|
||||
based on the device-tree's layout-id property.
|
||||
|
||||
If you are unsure and have a later Apple machine,
|
||||
compile it as a module.
|
||||
3
sound/aoa/fabrics/Makefile
Normal file
3
sound/aoa/fabrics/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
snd-aoa-fabric-layout-objs += layout.o
|
||||
|
||||
obj-$(CONFIG_SND_AOA_FABRIC_LAYOUT) += snd-aoa-fabric-layout.o
|
||||
1176
sound/aoa/fabrics/layout.c
Normal file
1176
sound/aoa/fabrics/layout.c
Normal file
File diff suppressed because it is too large
Load diff
14
sound/aoa/soundbus/Kconfig
Normal file
14
sound/aoa/soundbus/Kconfig
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
config SND_AOA_SOUNDBUS
|
||||
tristate "Apple Soundbus support"
|
||||
select SND_PCM
|
||||
---help---
|
||||
This option enables the generic driver for the soundbus
|
||||
support on Apple machines.
|
||||
|
||||
It is required for the sound bus implementations.
|
||||
|
||||
config SND_AOA_SOUNDBUS_I2S
|
||||
tristate "I2S bus support"
|
||||
depends on SND_AOA_SOUNDBUS && PCI
|
||||
---help---
|
||||
This option enables support for Apple I2S busses.
|
||||
3
sound/aoa/soundbus/Makefile
Normal file
3
sound/aoa/soundbus/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
obj-$(CONFIG_SND_AOA_SOUNDBUS) += snd-aoa-soundbus.o
|
||||
snd-aoa-soundbus-objs := core.o sysfs.o
|
||||
obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += i2sbus/
|
||||
219
sound/aoa/soundbus/core.c
Normal file
219
sound/aoa/soundbus/core.c
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* soundbus
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include "soundbus.h"
|
||||
|
||||
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Apple Soundbus");
|
||||
|
||||
struct soundbus_dev *soundbus_dev_get(struct soundbus_dev *dev)
|
||||
{
|
||||
struct device *tmp;
|
||||
|
||||
if (!dev)
|
||||
return NULL;
|
||||
tmp = get_device(&dev->ofdev.dev);
|
||||
if (tmp)
|
||||
return to_soundbus_device(tmp);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(soundbus_dev_get);
|
||||
|
||||
void soundbus_dev_put(struct soundbus_dev *dev)
|
||||
{
|
||||
if (dev)
|
||||
put_device(&dev->ofdev.dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(soundbus_dev_put);
|
||||
|
||||
static int soundbus_probe(struct device *dev)
|
||||
{
|
||||
int error = -ENODEV;
|
||||
struct soundbus_driver *drv;
|
||||
struct soundbus_dev *soundbus_dev;
|
||||
|
||||
drv = to_soundbus_driver(dev->driver);
|
||||
soundbus_dev = to_soundbus_device(dev);
|
||||
|
||||
if (!drv->probe)
|
||||
return error;
|
||||
|
||||
soundbus_dev_get(soundbus_dev);
|
||||
|
||||
error = drv->probe(soundbus_dev);
|
||||
if (error)
|
||||
soundbus_dev_put(soundbus_dev);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
static int soundbus_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
struct soundbus_dev * soundbus_dev;
|
||||
struct platform_device * of;
|
||||
const char *compat;
|
||||
int retval = 0;
|
||||
int cplen, seen = 0;
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
soundbus_dev = to_soundbus_device(dev);
|
||||
if (!soundbus_dev)
|
||||
return -ENODEV;
|
||||
|
||||
of = &soundbus_dev->ofdev;
|
||||
|
||||
/* stuff we want to pass to /sbin/hotplug */
|
||||
retval = add_uevent_var(env, "OF_NAME=%s", of->dev.of_node->name);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
retval = add_uevent_var(env, "OF_TYPE=%s", of->dev.of_node->type);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
/* Since the compatible field can contain pretty much anything
|
||||
* it's not really legal to split it out with commas. We split it
|
||||
* up using a number of environment variables instead. */
|
||||
|
||||
compat = of_get_property(of->dev.of_node, "compatible", &cplen);
|
||||
while (compat && cplen > 0) {
|
||||
int tmp = env->buflen;
|
||||
retval = add_uevent_var(env, "OF_COMPATIBLE_%d=%s", seen, compat);
|
||||
if (retval)
|
||||
return retval;
|
||||
compat += env->buflen - tmp;
|
||||
cplen -= env->buflen - tmp;
|
||||
seen += 1;
|
||||
}
|
||||
|
||||
retval = add_uevent_var(env, "OF_COMPATIBLE_N=%d", seen);
|
||||
if (retval)
|
||||
return retval;
|
||||
retval = add_uevent_var(env, "MODALIAS=%s", soundbus_dev->modalias);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int soundbus_device_remove(struct device *dev)
|
||||
{
|
||||
struct soundbus_dev * soundbus_dev = to_soundbus_device(dev);
|
||||
struct soundbus_driver * drv = to_soundbus_driver(dev->driver);
|
||||
|
||||
if (dev->driver && drv->remove)
|
||||
drv->remove(soundbus_dev);
|
||||
soundbus_dev_put(soundbus_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void soundbus_device_shutdown(struct device *dev)
|
||||
{
|
||||
struct soundbus_dev * soundbus_dev = to_soundbus_device(dev);
|
||||
struct soundbus_driver * drv = to_soundbus_driver(dev->driver);
|
||||
|
||||
if (dev->driver && drv->shutdown)
|
||||
drv->shutdown(soundbus_dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int soundbus_device_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
struct soundbus_dev * soundbus_dev = to_soundbus_device(dev);
|
||||
struct soundbus_driver * drv = to_soundbus_driver(dev->driver);
|
||||
|
||||
if (dev->driver && drv->suspend)
|
||||
return drv->suspend(soundbus_dev, state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int soundbus_device_resume(struct device * dev)
|
||||
{
|
||||
struct soundbus_dev * soundbus_dev = to_soundbus_device(dev);
|
||||
struct soundbus_driver * drv = to_soundbus_driver(dev->driver);
|
||||
|
||||
if (dev->driver && drv->resume)
|
||||
return drv->resume(soundbus_dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static struct bus_type soundbus_bus_type = {
|
||||
.name = "aoa-soundbus",
|
||||
.probe = soundbus_probe,
|
||||
.uevent = soundbus_uevent,
|
||||
.remove = soundbus_device_remove,
|
||||
.shutdown = soundbus_device_shutdown,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = soundbus_device_suspend,
|
||||
.resume = soundbus_device_resume,
|
||||
#endif
|
||||
.dev_attrs = soundbus_dev_attrs,
|
||||
};
|
||||
|
||||
int soundbus_add_one(struct soundbus_dev *dev)
|
||||
{
|
||||
static int devcount;
|
||||
|
||||
/* sanity checks */
|
||||
if (!dev->attach_codec ||
|
||||
!dev->ofdev.dev.of_node ||
|
||||
dev->pcmname ||
|
||||
dev->pcmid != -1) {
|
||||
printk(KERN_ERR "soundbus: adding device failed sanity check!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_set_name(&dev->ofdev.dev, "soundbus:%x", ++devcount);
|
||||
dev->ofdev.dev.bus = &soundbus_bus_type;
|
||||
return of_device_register(&dev->ofdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(soundbus_add_one);
|
||||
|
||||
void soundbus_remove_one(struct soundbus_dev *dev)
|
||||
{
|
||||
of_device_unregister(&dev->ofdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(soundbus_remove_one);
|
||||
|
||||
int soundbus_register_driver(struct soundbus_driver *drv)
|
||||
{
|
||||
/* initialize common driver fields */
|
||||
drv->driver.name = drv->name;
|
||||
drv->driver.bus = &soundbus_bus_type;
|
||||
|
||||
/* register with core */
|
||||
return driver_register(&drv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(soundbus_register_driver);
|
||||
|
||||
void soundbus_unregister_driver(struct soundbus_driver *drv)
|
||||
{
|
||||
driver_unregister(&drv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(soundbus_unregister_driver);
|
||||
|
||||
static int __init soundbus_init(void)
|
||||
{
|
||||
return bus_register(&soundbus_bus_type);
|
||||
}
|
||||
|
||||
static void __exit soundbus_exit(void)
|
||||
{
|
||||
bus_unregister(&soundbus_bus_type);
|
||||
}
|
||||
|
||||
subsys_initcall(soundbus_init);
|
||||
module_exit(soundbus_exit);
|
||||
2
sound/aoa/soundbus/i2sbus/Makefile
Normal file
2
sound/aoa/soundbus/i2sbus/Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += snd-aoa-i2sbus.o
|
||||
snd-aoa-i2sbus-objs := core.o pcm.o control.o
|
||||
194
sound/aoa/soundbus/i2sbus/control.c
Normal file
194
sound/aoa/soundbus/i2sbus/control.c
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* i2sbus driver -- bus control routines
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/macio.h>
|
||||
#include <asm/pmac_feature.h>
|
||||
#include <asm/pmac_pfunc.h>
|
||||
#include <asm/keylargo.h>
|
||||
|
||||
#include "i2sbus.h"
|
||||
|
||||
int i2sbus_control_init(struct macio_dev* dev, struct i2sbus_control **c)
|
||||
{
|
||||
*c = kzalloc(sizeof(struct i2sbus_control), GFP_KERNEL);
|
||||
if (!*c)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&(*c)->list);
|
||||
|
||||
(*c)->macio = dev->bus->chip;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void i2sbus_control_destroy(struct i2sbus_control *c)
|
||||
{
|
||||
kfree(c);
|
||||
}
|
||||
|
||||
/* this is serialised externally */
|
||||
int i2sbus_control_add_dev(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
np = i2sdev->sound.ofdev.dev.of_node;
|
||||
i2sdev->enable = pmf_find_function(np, "enable");
|
||||
i2sdev->cell_enable = pmf_find_function(np, "cell-enable");
|
||||
i2sdev->clock_enable = pmf_find_function(np, "clock-enable");
|
||||
i2sdev->cell_disable = pmf_find_function(np, "cell-disable");
|
||||
i2sdev->clock_disable = pmf_find_function(np, "clock-disable");
|
||||
|
||||
/* if the bus number is not 0 or 1 we absolutely need to use
|
||||
* the platform functions -- there's nothing in Darwin that
|
||||
* would allow seeing a system behind what the FCRs are then,
|
||||
* and I don't want to go parsing a bunch of platform functions
|
||||
* by hand to try finding a system... */
|
||||
if (i2sdev->bus_number != 0 && i2sdev->bus_number != 1 &&
|
||||
(!i2sdev->enable ||
|
||||
!i2sdev->cell_enable || !i2sdev->clock_enable ||
|
||||
!i2sdev->cell_disable || !i2sdev->clock_disable)) {
|
||||
pmf_put_function(i2sdev->enable);
|
||||
pmf_put_function(i2sdev->cell_enable);
|
||||
pmf_put_function(i2sdev->clock_enable);
|
||||
pmf_put_function(i2sdev->cell_disable);
|
||||
pmf_put_function(i2sdev->clock_disable);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
list_add(&i2sdev->item, &c->list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void i2sbus_control_remove_dev(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev)
|
||||
{
|
||||
/* this is serialised externally */
|
||||
list_del(&i2sdev->item);
|
||||
if (list_empty(&c->list))
|
||||
i2sbus_control_destroy(c);
|
||||
}
|
||||
|
||||
int i2sbus_control_enable(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev)
|
||||
{
|
||||
struct pmf_args args = { .count = 0 };
|
||||
struct macio_chip *macio = c->macio;
|
||||
|
||||
if (i2sdev->enable)
|
||||
return pmf_call_one(i2sdev->enable, &args);
|
||||
|
||||
if (macio == NULL || macio->base == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
switch (i2sdev->bus_number) {
|
||||
case 0:
|
||||
/* these need to be locked or done through
|
||||
* newly created feature calls! */
|
||||
MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_ENABLE);
|
||||
break;
|
||||
case 1:
|
||||
MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_ENABLE);
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i2sbus_control_cell(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev,
|
||||
int enable)
|
||||
{
|
||||
struct pmf_args args = { .count = 0 };
|
||||
struct macio_chip *macio = c->macio;
|
||||
|
||||
switch (enable) {
|
||||
case 0:
|
||||
if (i2sdev->cell_disable)
|
||||
return pmf_call_one(i2sdev->cell_disable, &args);
|
||||
break;
|
||||
case 1:
|
||||
if (i2sdev->cell_enable)
|
||||
return pmf_call_one(i2sdev->cell_enable, &args);
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "i2sbus: INVALID CELL ENABLE VALUE\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (macio == NULL || macio->base == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
switch (i2sdev->bus_number) {
|
||||
case 0:
|
||||
if (enable)
|
||||
MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE);
|
||||
else
|
||||
MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE);
|
||||
break;
|
||||
case 1:
|
||||
if (enable)
|
||||
MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE);
|
||||
else
|
||||
MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE);
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i2sbus_control_clock(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev,
|
||||
int enable)
|
||||
{
|
||||
struct pmf_args args = { .count = 0 };
|
||||
struct macio_chip *macio = c->macio;
|
||||
|
||||
switch (enable) {
|
||||
case 0:
|
||||
if (i2sdev->clock_disable)
|
||||
return pmf_call_one(i2sdev->clock_disable, &args);
|
||||
break;
|
||||
case 1:
|
||||
if (i2sdev->clock_enable)
|
||||
return pmf_call_one(i2sdev->clock_enable, &args);
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "i2sbus: INVALID CLOCK ENABLE VALUE\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (macio == NULL || macio->base == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
switch (i2sdev->bus_number) {
|
||||
case 0:
|
||||
if (enable)
|
||||
MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
|
||||
else
|
||||
MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
|
||||
break;
|
||||
case 1:
|
||||
if (enable)
|
||||
MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT);
|
||||
else
|
||||
MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT);
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
463
sound/aoa/soundbus/i2sbus/core.c
Normal file
463
sound/aoa/soundbus/i2sbus/core.c
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* i2sbus driver
|
||||
*
|
||||
* Copyright 2006-2008 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
|
||||
#include <asm/macio.h>
|
||||
#include <asm/dbdma.h>
|
||||
|
||||
#include "../soundbus.h"
|
||||
#include "i2sbus.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
|
||||
MODULE_DESCRIPTION("Apple Soundbus: I2S support");
|
||||
|
||||
static int force;
|
||||
module_param(force, int, 0444);
|
||||
MODULE_PARM_DESC(force, "Force loading i2sbus even when"
|
||||
" no layout-id property is present");
|
||||
|
||||
static struct of_device_id i2sbus_match[] = {
|
||||
{ .name = "i2s" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, i2sbus_match);
|
||||
|
||||
static int alloc_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev,
|
||||
struct dbdma_command_mem *r,
|
||||
int numcmds)
|
||||
{
|
||||
/* one more for rounding, one for branch back, one for stop command */
|
||||
r->size = (numcmds + 3) * sizeof(struct dbdma_cmd);
|
||||
/* We use the PCI APIs for now until the generic one gets fixed
|
||||
* enough or until we get some macio-specific versions
|
||||
*/
|
||||
r->space = dma_zalloc_coherent(&macio_get_pci_dev(i2sdev->macio)->dev,
|
||||
r->size, &r->bus_addr, GFP_KERNEL);
|
||||
if (!r->space)
|
||||
return -ENOMEM;
|
||||
|
||||
r->cmds = (void*)DBDMA_ALIGN(r->space);
|
||||
r->bus_cmd_start = r->bus_addr +
|
||||
(dma_addr_t)((char*)r->cmds - (char*)r->space);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev,
|
||||
struct dbdma_command_mem *r)
|
||||
{
|
||||
if (!r->space) return;
|
||||
|
||||
dma_free_coherent(&macio_get_pci_dev(i2sdev->macio)->dev,
|
||||
r->size, r->space, r->bus_addr);
|
||||
}
|
||||
|
||||
static void i2sbus_release_dev(struct device *dev)
|
||||
{
|
||||
struct i2sbus_dev *i2sdev;
|
||||
int i;
|
||||
|
||||
i2sdev = container_of(dev, struct i2sbus_dev, sound.ofdev.dev);
|
||||
|
||||
if (i2sdev->intfregs) iounmap(i2sdev->intfregs);
|
||||
if (i2sdev->out.dbdma) iounmap(i2sdev->out.dbdma);
|
||||
if (i2sdev->in.dbdma) iounmap(i2sdev->in.dbdma);
|
||||
for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
|
||||
if (i2sdev->allocated_resource[i])
|
||||
release_and_free_resource(i2sdev->allocated_resource[i]);
|
||||
free_dbdma_descriptor_ring(i2sdev, &i2sdev->out.dbdma_ring);
|
||||
free_dbdma_descriptor_ring(i2sdev, &i2sdev->in.dbdma_ring);
|
||||
for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
|
||||
free_irq(i2sdev->interrupts[i], i2sdev);
|
||||
i2sbus_control_remove_dev(i2sdev->control, i2sdev);
|
||||
mutex_destroy(&i2sdev->lock);
|
||||
kfree(i2sdev);
|
||||
}
|
||||
|
||||
static irqreturn_t i2sbus_bus_intr(int irq, void *devid)
|
||||
{
|
||||
struct i2sbus_dev *dev = devid;
|
||||
u32 intreg;
|
||||
|
||||
spin_lock(&dev->low_lock);
|
||||
intreg = in_le32(&dev->intfregs->intr_ctl);
|
||||
|
||||
/* acknowledge interrupt reasons */
|
||||
out_le32(&dev->intfregs->intr_ctl, intreg);
|
||||
|
||||
spin_unlock(&dev->low_lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* XXX FIXME: We test the layout_id's here to get the proper way of
|
||||
* mapping in various registers, thanks to bugs in Apple device-trees.
|
||||
* We could instead key off the machine model and the name of the i2s
|
||||
* node (i2s-a). This we'll do when we move it all to macio_asic.c
|
||||
* and have that export items for each sub-node too.
|
||||
*/
|
||||
static int i2sbus_get_and_fixup_rsrc(struct device_node *np, int index,
|
||||
int layout, struct resource *res)
|
||||
{
|
||||
struct device_node *parent;
|
||||
int pindex, rc = -ENXIO;
|
||||
const u32 *reg;
|
||||
|
||||
/* Machines with layout 76 and 36 (K2 based) have a weird device
|
||||
* tree what we need to special case.
|
||||
* Normal machines just fetch the resource from the i2s-X node.
|
||||
* Darwin further divides normal machines into old and new layouts
|
||||
* with a subtely different code path but that doesn't seem necessary
|
||||
* in practice, they just bloated it. In addition, even on our K2
|
||||
* case the i2s-modem node, if we ever want to handle it, uses the
|
||||
* normal layout
|
||||
*/
|
||||
if (layout != 76 && layout != 36)
|
||||
return of_address_to_resource(np, index, res);
|
||||
|
||||
parent = of_get_parent(np);
|
||||
pindex = (index == aoa_resource_i2smmio) ? 0 : 1;
|
||||
rc = of_address_to_resource(parent, pindex, res);
|
||||
if (rc)
|
||||
goto bail;
|
||||
reg = of_get_property(np, "reg", NULL);
|
||||
if (reg == NULL) {
|
||||
rc = -ENXIO;
|
||||
goto bail;
|
||||
}
|
||||
res->start += reg[index * 2];
|
||||
res->end = res->start + reg[index * 2 + 1] - 1;
|
||||
bail:
|
||||
of_node_put(parent);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* FIXME: look at device node refcounting */
|
||||
static int i2sbus_add_dev(struct macio_dev *macio,
|
||||
struct i2sbus_control *control,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct i2sbus_dev *dev;
|
||||
struct device_node *child = NULL, *sound = NULL;
|
||||
struct resource *r;
|
||||
int i, layout = 0, rlen, ok = force;
|
||||
static const char *rnames[] = { "i2sbus: %s (control)",
|
||||
"i2sbus: %s (tx)",
|
||||
"i2sbus: %s (rx)" };
|
||||
static irq_handler_t ints[] = {
|
||||
i2sbus_bus_intr,
|
||||
i2sbus_tx_intr,
|
||||
i2sbus_rx_intr
|
||||
};
|
||||
|
||||
if (strlen(np->name) != 5)
|
||||
return 0;
|
||||
if (strncmp(np->name, "i2s-", 4))
|
||||
return 0;
|
||||
|
||||
dev = kzalloc(sizeof(struct i2sbus_dev), GFP_KERNEL);
|
||||
if (!dev)
|
||||
return 0;
|
||||
|
||||
i = 0;
|
||||
while ((child = of_get_next_child(np, child))) {
|
||||
if (strcmp(child->name, "sound") == 0) {
|
||||
i++;
|
||||
sound = child;
|
||||
}
|
||||
}
|
||||
if (i == 1) {
|
||||
const u32 *id = of_get_property(sound, "layout-id", NULL);
|
||||
|
||||
if (id) {
|
||||
layout = *id;
|
||||
snprintf(dev->sound.modalias, 32,
|
||||
"sound-layout-%d", layout);
|
||||
ok = 1;
|
||||
} else {
|
||||
id = of_get_property(sound, "device-id", NULL);
|
||||
/*
|
||||
* We probably cannot handle all device-id machines,
|
||||
* so restrict to those we do handle for now.
|
||||
*/
|
||||
if (id && (*id == 22 || *id == 14 || *id == 35 ||
|
||||
*id == 44)) {
|
||||
snprintf(dev->sound.modalias, 32,
|
||||
"aoa-device-id-%d", *id);
|
||||
ok = 1;
|
||||
layout = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* for the time being, until we can handle non-layout-id
|
||||
* things in some fabric, refuse to attach if there is no
|
||||
* layout-id property or we haven't been forced to attach.
|
||||
* When there are two i2s busses and only one has a layout-id,
|
||||
* then this depends on the order, but that isn't important
|
||||
* either as the second one in that case is just a modem. */
|
||||
if (!ok) {
|
||||
kfree(dev);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mutex_init(&dev->lock);
|
||||
spin_lock_init(&dev->low_lock);
|
||||
dev->sound.ofdev.archdata.dma_mask = macio->ofdev.archdata.dma_mask;
|
||||
dev->sound.ofdev.dev.of_node = np;
|
||||
dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.archdata.dma_mask;
|
||||
dev->sound.ofdev.dev.parent = &macio->ofdev.dev;
|
||||
dev->sound.ofdev.dev.release = i2sbus_release_dev;
|
||||
dev->sound.attach_codec = i2sbus_attach_codec;
|
||||
dev->sound.detach_codec = i2sbus_detach_codec;
|
||||
dev->sound.pcmid = -1;
|
||||
dev->macio = macio;
|
||||
dev->control = control;
|
||||
dev->bus_number = np->name[4] - 'a';
|
||||
INIT_LIST_HEAD(&dev->sound.codec_list);
|
||||
|
||||
for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
|
||||
dev->interrupts[i] = -1;
|
||||
snprintf(dev->rnames[i], sizeof(dev->rnames[i]),
|
||||
rnames[i], np->name);
|
||||
}
|
||||
for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
|
||||
int irq = irq_of_parse_and_map(np, i);
|
||||
if (request_irq(irq, ints[i], 0, dev->rnames[i], dev))
|
||||
goto err;
|
||||
dev->interrupts[i] = irq;
|
||||
}
|
||||
|
||||
|
||||
/* Resource handling is problematic as some device-trees contain
|
||||
* useless crap (ugh ugh ugh). We work around that here by calling
|
||||
* specific functions for calculating the appropriate resources.
|
||||
*
|
||||
* This will all be moved to macio_asic.c at one point
|
||||
*/
|
||||
for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
|
||||
if (i2sbus_get_and_fixup_rsrc(np,i,layout,&dev->resources[i]))
|
||||
goto err;
|
||||
/* If only we could use our resource dev->resources[i]...
|
||||
* but request_resource doesn't know about parents and
|
||||
* contained resources...
|
||||
*/
|
||||
dev->allocated_resource[i] =
|
||||
request_mem_region(dev->resources[i].start,
|
||||
resource_size(&dev->resources[i]),
|
||||
dev->rnames[i]);
|
||||
if (!dev->allocated_resource[i]) {
|
||||
printk(KERN_ERR "i2sbus: failed to claim resource %d!\n", i);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
r = &dev->resources[aoa_resource_i2smmio];
|
||||
rlen = resource_size(r);
|
||||
if (rlen < sizeof(struct i2s_interface_regs))
|
||||
goto err;
|
||||
dev->intfregs = ioremap(r->start, rlen);
|
||||
|
||||
r = &dev->resources[aoa_resource_txdbdma];
|
||||
rlen = resource_size(r);
|
||||
if (rlen < sizeof(struct dbdma_regs))
|
||||
goto err;
|
||||
dev->out.dbdma = ioremap(r->start, rlen);
|
||||
|
||||
r = &dev->resources[aoa_resource_rxdbdma];
|
||||
rlen = resource_size(r);
|
||||
if (rlen < sizeof(struct dbdma_regs))
|
||||
goto err;
|
||||
dev->in.dbdma = ioremap(r->start, rlen);
|
||||
|
||||
if (!dev->intfregs || !dev->out.dbdma || !dev->in.dbdma)
|
||||
goto err;
|
||||
|
||||
if (alloc_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring,
|
||||
MAX_DBDMA_COMMANDS))
|
||||
goto err;
|
||||
if (alloc_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring,
|
||||
MAX_DBDMA_COMMANDS))
|
||||
goto err;
|
||||
|
||||
if (i2sbus_control_add_dev(dev->control, dev)) {
|
||||
printk(KERN_ERR "i2sbus: control layer didn't like bus\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (soundbus_add_one(&dev->sound)) {
|
||||
printk(KERN_DEBUG "i2sbus: device registration error!\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* enable this cell */
|
||||
i2sbus_control_cell(dev->control, dev, 1);
|
||||
i2sbus_control_enable(dev->control, dev);
|
||||
i2sbus_control_clock(dev->control, dev, 1);
|
||||
|
||||
return 1;
|
||||
err:
|
||||
for (i=0;i<3;i++)
|
||||
if (dev->interrupts[i] != -1)
|
||||
free_irq(dev->interrupts[i], dev);
|
||||
free_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring);
|
||||
free_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring);
|
||||
if (dev->intfregs) iounmap(dev->intfregs);
|
||||
if (dev->out.dbdma) iounmap(dev->out.dbdma);
|
||||
if (dev->in.dbdma) iounmap(dev->in.dbdma);
|
||||
for (i=0;i<3;i++)
|
||||
if (dev->allocated_resource[i])
|
||||
release_and_free_resource(dev->allocated_resource[i]);
|
||||
mutex_destroy(&dev->lock);
|
||||
kfree(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match)
|
||||
{
|
||||
struct device_node *np = NULL;
|
||||
int got = 0, err;
|
||||
struct i2sbus_control *control = NULL;
|
||||
|
||||
err = i2sbus_control_init(dev, &control);
|
||||
if (err)
|
||||
return err;
|
||||
if (!control) {
|
||||
printk(KERN_ERR "i2sbus_control_init API breakage\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
while ((np = of_get_next_child(dev->ofdev.dev.of_node, np))) {
|
||||
if (of_device_is_compatible(np, "i2sbus") ||
|
||||
of_device_is_compatible(np, "i2s-modem")) {
|
||||
got += i2sbus_add_dev(dev, control, np);
|
||||
}
|
||||
}
|
||||
|
||||
if (!got) {
|
||||
/* found none, clean up */
|
||||
i2sbus_control_destroy(control);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&dev->ofdev.dev, control);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2sbus_remove(struct macio_dev* dev)
|
||||
{
|
||||
struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
|
||||
struct i2sbus_dev *i2sdev, *tmp;
|
||||
|
||||
list_for_each_entry_safe(i2sdev, tmp, &control->list, item)
|
||||
soundbus_remove_one(&i2sdev->sound);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state)
|
||||
{
|
||||
struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
|
||||
struct codec_info_item *cii;
|
||||
struct i2sbus_dev* i2sdev;
|
||||
int err, ret = 0;
|
||||
|
||||
list_for_each_entry(i2sdev, &control->list, item) {
|
||||
/* Notify Alsa */
|
||||
if (i2sdev->sound.pcm) {
|
||||
/* Suspend PCM streams */
|
||||
snd_pcm_suspend_all(i2sdev->sound.pcm);
|
||||
}
|
||||
|
||||
/* Notify codecs */
|
||||
list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
|
||||
err = 0;
|
||||
if (cii->codec->suspend)
|
||||
err = cii->codec->suspend(cii, state);
|
||||
if (err)
|
||||
ret = err;
|
||||
}
|
||||
|
||||
/* wait until streams are stopped */
|
||||
i2sbus_wait_for_stop_both(i2sdev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int i2sbus_resume(struct macio_dev* dev)
|
||||
{
|
||||
struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
|
||||
struct codec_info_item *cii;
|
||||
struct i2sbus_dev* i2sdev;
|
||||
int err, ret = 0;
|
||||
|
||||
list_for_each_entry(i2sdev, &control->list, item) {
|
||||
/* reset i2s bus format etc. */
|
||||
i2sbus_pcm_prepare_both(i2sdev);
|
||||
|
||||
/* Notify codecs so they can re-initialize */
|
||||
list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
|
||||
err = 0;
|
||||
if (cii->codec->resume)
|
||||
err = cii->codec->resume(cii);
|
||||
if (err)
|
||||
ret = err;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static int i2sbus_shutdown(struct macio_dev* dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct macio_driver i2sbus_drv = {
|
||||
.driver = {
|
||||
.name = "soundbus-i2s",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = i2sbus_match,
|
||||
},
|
||||
.probe = i2sbus_probe,
|
||||
.remove = i2sbus_remove,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = i2sbus_suspend,
|
||||
.resume = i2sbus_resume,
|
||||
#endif
|
||||
.shutdown = i2sbus_shutdown,
|
||||
};
|
||||
|
||||
static int __init soundbus_i2sbus_init(void)
|
||||
{
|
||||
return macio_register_driver(&i2sbus_drv);
|
||||
}
|
||||
|
||||
static void __exit soundbus_i2sbus_exit(void)
|
||||
{
|
||||
macio_unregister_driver(&i2sbus_drv);
|
||||
}
|
||||
|
||||
module_init(soundbus_i2sbus_init);
|
||||
module_exit(soundbus_i2sbus_exit);
|
||||
126
sound/aoa/soundbus/i2sbus/i2sbus.h
Normal file
126
sound/aoa/soundbus/i2sbus/i2sbus.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* i2sbus driver -- private definitions
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
#ifndef __I2SBUS_H
|
||||
#define __I2SBUS_H
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/completion.h>
|
||||
|
||||
#include <sound/pcm.h>
|
||||
|
||||
#include <asm/prom.h>
|
||||
#include <asm/pmac_feature.h>
|
||||
#include <asm/dbdma.h>
|
||||
|
||||
#include "interface.h"
|
||||
#include "../soundbus.h"
|
||||
|
||||
struct i2sbus_control {
|
||||
struct list_head list;
|
||||
struct macio_chip *macio;
|
||||
};
|
||||
|
||||
#define MAX_DBDMA_COMMANDS 32
|
||||
|
||||
struct dbdma_command_mem {
|
||||
dma_addr_t bus_addr;
|
||||
dma_addr_t bus_cmd_start;
|
||||
struct dbdma_cmd *cmds;
|
||||
void *space;
|
||||
int size;
|
||||
u32 running:1;
|
||||
u32 stopping:1;
|
||||
};
|
||||
|
||||
struct pcm_info {
|
||||
u32 created:1, /* has this direction been created with alsa? */
|
||||
active:1; /* is this stream active? */
|
||||
/* runtime information */
|
||||
struct snd_pcm_substream *substream;
|
||||
int current_period;
|
||||
u32 frame_count;
|
||||
struct dbdma_command_mem dbdma_ring;
|
||||
volatile struct dbdma_regs __iomem *dbdma;
|
||||
struct completion *stop_completion;
|
||||
};
|
||||
|
||||
enum {
|
||||
aoa_resource_i2smmio = 0,
|
||||
aoa_resource_txdbdma,
|
||||
aoa_resource_rxdbdma,
|
||||
};
|
||||
|
||||
struct i2sbus_dev {
|
||||
struct soundbus_dev sound;
|
||||
struct macio_dev *macio;
|
||||
struct i2sbus_control *control;
|
||||
volatile struct i2s_interface_regs __iomem *intfregs;
|
||||
|
||||
struct resource resources[3];
|
||||
struct resource *allocated_resource[3];
|
||||
int interrupts[3];
|
||||
char rnames[3][32];
|
||||
|
||||
/* info about currently active substreams */
|
||||
struct pcm_info out, in;
|
||||
snd_pcm_format_t format;
|
||||
unsigned int rate;
|
||||
|
||||
/* list for a single controller */
|
||||
struct list_head item;
|
||||
/* number of bus on controller */
|
||||
int bus_number;
|
||||
/* for use by control layer */
|
||||
struct pmf_function *enable,
|
||||
*cell_enable,
|
||||
*cell_disable,
|
||||
*clock_enable,
|
||||
*clock_disable;
|
||||
|
||||
/* locks */
|
||||
/* spinlock for low-level interrupt locking */
|
||||
spinlock_t low_lock;
|
||||
/* mutex for high-level consistency */
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
#define soundbus_dev_to_i2sbus_dev(sdev) \
|
||||
container_of(sdev, struct i2sbus_dev, sound)
|
||||
|
||||
/* pcm specific functions */
|
||||
extern int
|
||||
i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card,
|
||||
struct codec_info *ci, void *data);
|
||||
extern void
|
||||
i2sbus_detach_codec(struct soundbus_dev *dev, void *data);
|
||||
extern irqreturn_t
|
||||
i2sbus_tx_intr(int irq, void *devid);
|
||||
extern irqreturn_t
|
||||
i2sbus_rx_intr(int irq, void *devid);
|
||||
|
||||
extern void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev);
|
||||
extern void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev);
|
||||
|
||||
/* control specific functions */
|
||||
extern int i2sbus_control_init(struct macio_dev* dev,
|
||||
struct i2sbus_control **c);
|
||||
extern void i2sbus_control_destroy(struct i2sbus_control *c);
|
||||
extern int i2sbus_control_add_dev(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev);
|
||||
extern void i2sbus_control_remove_dev(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev);
|
||||
extern int i2sbus_control_enable(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev);
|
||||
extern int i2sbus_control_cell(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev,
|
||||
int enable);
|
||||
extern int i2sbus_control_clock(struct i2sbus_control *c,
|
||||
struct i2sbus_dev *i2sdev,
|
||||
int enable);
|
||||
#endif /* __I2SBUS_H */
|
||||
187
sound/aoa/soundbus/i2sbus/interface.h
Normal file
187
sound/aoa/soundbus/i2sbus/interface.h
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* i2sbus driver -- interface register definitions
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
#ifndef __I2SBUS_INTERFACE_H
|
||||
#define __I2SBUS_INTERFACE_H
|
||||
|
||||
/* i2s bus control registers, at least what we know about them */
|
||||
|
||||
#define __PAD(m,n) u8 __pad##m[n]
|
||||
#define _PAD(line, n) __PAD(line, n)
|
||||
#define PAD(n) _PAD(__LINE__, (n))
|
||||
struct i2s_interface_regs {
|
||||
__le32 intr_ctl; /* 0x00 */
|
||||
PAD(12);
|
||||
__le32 serial_format; /* 0x10 */
|
||||
PAD(12);
|
||||
__le32 codec_msg_out; /* 0x20 */
|
||||
PAD(12);
|
||||
__le32 codec_msg_in; /* 0x30 */
|
||||
PAD(12);
|
||||
__le32 frame_count; /* 0x40 */
|
||||
PAD(12);
|
||||
__le32 frame_match; /* 0x50 */
|
||||
PAD(12);
|
||||
__le32 data_word_sizes; /* 0x60 */
|
||||
PAD(12);
|
||||
__le32 peak_level_sel; /* 0x70 */
|
||||
PAD(12);
|
||||
__le32 peak_level_in0; /* 0x80 */
|
||||
PAD(12);
|
||||
__le32 peak_level_in1; /* 0x90 */
|
||||
PAD(12);
|
||||
/* total size: 0x100 bytes */
|
||||
} __attribute__((__packed__));
|
||||
|
||||
/* interrupt register is just a bitfield with
|
||||
* interrupt enable and pending bits */
|
||||
#define I2S_REG_INTR_CTL 0x00
|
||||
# define I2S_INT_FRAME_COUNT (1<<31)
|
||||
# define I2S_PENDING_FRAME_COUNT (1<<30)
|
||||
# define I2S_INT_MESSAGE_FLAG (1<<29)
|
||||
# define I2S_PENDING_MESSAGE_FLAG (1<<28)
|
||||
# define I2S_INT_NEW_PEAK (1<<27)
|
||||
# define I2S_PENDING_NEW_PEAK (1<<26)
|
||||
# define I2S_INT_CLOCKS_STOPPED (1<<25)
|
||||
# define I2S_PENDING_CLOCKS_STOPPED (1<<24)
|
||||
# define I2S_INT_EXTERNAL_SYNC_ERROR (1<<23)
|
||||
# define I2S_PENDING_EXTERNAL_SYNC_ERROR (1<<22)
|
||||
# define I2S_INT_EXTERNAL_SYNC_OK (1<<21)
|
||||
# define I2S_PENDING_EXTERNAL_SYNC_OK (1<<20)
|
||||
# define I2S_INT_NEW_SAMPLE_RATE (1<<19)
|
||||
# define I2S_PENDING_NEW_SAMPLE_RATE (1<<18)
|
||||
# define I2S_INT_STATUS_FLAG (1<<17)
|
||||
# define I2S_PENDING_STATUS_FLAG (1<<16)
|
||||
|
||||
/* serial format register is more interesting :)
|
||||
* It contains:
|
||||
* - clock source
|
||||
* - MClk divisor
|
||||
* - SClk divisor
|
||||
* - SClk master flag
|
||||
* - serial format (sony, i2s 64x, i2s 32x, dav, silabs)
|
||||
* - external sample frequency interrupt (don't understand)
|
||||
* - external sample frequency
|
||||
*/
|
||||
#define I2S_REG_SERIAL_FORMAT 0x10
|
||||
/* clock source. You get either 18.432, 45.1584 or 49.1520 MHz */
|
||||
# define I2S_SF_CLOCK_SOURCE_SHIFT 30
|
||||
# define I2S_SF_CLOCK_SOURCE_MASK (3<<I2S_SF_CLOCK_SOURCE_SHIFT)
|
||||
# define I2S_SF_CLOCK_SOURCE_18MHz (0<<I2S_SF_CLOCK_SOURCE_SHIFT)
|
||||
# define I2S_SF_CLOCK_SOURCE_45MHz (1<<I2S_SF_CLOCK_SOURCE_SHIFT)
|
||||
# define I2S_SF_CLOCK_SOURCE_49MHz (2<<I2S_SF_CLOCK_SOURCE_SHIFT)
|
||||
/* also, let's define the exact clock speeds here, in Hz */
|
||||
#define I2S_CLOCK_SPEED_18MHz 18432000
|
||||
#define I2S_CLOCK_SPEED_45MHz 45158400
|
||||
#define I2S_CLOCK_SPEED_49MHz 49152000
|
||||
/* MClk is the clock that drives the codec, usually called its 'system clock'.
|
||||
* It is derived by taking only every 'divisor' tick of the clock.
|
||||
*/
|
||||
# define I2S_SF_MCLKDIV_SHIFT 24
|
||||
# define I2S_SF_MCLKDIV_MASK (0x1F<<I2S_SF_MCLKDIV_SHIFT)
|
||||
# define I2S_SF_MCLKDIV_1 (0x14<<I2S_SF_MCLKDIV_SHIFT)
|
||||
# define I2S_SF_MCLKDIV_3 (0x13<<I2S_SF_MCLKDIV_SHIFT)
|
||||
# define I2S_SF_MCLKDIV_5 (0x12<<I2S_SF_MCLKDIV_SHIFT)
|
||||
# define I2S_SF_MCLKDIV_14 (0x0E<<I2S_SF_MCLKDIV_SHIFT)
|
||||
# define I2S_SF_MCLKDIV_OTHER(div) (((div/2-1)<<I2S_SF_MCLKDIV_SHIFT)&I2S_SF_MCLKDIV_MASK)
|
||||
static inline int i2s_sf_mclkdiv(int div, int *out)
|
||||
{
|
||||
int d;
|
||||
|
||||
switch(div) {
|
||||
case 1: *out |= I2S_SF_MCLKDIV_1; return 0;
|
||||
case 3: *out |= I2S_SF_MCLKDIV_3; return 0;
|
||||
case 5: *out |= I2S_SF_MCLKDIV_5; return 0;
|
||||
case 14: *out |= I2S_SF_MCLKDIV_14; return 0;
|
||||
default:
|
||||
if (div%2) return -1;
|
||||
d = div/2-1;
|
||||
if (d == 0x14 || d == 0x13 || d == 0x12 || d == 0x0E)
|
||||
return -1;
|
||||
*out |= I2S_SF_MCLKDIV_OTHER(div);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* SClk is the clock that drives the i2s wire bus. Note that it is
|
||||
* derived from the MClk above by taking only every 'divisor' tick
|
||||
* of MClk.
|
||||
*/
|
||||
# define I2S_SF_SCLKDIV_SHIFT 20
|
||||
# define I2S_SF_SCLKDIV_MASK (0xF<<I2S_SF_SCLKDIV_SHIFT)
|
||||
# define I2S_SF_SCLKDIV_1 (8<<I2S_SF_SCLKDIV_SHIFT)
|
||||
# define I2S_SF_SCLKDIV_3 (9<<I2S_SF_SCLKDIV_SHIFT)
|
||||
# define I2S_SF_SCLKDIV_OTHER(div) (((div/2-1)<<I2S_SF_SCLKDIV_SHIFT)&I2S_SF_SCLKDIV_MASK)
|
||||
static inline int i2s_sf_sclkdiv(int div, int *out)
|
||||
{
|
||||
int d;
|
||||
|
||||
switch(div) {
|
||||
case 1: *out |= I2S_SF_SCLKDIV_1; return 0;
|
||||
case 3: *out |= I2S_SF_SCLKDIV_3; return 0;
|
||||
default:
|
||||
if (div%2) return -1;
|
||||
d = div/2-1;
|
||||
if (d == 8 || d == 9) return -1;
|
||||
*out |= I2S_SF_SCLKDIV_OTHER(div);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
# define I2S_SF_SCLK_MASTER (1<<19)
|
||||
/* serial format is the way the data is put to the i2s wire bus */
|
||||
# define I2S_SF_SERIAL_FORMAT_SHIFT 16
|
||||
# define I2S_SF_SERIAL_FORMAT_MASK (7<<I2S_SF_SERIAL_FORMAT_SHIFT)
|
||||
# define I2S_SF_SERIAL_FORMAT_SONY (0<<I2S_SF_SERIAL_FORMAT_SHIFT)
|
||||
# define I2S_SF_SERIAL_FORMAT_I2S_64X (1<<I2S_SF_SERIAL_FORMAT_SHIFT)
|
||||
# define I2S_SF_SERIAL_FORMAT_I2S_32X (2<<I2S_SF_SERIAL_FORMAT_SHIFT)
|
||||
# define I2S_SF_SERIAL_FORMAT_I2S_DAV (4<<I2S_SF_SERIAL_FORMAT_SHIFT)
|
||||
# define I2S_SF_SERIAL_FORMAT_I2S_SILABS (5<<I2S_SF_SERIAL_FORMAT_SHIFT)
|
||||
/* unknown */
|
||||
# define I2S_SF_EXT_SAMPLE_FREQ_INT_SHIFT 12
|
||||
# define I2S_SF_EXT_SAMPLE_FREQ_INT_MASK (0xF<<I2S_SF_SAMPLE_FREQ_INT_SHIFT)
|
||||
/* probably gives external frequency? */
|
||||
# define I2S_SF_EXT_SAMPLE_FREQ_MASK 0xFFF
|
||||
|
||||
/* used to send codec messages, but how isn't clear */
|
||||
#define I2S_REG_CODEC_MSG_OUT 0x20
|
||||
|
||||
/* used to receive codec messages, but how isn't clear */
|
||||
#define I2S_REG_CODEC_MSG_IN 0x30
|
||||
|
||||
/* frame count reg isn't clear to me yet, but probably useful */
|
||||
#define I2S_REG_FRAME_COUNT 0x40
|
||||
|
||||
/* program to some value, and get interrupt if frame count reaches it */
|
||||
#define I2S_REG_FRAME_MATCH 0x50
|
||||
|
||||
/* this register describes how the bus transfers data */
|
||||
#define I2S_REG_DATA_WORD_SIZES 0x60
|
||||
/* number of interleaved input channels */
|
||||
# define I2S_DWS_NUM_CHANNELS_IN_SHIFT 24
|
||||
# define I2S_DWS_NUM_CHANNELS_IN_MASK (0x1F<<I2S_DWS_NUM_CHANNELS_IN_SHIFT)
|
||||
/* word size of input data */
|
||||
# define I2S_DWS_DATA_IN_SIZE_SHIFT 16
|
||||
# define I2S_DWS_DATA_IN_16BIT (0<<I2S_DWS_DATA_IN_SIZE_SHIFT)
|
||||
# define I2S_DWS_DATA_IN_24BIT (3<<I2S_DWS_DATA_IN_SIZE_SHIFT)
|
||||
/* number of interleaved output channels */
|
||||
# define I2S_DWS_NUM_CHANNELS_OUT_SHIFT 8
|
||||
# define I2S_DWS_NUM_CHANNELS_OUT_MASK (0x1F<<I2S_DWS_NUM_CHANNELS_OUT_SHIFT)
|
||||
/* word size of output data */
|
||||
# define I2S_DWS_DATA_OUT_SIZE_SHIFT 0
|
||||
# define I2S_DWS_DATA_OUT_16BIT (0<<I2S_DWS_DATA_OUT_SIZE_SHIFT)
|
||||
# define I2S_DWS_DATA_OUT_24BIT (3<<I2S_DWS_DATA_OUT_SIZE_SHIFT)
|
||||
|
||||
|
||||
/* unknown */
|
||||
#define I2S_REG_PEAK_LEVEL_SEL 0x70
|
||||
|
||||
/* unknown */
|
||||
#define I2S_REG_PEAK_LEVEL_IN0 0x80
|
||||
|
||||
/* unknown */
|
||||
#define I2S_REG_PEAK_LEVEL_IN1 0x90
|
||||
|
||||
#endif /* __I2SBUS_INTERFACE_H */
|
||||
1064
sound/aoa/soundbus/i2sbus/pcm.c
Normal file
1064
sound/aoa/soundbus/i2sbus/pcm.c
Normal file
File diff suppressed because it is too large
Load diff
204
sound/aoa/soundbus/soundbus.h
Normal file
204
sound/aoa/soundbus/soundbus.h
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* soundbus generic definitions
|
||||
*
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* GPL v2, can be found in COPYING.
|
||||
*/
|
||||
#ifndef __SOUNDBUS_H
|
||||
#define __SOUNDBUS_H
|
||||
|
||||
#include <linux/of_device.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
|
||||
/* When switching from master to slave or the other way around,
|
||||
* you don't want to have the codec chip acting as clock source
|
||||
* while the bus still is.
|
||||
* More importantly, while switch from slave to master, you need
|
||||
* to turn off the chip's master function first, but then there's
|
||||
* no clock for a while and other chips might reset, so we notify
|
||||
* their drivers after having switched.
|
||||
* The constants here are codec-point of view, so when we switch
|
||||
* the soundbus to master we tell the codec we're going to switch
|
||||
* and give it CLOCK_SWITCH_PREPARE_SLAVE!
|
||||
*/
|
||||
enum clock_switch {
|
||||
CLOCK_SWITCH_PREPARE_SLAVE,
|
||||
CLOCK_SWITCH_PREPARE_MASTER,
|
||||
CLOCK_SWITCH_SLAVE,
|
||||
CLOCK_SWITCH_MASTER,
|
||||
CLOCK_SWITCH_NOTIFY,
|
||||
};
|
||||
|
||||
/* information on a transfer the codec can take */
|
||||
struct transfer_info {
|
||||
u64 formats; /* SNDRV_PCM_FMTBIT_* */
|
||||
unsigned int rates; /* SNDRV_PCM_RATE_* */
|
||||
/* flags */
|
||||
u32 transfer_in:1, /* input = 1, output = 0 */
|
||||
must_be_clock_source:1;
|
||||
/* for codecs to distinguish among their TIs */
|
||||
int tag;
|
||||
};
|
||||
|
||||
struct codec_info_item {
|
||||
struct codec_info *codec;
|
||||
void *codec_data;
|
||||
struct soundbus_dev *sdev;
|
||||
/* internal, to be used by the soundbus provider */
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/* for prepare, where the codecs need to know
|
||||
* what we're going to drive the bus with */
|
||||
struct bus_info {
|
||||
/* see below */
|
||||
int sysclock_factor;
|
||||
int bus_factor;
|
||||
};
|
||||
|
||||
/* information on the codec itself, plus function pointers */
|
||||
struct codec_info {
|
||||
/* the module this lives in */
|
||||
struct module *owner;
|
||||
|
||||
/* supported transfer possibilities, array terminated by
|
||||
* formats or rates being 0. */
|
||||
struct transfer_info *transfers;
|
||||
|
||||
/* Master clock speed factor
|
||||
* to be used (master clock speed = sysclock_factor * sampling freq)
|
||||
* Unused if the soundbus provider has no such notion.
|
||||
*/
|
||||
int sysclock_factor;
|
||||
|
||||
/* Bus factor, bus clock speed = bus_factor * sampling freq)
|
||||
* Unused if the soundbus provider has no such notion.
|
||||
*/
|
||||
int bus_factor;
|
||||
|
||||
/* operations */
|
||||
/* clock switching, see above */
|
||||
int (*switch_clock)(struct codec_info_item *cii,
|
||||
enum clock_switch clock);
|
||||
|
||||
/* called for each transfer_info when the user
|
||||
* opens the pcm device to determine what the
|
||||
* hardware can support at this point in time.
|
||||
* That can depend on other user-switchable controls.
|
||||
* Return 1 if usable, 0 if not.
|
||||
* out points to another instance of a transfer_info
|
||||
* which is initialised to the values in *ti, and
|
||||
* it's format and rate values can be modified by
|
||||
* the callback if it is necessary to further restrict
|
||||
* the formats that can be used at the moment, for
|
||||
* example when one codec has multiple logical codec
|
||||
* info structs for multiple inputs.
|
||||
*/
|
||||
int (*usable)(struct codec_info_item *cii,
|
||||
struct transfer_info *ti,
|
||||
struct transfer_info *out);
|
||||
|
||||
/* called when pcm stream is opened, probably not implemented
|
||||
* most of the time since it isn't too useful */
|
||||
int (*open)(struct codec_info_item *cii,
|
||||
struct snd_pcm_substream *substream);
|
||||
|
||||
/* called when the pcm stream is closed, at this point
|
||||
* the user choices can all be unlocked (see below) */
|
||||
int (*close)(struct codec_info_item *cii,
|
||||
struct snd_pcm_substream *substream);
|
||||
|
||||
/* if the codec must forbid some user choices because
|
||||
* they are not valid with the substream/transfer info,
|
||||
* it must do so here. Example: no digital output for
|
||||
* incompatible framerate, say 8KHz, on Onyx.
|
||||
* If the selected stuff in the substream is NOT
|
||||
* compatible, you have to reject this call! */
|
||||
int (*prepare)(struct codec_info_item *cii,
|
||||
struct bus_info *bi,
|
||||
struct snd_pcm_substream *substream);
|
||||
|
||||
/* start() is called before data is pushed to the codec.
|
||||
* Note that start() must be atomic! */
|
||||
int (*start)(struct codec_info_item *cii,
|
||||
struct snd_pcm_substream *substream);
|
||||
|
||||
/* stop() is called after data is no longer pushed to the codec.
|
||||
* Note that stop() must be atomic! */
|
||||
int (*stop)(struct codec_info_item *cii,
|
||||
struct snd_pcm_substream *substream);
|
||||
|
||||
int (*suspend)(struct codec_info_item *cii, pm_message_t state);
|
||||
int (*resume)(struct codec_info_item *cii);
|
||||
};
|
||||
|
||||
/* information on a soundbus device */
|
||||
struct soundbus_dev {
|
||||
/* the bus it belongs to */
|
||||
struct list_head onbuslist;
|
||||
|
||||
/* the of device it represents */
|
||||
struct platform_device ofdev;
|
||||
|
||||
/* what modules go by */
|
||||
char modalias[32];
|
||||
|
||||
/* These fields must be before attach_codec can be called.
|
||||
* They should be set by the owner of the alsa card object
|
||||
* that is needed, and whoever sets them must make sure
|
||||
* that they are unique within that alsa card object. */
|
||||
char *pcmname;
|
||||
int pcmid;
|
||||
|
||||
/* this is assigned by the soundbus provider in attach_codec */
|
||||
struct snd_pcm *pcm;
|
||||
|
||||
/* operations */
|
||||
/* attach a codec to this soundbus, give the alsa
|
||||
* card object the PCMs for this soundbus should be in.
|
||||
* The 'data' pointer must be unique, it is used as the
|
||||
* key for detach_codec(). */
|
||||
int (*attach_codec)(struct soundbus_dev *dev, struct snd_card *card,
|
||||
struct codec_info *ci, void *data);
|
||||
void (*detach_codec)(struct soundbus_dev *dev, void *data);
|
||||
/* TODO: suspend/resume */
|
||||
|
||||
/* private for the soundbus provider */
|
||||
struct list_head codec_list;
|
||||
u32 have_out:1, have_in:1;
|
||||
};
|
||||
#define to_soundbus_device(d) container_of(d, struct soundbus_dev, ofdev.dev)
|
||||
#define of_to_soundbus_device(d) container_of(d, struct soundbus_dev, ofdev)
|
||||
|
||||
extern int soundbus_add_one(struct soundbus_dev *dev);
|
||||
extern void soundbus_remove_one(struct soundbus_dev *dev);
|
||||
|
||||
extern struct soundbus_dev *soundbus_dev_get(struct soundbus_dev *dev);
|
||||
extern void soundbus_dev_put(struct soundbus_dev *dev);
|
||||
|
||||
struct soundbus_driver {
|
||||
char *name;
|
||||
struct module *owner;
|
||||
|
||||
/* we don't implement any matching at all */
|
||||
|
||||
int (*probe)(struct soundbus_dev* dev);
|
||||
int (*remove)(struct soundbus_dev* dev);
|
||||
|
||||
int (*suspend)(struct soundbus_dev* dev, pm_message_t state);
|
||||
int (*resume)(struct soundbus_dev* dev);
|
||||
int (*shutdown)(struct soundbus_dev* dev);
|
||||
|
||||
struct device_driver driver;
|
||||
};
|
||||
#define to_soundbus_driver(drv) container_of(drv,struct soundbus_driver, driver)
|
||||
|
||||
extern int soundbus_register_driver(struct soundbus_driver *drv);
|
||||
extern void soundbus_unregister_driver(struct soundbus_driver *drv);
|
||||
|
||||
extern struct device_attribute soundbus_dev_attrs[];
|
||||
|
||||
#endif /* __SOUNDBUS_H */
|
||||
42
sound/aoa/soundbus/sysfs.c
Normal file
42
sound/aoa/soundbus/sysfs.c
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/stat.h>
|
||||
/* FIX UP */
|
||||
#include "soundbus.h"
|
||||
|
||||
#define soundbus_config_of_attr(field, format_string) \
|
||||
static ssize_t \
|
||||
field##_show (struct device *dev, struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct soundbus_dev *mdev = to_soundbus_device (dev); \
|
||||
return sprintf (buf, format_string, mdev->ofdev.dev.of_node->field); \
|
||||
}
|
||||
|
||||
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct soundbus_dev *sdev = to_soundbus_device(dev);
|
||||
struct platform_device *of = &sdev->ofdev;
|
||||
int length;
|
||||
|
||||
if (*sdev->modalias) {
|
||||
strlcpy(buf, sdev->modalias, sizeof(sdev->modalias) + 1);
|
||||
strcat(buf, "\n");
|
||||
length = strlen(buf);
|
||||
} else {
|
||||
length = sprintf(buf, "of:N%sT%s\n",
|
||||
of->dev.of_node->name, of->dev.of_node->type);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
soundbus_config_of_attr (name, "%s\n");
|
||||
soundbus_config_of_attr (type, "%s\n");
|
||||
|
||||
struct device_attribute soundbus_dev_attrs[] = {
|
||||
__ATTR_RO(name),
|
||||
__ATTR_RO(type),
|
||||
__ATTR_RO(modalias),
|
||||
__ATTR_NULL
|
||||
};
|
||||
43
sound/arm/Kconfig
Normal file
43
sound/arm/Kconfig
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# ALSA ARM drivers
|
||||
|
||||
menuconfig SND_ARM
|
||||
bool "ARM sound devices"
|
||||
depends on ARM
|
||||
default y
|
||||
help
|
||||
Support for sound devices specific to ARM architectures.
|
||||
Drivers that are implemented on ASoC can be found in
|
||||
"ALSA for SoC audio support" section.
|
||||
|
||||
if SND_ARM
|
||||
|
||||
config SND_ARMAACI
|
||||
tristate "ARM PrimeCell PL041 AC Link support"
|
||||
depends on ARM_AMBA
|
||||
select SND_PCM
|
||||
select SND_AC97_CODEC
|
||||
|
||||
config SND_PXA2XX_PCM
|
||||
tristate
|
||||
select SND_PCM
|
||||
|
||||
config SND_PXA2XX_LIB
|
||||
tristate
|
||||
select SND_AC97_CODEC if SND_PXA2XX_LIB_AC97
|
||||
|
||||
config SND_PXA2XX_LIB_AC97
|
||||
bool
|
||||
|
||||
config SND_PXA2XX_AC97
|
||||
tristate "AC97 driver for the Intel PXA2xx chip"
|
||||
depends on ARCH_PXA
|
||||
select SND_PXA2XX_PCM
|
||||
select SND_AC97_CODEC
|
||||
select SND_PXA2XX_LIB
|
||||
select SND_PXA2XX_LIB_AC97
|
||||
help
|
||||
Say Y or M if you want to support any AC97 codec attached to
|
||||
the PXA2xx AC97 interface.
|
||||
|
||||
endif # SND_ARM
|
||||
|
||||
16
sound/arm/Makefile
Normal file
16
sound/arm/Makefile
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
#
|
||||
|
||||
obj-$(CONFIG_SND_ARMAACI) += snd-aaci.o
|
||||
snd-aaci-objs := aaci.o
|
||||
|
||||
obj-$(CONFIG_SND_PXA2XX_PCM) += snd-pxa2xx-pcm.o
|
||||
snd-pxa2xx-pcm-objs := pxa2xx-pcm.o
|
||||
|
||||
obj-$(CONFIG_SND_PXA2XX_LIB) += snd-pxa2xx-lib.o
|
||||
snd-pxa2xx-lib-y := pxa2xx-pcm-lib.o
|
||||
snd-pxa2xx-lib-$(CONFIG_SND_PXA2XX_LIB_AC97) += pxa2xx-ac97-lib.o
|
||||
|
||||
obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o
|
||||
snd-pxa2xx-ac97-objs := pxa2xx-ac97.o
|
||||
1111
sound/arm/aaci.c
Normal file
1111
sound/arm/aaci.c
Normal file
File diff suppressed because it is too large
Load diff
250
sound/arm/aaci.h
Normal file
250
sound/arm/aaci.h
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver
|
||||
*
|
||||
* Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef AACI_H
|
||||
#define AACI_H
|
||||
|
||||
/*
|
||||
* Control and status register offsets
|
||||
* P39.
|
||||
*/
|
||||
#define AACI_CSCH1 0x000
|
||||
#define AACI_CSCH2 0x014
|
||||
#define AACI_CSCH3 0x028
|
||||
#define AACI_CSCH4 0x03c
|
||||
|
||||
#define AACI_RXCR 0x000 /* 29 bits Control Rx FIFO */
|
||||
#define AACI_TXCR 0x004 /* 17 bits Control Tx FIFO */
|
||||
#define AACI_SR 0x008 /* 12 bits Status */
|
||||
#define AACI_ISR 0x00c /* 7 bits Int Status */
|
||||
#define AACI_IE 0x010 /* 7 bits Int Enable */
|
||||
|
||||
/*
|
||||
* Other registers
|
||||
*/
|
||||
#define AACI_SL1RX 0x050
|
||||
#define AACI_SL1TX 0x054
|
||||
#define AACI_SL2RX 0x058
|
||||
#define AACI_SL2TX 0x05c
|
||||
#define AACI_SL12RX 0x060
|
||||
#define AACI_SL12TX 0x064
|
||||
#define AACI_SLFR 0x068 /* slot flags */
|
||||
#define AACI_SLISTAT 0x06c /* slot interrupt status */
|
||||
#define AACI_SLIEN 0x070 /* slot interrupt enable */
|
||||
#define AACI_INTCLR 0x074 /* interrupt clear */
|
||||
#define AACI_MAINCR 0x078 /* main control */
|
||||
#define AACI_RESET 0x07c /* reset control */
|
||||
#define AACI_SYNC 0x080 /* sync control */
|
||||
#define AACI_ALLINTS 0x084 /* all fifo interrupt status */
|
||||
#define AACI_MAINFR 0x088 /* main flag register */
|
||||
#define AACI_DR1 0x090 /* data read/written fifo 1 */
|
||||
#define AACI_DR2 0x0b0 /* data read/written fifo 2 */
|
||||
#define AACI_DR3 0x0d0 /* data read/written fifo 3 */
|
||||
#define AACI_DR4 0x0f0 /* data read/written fifo 4 */
|
||||
|
||||
/*
|
||||
* TX/RX fifo control register (CR). P48
|
||||
*/
|
||||
#define CR_FEN (1 << 16) /* fifo enable */
|
||||
#define CR_COMPACT (1 << 15) /* compact mode */
|
||||
#define CR_SZ16 (0 << 13) /* 16 bits */
|
||||
#define CR_SZ18 (1 << 13) /* 18 bits */
|
||||
#define CR_SZ20 (2 << 13) /* 20 bits */
|
||||
#define CR_SZ12 (3 << 13) /* 12 bits */
|
||||
#define CR_SL12 (1 << 12)
|
||||
#define CR_SL11 (1 << 11)
|
||||
#define CR_SL10 (1 << 10)
|
||||
#define CR_SL9 (1 << 9)
|
||||
#define CR_SL8 (1 << 8)
|
||||
#define CR_SL7 (1 << 7)
|
||||
#define CR_SL6 (1 << 6)
|
||||
#define CR_SL5 (1 << 5)
|
||||
#define CR_SL4 (1 << 4)
|
||||
#define CR_SL3 (1 << 3)
|
||||
#define CR_SL2 (1 << 2)
|
||||
#define CR_SL1 (1 << 1)
|
||||
#define CR_EN (1 << 0) /* transmit enable */
|
||||
|
||||
/*
|
||||
* status register bits. P49
|
||||
*/
|
||||
#define SR_RXTOFE (1 << 11) /* rx timeout fifo empty */
|
||||
#define SR_TXTO (1 << 10) /* rx timeout fifo nonempty */
|
||||
#define SR_TXU (1 << 9) /* tx underrun */
|
||||
#define SR_RXO (1 << 8) /* rx overrun */
|
||||
#define SR_TXB (1 << 7) /* tx busy */
|
||||
#define SR_RXB (1 << 6) /* rx busy */
|
||||
#define SR_TXFF (1 << 5) /* tx fifo full */
|
||||
#define SR_RXFF (1 << 4) /* rx fifo full */
|
||||
#define SR_TXHE (1 << 3) /* tx fifo half empty */
|
||||
#define SR_RXHF (1 << 2) /* rx fifo half full */
|
||||
#define SR_TXFE (1 << 1) /* tx fifo empty */
|
||||
#define SR_RXFE (1 << 0) /* rx fifo empty */
|
||||
|
||||
/*
|
||||
* interrupt status register bits.
|
||||
*/
|
||||
#define ISR_RXTOFEINTR (1 << 6) /* rx fifo empty */
|
||||
#define ISR_URINTR (1 << 5) /* tx underflow */
|
||||
#define ISR_ORINTR (1 << 4) /* rx overflow */
|
||||
#define ISR_RXINTR (1 << 3) /* rx fifo */
|
||||
#define ISR_TXINTR (1 << 2) /* tx fifo intr */
|
||||
#define ISR_RXTOINTR (1 << 1) /* tx timeout */
|
||||
#define ISR_TXCINTR (1 << 0) /* tx complete */
|
||||
|
||||
/*
|
||||
* interrupt enable register bits.
|
||||
*/
|
||||
#define IE_RXTOIE (1 << 6)
|
||||
#define IE_URIE (1 << 5)
|
||||
#define IE_ORIE (1 << 4)
|
||||
#define IE_RXIE (1 << 3)
|
||||
#define IE_TXIE (1 << 2)
|
||||
#define IE_RXTIE (1 << 1)
|
||||
#define IE_TXCIE (1 << 0)
|
||||
|
||||
/*
|
||||
* interrupt status. P51
|
||||
*/
|
||||
#define ISR_RXTOFE (1 << 6) /* rx timeout fifo empty */
|
||||
#define ISR_UR (1 << 5) /* tx fifo underrun */
|
||||
#define ISR_OR (1 << 4) /* rx fifo overrun */
|
||||
#define ISR_RX (1 << 3) /* rx interrupt status */
|
||||
#define ISR_TX (1 << 2) /* tx interrupt status */
|
||||
#define ISR_RXTO (1 << 1) /* rx timeout */
|
||||
#define ISR_TXC (1 << 0) /* tx complete */
|
||||
|
||||
/*
|
||||
* interrupt enable. P52
|
||||
*/
|
||||
#define IE_RXTOFE (1 << 6) /* rx timeout fifo empty */
|
||||
#define IE_UR (1 << 5) /* tx fifo underrun */
|
||||
#define IE_OR (1 << 4) /* rx fifo overrun */
|
||||
#define IE_RX (1 << 3) /* rx interrupt status */
|
||||
#define IE_TX (1 << 2) /* tx interrupt status */
|
||||
#define IE_RXTO (1 << 1) /* rx timeout */
|
||||
#define IE_TXC (1 << 0) /* tx complete */
|
||||
|
||||
/*
|
||||
* slot flag register bits. P56
|
||||
*/
|
||||
#define SLFR_RWIS (1 << 13) /* raw wake-up interrupt status */
|
||||
#define SLFR_RGPIOINTR (1 << 12) /* raw gpio interrupt */
|
||||
#define SLFR_12TXE (1 << 11) /* slot 12 tx empty */
|
||||
#define SLFR_12RXV (1 << 10) /* slot 12 rx valid */
|
||||
#define SLFR_2TXE (1 << 9) /* slot 2 tx empty */
|
||||
#define SLFR_2RXV (1 << 8) /* slot 2 rx valid */
|
||||
#define SLFR_1TXE (1 << 7) /* slot 1 tx empty */
|
||||
#define SLFR_1RXV (1 << 6) /* slot 1 rx valid */
|
||||
#define SLFR_12TXB (1 << 5) /* slot 12 tx busy */
|
||||
#define SLFR_12RXB (1 << 4) /* slot 12 rx busy */
|
||||
#define SLFR_2TXB (1 << 3) /* slot 2 tx busy */
|
||||
#define SLFR_2RXB (1 << 2) /* slot 2 rx busy */
|
||||
#define SLFR_1TXB (1 << 1) /* slot 1 tx busy */
|
||||
#define SLFR_1RXB (1 << 0) /* slot 1 rx busy */
|
||||
|
||||
/*
|
||||
* Interrupt clear register.
|
||||
*/
|
||||
#define ICLR_RXTOFEC4 (1 << 12)
|
||||
#define ICLR_RXTOFEC3 (1 << 11)
|
||||
#define ICLR_RXTOFEC2 (1 << 10)
|
||||
#define ICLR_RXTOFEC1 (1 << 9)
|
||||
#define ICLR_TXUEC4 (1 << 8)
|
||||
#define ICLR_TXUEC3 (1 << 7)
|
||||
#define ICLR_TXUEC2 (1 << 6)
|
||||
#define ICLR_TXUEC1 (1 << 5)
|
||||
#define ICLR_RXOEC4 (1 << 4)
|
||||
#define ICLR_RXOEC3 (1 << 3)
|
||||
#define ICLR_RXOEC2 (1 << 2)
|
||||
#define ICLR_RXOEC1 (1 << 1)
|
||||
#define ICLR_WISC (1 << 0)
|
||||
|
||||
/*
|
||||
* Main control register bits. P62
|
||||
*/
|
||||
#define MAINCR_SCRA(x) ((x) << 10) /* secondary codec reg access */
|
||||
#define MAINCR_DMAEN (1 << 9) /* dma enable */
|
||||
#define MAINCR_SL12TXEN (1 << 8) /* slot 12 transmit enable */
|
||||
#define MAINCR_SL12RXEN (1 << 7) /* slot 12 receive enable */
|
||||
#define MAINCR_SL2TXEN (1 << 6) /* slot 2 transmit enable */
|
||||
#define MAINCR_SL2RXEN (1 << 5) /* slot 2 receive enable */
|
||||
#define MAINCR_SL1TXEN (1 << 4) /* slot 1 transmit enable */
|
||||
#define MAINCR_SL1RXEN (1 << 3) /* slot 1 receive enable */
|
||||
#define MAINCR_LPM (1 << 2) /* low power mode */
|
||||
#define MAINCR_LOOPBK (1 << 1) /* loopback */
|
||||
#define MAINCR_IE (1 << 0) /* aaci interface enable */
|
||||
|
||||
/*
|
||||
* Reset register bits. P65
|
||||
*/
|
||||
#define RESET_NRST (1 << 0)
|
||||
|
||||
/*
|
||||
* Sync register bits. P65
|
||||
*/
|
||||
#define SYNC_FORCE (1 << 0)
|
||||
|
||||
/*
|
||||
* Main flag register bits. P66
|
||||
*/
|
||||
#define MAINFR_TXB (1 << 1) /* transmit busy */
|
||||
#define MAINFR_RXB (1 << 0) /* receive busy */
|
||||
|
||||
|
||||
|
||||
struct aaci_runtime {
|
||||
void __iomem *base;
|
||||
void __iomem *fifo;
|
||||
spinlock_t lock;
|
||||
|
||||
struct ac97_pcm *pcm;
|
||||
int pcm_open;
|
||||
|
||||
u32 cr;
|
||||
struct snd_pcm_substream *substream;
|
||||
|
||||
unsigned int period; /* byte size of a "period" */
|
||||
|
||||
/*
|
||||
* PIO support
|
||||
*/
|
||||
void *start;
|
||||
void *end;
|
||||
void *ptr;
|
||||
int bytes;
|
||||
unsigned int fifo_bytes;
|
||||
};
|
||||
|
||||
struct aaci {
|
||||
struct amba_device *dev;
|
||||
struct snd_card *card;
|
||||
void __iomem *base;
|
||||
unsigned int fifo_depth;
|
||||
unsigned int users;
|
||||
struct mutex irq_lock;
|
||||
|
||||
/* AC'97 */
|
||||
struct mutex ac97_sem;
|
||||
struct snd_ac97_bus *ac97_bus;
|
||||
struct snd_ac97 *ac97;
|
||||
|
||||
u32 maincr;
|
||||
|
||||
struct aaci_runtime playback;
|
||||
struct aaci_runtime capture;
|
||||
|
||||
struct snd_pcm *pcm;
|
||||
};
|
||||
|
||||
#define ACSTREAM_FRONT 0
|
||||
#define ACSTREAM_SURROUND 1
|
||||
#define ACSTREAM_LFE 2
|
||||
|
||||
#endif
|
||||
415
sound/arm/pxa2xx-ac97-lib.c
Normal file
415
sound/arm/pxa2xx-ac97-lib.c
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
* Based on sound/arm/pxa2xx-ac97.c and sound/soc/pxa/pxa2xx-ac97.c
|
||||
* which contain:
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: Dec 02, 2004
|
||||
* Copyright: MontaVista Software Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include <sound/ac97_codec.h>
|
||||
#include <sound/pxa2xx-lib.h>
|
||||
|
||||
#include <mach/irqs.h>
|
||||
#include <mach/regs-ac97.h>
|
||||
#include <mach/audio.h>
|
||||
|
||||
static DEFINE_MUTEX(car_mutex);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(gsr_wq);
|
||||
static volatile long gsr_bits;
|
||||
static struct clk *ac97_clk;
|
||||
static struct clk *ac97conf_clk;
|
||||
static int reset_gpio;
|
||||
|
||||
extern void pxa27x_configure_ac97reset(int reset_gpio, bool to_gpio);
|
||||
|
||||
/*
|
||||
* Beware PXA27x bugs:
|
||||
*
|
||||
* o Slot 12 read from modem space will hang controller.
|
||||
* o CDONE, SDONE interrupt fails after any slot 12 IO.
|
||||
*
|
||||
* We therefore have an hybrid approach for waiting on SDONE (interrupt or
|
||||
* 1 jiffy timeout if interrupt never comes).
|
||||
*/
|
||||
|
||||
unsigned short pxa2xx_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
|
||||
{
|
||||
unsigned short val = -1;
|
||||
volatile u32 *reg_addr;
|
||||
|
||||
mutex_lock(&car_mutex);
|
||||
|
||||
/* set up primary or secondary codec space */
|
||||
if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS)
|
||||
reg_addr = ac97->num ? &SMC_REG_BASE : &PMC_REG_BASE;
|
||||
else
|
||||
reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE;
|
||||
reg_addr += (reg >> 1);
|
||||
|
||||
/* start read access across the ac97 link */
|
||||
GSR = GSR_CDONE | GSR_SDONE;
|
||||
gsr_bits = 0;
|
||||
val = *reg_addr;
|
||||
if (reg == AC97_GPIO_STATUS)
|
||||
goto out;
|
||||
if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1) <= 0 &&
|
||||
!((GSR | gsr_bits) & GSR_SDONE)) {
|
||||
printk(KERN_ERR "%s: read error (ac97_reg=%d GSR=%#lx)\n",
|
||||
__func__, reg, GSR | gsr_bits);
|
||||
val = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* valid data now */
|
||||
GSR = GSR_CDONE | GSR_SDONE;
|
||||
gsr_bits = 0;
|
||||
val = *reg_addr;
|
||||
/* but we've just started another cycle... */
|
||||
wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1);
|
||||
|
||||
out: mutex_unlock(&car_mutex);
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pxa2xx_ac97_read);
|
||||
|
||||
void pxa2xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
||||
unsigned short val)
|
||||
{
|
||||
volatile u32 *reg_addr;
|
||||
|
||||
mutex_lock(&car_mutex);
|
||||
|
||||
/* set up primary or secondary codec space */
|
||||
if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS)
|
||||
reg_addr = ac97->num ? &SMC_REG_BASE : &PMC_REG_BASE;
|
||||
else
|
||||
reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE;
|
||||
reg_addr += (reg >> 1);
|
||||
|
||||
GSR = GSR_CDONE | GSR_SDONE;
|
||||
gsr_bits = 0;
|
||||
*reg_addr = val;
|
||||
if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_CDONE, 1) <= 0 &&
|
||||
!((GSR | gsr_bits) & GSR_CDONE))
|
||||
printk(KERN_ERR "%s: write error (ac97_reg=%d GSR=%#lx)\n",
|
||||
__func__, reg, GSR | gsr_bits);
|
||||
|
||||
mutex_unlock(&car_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pxa2xx_ac97_write);
|
||||
|
||||
#ifdef CONFIG_PXA25x
|
||||
static inline void pxa_ac97_warm_pxa25x(void)
|
||||
{
|
||||
gsr_bits = 0;
|
||||
|
||||
GCR |= GCR_WARM_RST;
|
||||
}
|
||||
|
||||
static inline void pxa_ac97_cold_pxa25x(void)
|
||||
{
|
||||
GCR &= GCR_COLD_RST; /* clear everything but nCRST */
|
||||
GCR &= ~GCR_COLD_RST; /* then assert nCRST */
|
||||
|
||||
gsr_bits = 0;
|
||||
|
||||
GCR = GCR_COLD_RST;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PXA27x
|
||||
static inline void pxa_ac97_warm_pxa27x(void)
|
||||
{
|
||||
gsr_bits = 0;
|
||||
|
||||
/* warm reset broken on Bulverde, so manually keep AC97 reset high */
|
||||
pxa27x_configure_ac97reset(reset_gpio, true);
|
||||
udelay(10);
|
||||
GCR |= GCR_WARM_RST;
|
||||
pxa27x_configure_ac97reset(reset_gpio, false);
|
||||
udelay(500);
|
||||
}
|
||||
|
||||
static inline void pxa_ac97_cold_pxa27x(void)
|
||||
{
|
||||
GCR &= GCR_COLD_RST; /* clear everything but nCRST */
|
||||
GCR &= ~GCR_COLD_RST; /* then assert nCRST */
|
||||
|
||||
gsr_bits = 0;
|
||||
|
||||
/* PXA27x Developers Manual section 13.5.2.2.1 */
|
||||
clk_prepare_enable(ac97conf_clk);
|
||||
udelay(5);
|
||||
clk_disable_unprepare(ac97conf_clk);
|
||||
GCR = GCR_COLD_RST | GCR_WARM_RST;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PXA3xx
|
||||
static inline void pxa_ac97_warm_pxa3xx(void)
|
||||
{
|
||||
gsr_bits = 0;
|
||||
|
||||
/* Can't use interrupts */
|
||||
GCR |= GCR_WARM_RST;
|
||||
}
|
||||
|
||||
static inline void pxa_ac97_cold_pxa3xx(void)
|
||||
{
|
||||
/* Hold CLKBPB for 100us */
|
||||
GCR = 0;
|
||||
GCR = GCR_CLKBPB;
|
||||
udelay(100);
|
||||
GCR = 0;
|
||||
|
||||
GCR &= GCR_COLD_RST; /* clear everything but nCRST */
|
||||
GCR &= ~GCR_COLD_RST; /* then assert nCRST */
|
||||
|
||||
gsr_bits = 0;
|
||||
|
||||
/* Can't use interrupts on PXA3xx */
|
||||
GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN);
|
||||
|
||||
GCR = GCR_WARM_RST | GCR_COLD_RST;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool pxa2xx_ac97_try_warm_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
unsigned long gsr;
|
||||
unsigned int timeout = 100;
|
||||
|
||||
#ifdef CONFIG_PXA25x
|
||||
if (cpu_is_pxa25x())
|
||||
pxa_ac97_warm_pxa25x();
|
||||
else
|
||||
#endif
|
||||
#ifdef CONFIG_PXA27x
|
||||
if (cpu_is_pxa27x())
|
||||
pxa_ac97_warm_pxa27x();
|
||||
else
|
||||
#endif
|
||||
#ifdef CONFIG_PXA3xx
|
||||
if (cpu_is_pxa3xx())
|
||||
pxa_ac97_warm_pxa3xx();
|
||||
else
|
||||
#endif
|
||||
snd_BUG();
|
||||
|
||||
while (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--)
|
||||
mdelay(1);
|
||||
|
||||
gsr = GSR | gsr_bits;
|
||||
if (!(gsr & (GSR_PCR | GSR_SCR))) {
|
||||
printk(KERN_INFO "%s: warm reset timeout (GSR=%#lx)\n",
|
||||
__func__, gsr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pxa2xx_ac97_try_warm_reset);
|
||||
|
||||
bool pxa2xx_ac97_try_cold_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
unsigned long gsr;
|
||||
unsigned int timeout = 1000;
|
||||
|
||||
#ifdef CONFIG_PXA25x
|
||||
if (cpu_is_pxa25x())
|
||||
pxa_ac97_cold_pxa25x();
|
||||
else
|
||||
#endif
|
||||
#ifdef CONFIG_PXA27x
|
||||
if (cpu_is_pxa27x())
|
||||
pxa_ac97_cold_pxa27x();
|
||||
else
|
||||
#endif
|
||||
#ifdef CONFIG_PXA3xx
|
||||
if (cpu_is_pxa3xx())
|
||||
pxa_ac97_cold_pxa3xx();
|
||||
else
|
||||
#endif
|
||||
snd_BUG();
|
||||
|
||||
while (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--)
|
||||
mdelay(1);
|
||||
|
||||
gsr = GSR | gsr_bits;
|
||||
if (!(gsr & (GSR_PCR | GSR_SCR))) {
|
||||
printk(KERN_INFO "%s: cold reset timeout (GSR=%#lx)\n",
|
||||
__func__, gsr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pxa2xx_ac97_try_cold_reset);
|
||||
|
||||
|
||||
void pxa2xx_ac97_finish_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN);
|
||||
GCR |= GCR_SDONE_IE|GCR_CDONE_IE;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pxa2xx_ac97_finish_reset);
|
||||
|
||||
static irqreturn_t pxa2xx_ac97_irq(int irq, void *dev_id)
|
||||
{
|
||||
long status;
|
||||
|
||||
status = GSR;
|
||||
if (status) {
|
||||
GSR = status;
|
||||
gsr_bits |= status;
|
||||
wake_up(&gsr_wq);
|
||||
|
||||
/* Although we don't use those we still need to clear them
|
||||
since they tend to spuriously trigger when MMC is used
|
||||
(hardware bug? go figure)... */
|
||||
if (cpu_is_pxa27x()) {
|
||||
MISR = MISR_EOC;
|
||||
PISR = PISR_EOC;
|
||||
MCSR = MCSR_EOC;
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
int pxa2xx_ac97_hw_suspend(void)
|
||||
{
|
||||
GCR |= GCR_ACLINK_OFF;
|
||||
clk_disable_unprepare(ac97_clk);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_suspend);
|
||||
|
||||
int pxa2xx_ac97_hw_resume(void)
|
||||
{
|
||||
clk_prepare_enable(ac97_clk);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_resume);
|
||||
#endif
|
||||
|
||||
int pxa2xx_ac97_hw_probe(struct platform_device *dev)
|
||||
{
|
||||
int ret;
|
||||
pxa2xx_audio_ops_t *pdata = dev->dev.platform_data;
|
||||
|
||||
if (pdata) {
|
||||
switch (pdata->reset_gpio) {
|
||||
case 95:
|
||||
case 113:
|
||||
reset_gpio = pdata->reset_gpio;
|
||||
break;
|
||||
case 0:
|
||||
reset_gpio = 113;
|
||||
break;
|
||||
case -1:
|
||||
break;
|
||||
default:
|
||||
dev_err(&dev->dev, "Invalid reset GPIO %d\n",
|
||||
pdata->reset_gpio);
|
||||
}
|
||||
} else {
|
||||
if (cpu_is_pxa27x())
|
||||
reset_gpio = 113;
|
||||
}
|
||||
|
||||
if (cpu_is_pxa27x()) {
|
||||
/*
|
||||
* This gpio is needed for a work-around to a bug in the ac97
|
||||
* controller during warm reset. The direction and level is set
|
||||
* here so that it is an output driven high when switching from
|
||||
* AC97_nRESET alt function to generic gpio.
|
||||
*/
|
||||
ret = gpio_request_one(reset_gpio, GPIOF_OUT_INIT_HIGH,
|
||||
"pxa27x ac97 reset");
|
||||
if (ret < 0) {
|
||||
pr_err("%s: gpio_request_one() failed: %d\n",
|
||||
__func__, ret);
|
||||
goto err_conf;
|
||||
}
|
||||
pxa27x_configure_ac97reset(reset_gpio, false);
|
||||
|
||||
ac97conf_clk = clk_get(&dev->dev, "AC97CONFCLK");
|
||||
if (IS_ERR(ac97conf_clk)) {
|
||||
ret = PTR_ERR(ac97conf_clk);
|
||||
ac97conf_clk = NULL;
|
||||
goto err_conf;
|
||||
}
|
||||
}
|
||||
|
||||
ac97_clk = clk_get(&dev->dev, "AC97CLK");
|
||||
if (IS_ERR(ac97_clk)) {
|
||||
ret = PTR_ERR(ac97_clk);
|
||||
ac97_clk = NULL;
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(ac97_clk);
|
||||
if (ret)
|
||||
goto err_clk2;
|
||||
|
||||
ret = request_irq(IRQ_AC97, pxa2xx_ac97_irq, 0, "AC97", NULL);
|
||||
if (ret < 0)
|
||||
goto err_irq;
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq:
|
||||
GCR |= GCR_ACLINK_OFF;
|
||||
err_clk2:
|
||||
clk_put(ac97_clk);
|
||||
ac97_clk = NULL;
|
||||
err_clk:
|
||||
if (ac97conf_clk) {
|
||||
clk_put(ac97conf_clk);
|
||||
ac97conf_clk = NULL;
|
||||
}
|
||||
err_conf:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_probe);
|
||||
|
||||
void pxa2xx_ac97_hw_remove(struct platform_device *dev)
|
||||
{
|
||||
if (cpu_is_pxa27x())
|
||||
gpio_free(reset_gpio);
|
||||
GCR |= GCR_ACLINK_OFF;
|
||||
free_irq(IRQ_AC97, NULL);
|
||||
if (ac97conf_clk) {
|
||||
clk_put(ac97conf_clk);
|
||||
ac97conf_clk = NULL;
|
||||
}
|
||||
clk_disable_unprepare(ac97_clk);
|
||||
clk_put(ac97_clk);
|
||||
ac97_clk = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_remove);
|
||||
|
||||
MODULE_AUTHOR("Nicolas Pitre");
|
||||
MODULE_DESCRIPTION("Intel/Marvell PXA sound library");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
256
sound/arm/pxa2xx-ac97.c
Normal file
256
sound/arm/pxa2xx-ac97.c
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip.
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: Dec 02, 2004
|
||||
* Copyright: MontaVista Software Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dmaengine.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/ac97_codec.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pxa2xx-lib.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
#include <mach/regs-ac97.h>
|
||||
#include <mach/audio.h>
|
||||
|
||||
#include "pxa2xx-pcm.h"
|
||||
|
||||
static void pxa2xx_ac97_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
if (!pxa2xx_ac97_try_cold_reset(ac97)) {
|
||||
pxa2xx_ac97_try_warm_reset(ac97);
|
||||
}
|
||||
|
||||
pxa2xx_ac97_finish_reset(ac97);
|
||||
}
|
||||
|
||||
static struct snd_ac97_bus_ops pxa2xx_ac97_ops = {
|
||||
.read = pxa2xx_ac97_read,
|
||||
.write = pxa2xx_ac97_write,
|
||||
.reset = pxa2xx_ac97_reset,
|
||||
};
|
||||
|
||||
static unsigned long pxa2xx_ac97_pcm_out_req = 12;
|
||||
static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_out = {
|
||||
.addr = __PREG(PCDR),
|
||||
.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
|
||||
.maxburst = 32,
|
||||
.filter_data = &pxa2xx_ac97_pcm_out_req,
|
||||
};
|
||||
|
||||
static unsigned long pxa2xx_ac97_pcm_in_req = 11;
|
||||
static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_in = {
|
||||
.addr = __PREG(PCDR),
|
||||
.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
|
||||
.maxburst = 32,
|
||||
.filter_data = &pxa2xx_ac97_pcm_in_req,
|
||||
};
|
||||
|
||||
static struct snd_pcm *pxa2xx_ac97_pcm;
|
||||
static struct snd_ac97 *pxa2xx_ac97_ac97;
|
||||
|
||||
static int pxa2xx_ac97_pcm_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
pxa2xx_audio_ops_t *platform_ops;
|
||||
int r;
|
||||
|
||||
runtime->hw.channels_min = 2;
|
||||
runtime->hw.channels_max = 2;
|
||||
|
||||
r = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
AC97_RATES_FRONT_DAC : AC97_RATES_ADC;
|
||||
runtime->hw.rates = pxa2xx_ac97_ac97->rates[r];
|
||||
snd_pcm_limit_hw_rates(runtime);
|
||||
|
||||
platform_ops = substream->pcm->card->dev->platform_data;
|
||||
if (platform_ops && platform_ops->startup)
|
||||
return platform_ops->startup(substream, platform_ops->priv);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pxa2xx_ac97_pcm_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
pxa2xx_audio_ops_t *platform_ops;
|
||||
|
||||
platform_ops = substream->pcm->card->dev->platform_data;
|
||||
if (platform_ops && platform_ops->shutdown)
|
||||
platform_ops->shutdown(substream, platform_ops->priv);
|
||||
}
|
||||
|
||||
static int pxa2xx_ac97_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
|
||||
return snd_ac97_set_rate(pxa2xx_ac97_ac97, reg, runtime->rate);
|
||||
}
|
||||
|
||||
static struct pxa2xx_pcm_client pxa2xx_ac97_pcm_client = {
|
||||
.playback_params = &pxa2xx_ac97_pcm_out,
|
||||
.capture_params = &pxa2xx_ac97_pcm_in,
|
||||
.startup = pxa2xx_ac97_pcm_startup,
|
||||
.shutdown = pxa2xx_ac97_pcm_shutdown,
|
||||
.prepare = pxa2xx_ac97_pcm_prepare,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
||||
static int pxa2xx_ac97_do_suspend(struct snd_card *card)
|
||||
{
|
||||
pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data;
|
||||
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3cold);
|
||||
snd_pcm_suspend_all(pxa2xx_ac97_pcm);
|
||||
snd_ac97_suspend(pxa2xx_ac97_ac97);
|
||||
if (platform_ops && platform_ops->suspend)
|
||||
platform_ops->suspend(platform_ops->priv);
|
||||
|
||||
return pxa2xx_ac97_hw_suspend();
|
||||
}
|
||||
|
||||
static int pxa2xx_ac97_do_resume(struct snd_card *card)
|
||||
{
|
||||
pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data;
|
||||
int rc;
|
||||
|
||||
rc = pxa2xx_ac97_hw_resume();
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (platform_ops && platform_ops->resume)
|
||||
platform_ops->resume(platform_ops->priv);
|
||||
snd_ac97_resume(pxa2xx_ac97_ac97);
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pxa2xx_ac97_suspend(struct device *dev)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (card)
|
||||
ret = pxa2xx_ac97_do_suspend(card);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pxa2xx_ac97_resume(struct device *dev)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (card)
|
||||
ret = pxa2xx_ac97_do_resume(card);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(pxa2xx_ac97_pm_ops, pxa2xx_ac97_suspend, pxa2xx_ac97_resume);
|
||||
#endif
|
||||
|
||||
static int pxa2xx_ac97_probe(struct platform_device *dev)
|
||||
{
|
||||
struct snd_card *card;
|
||||
struct snd_ac97_bus *ac97_bus;
|
||||
struct snd_ac97_template ac97_template;
|
||||
int ret;
|
||||
pxa2xx_audio_ops_t *pdata = dev->dev.platform_data;
|
||||
|
||||
if (dev->id >= 0) {
|
||||
dev_err(&dev->dev, "PXA2xx has only one AC97 port.\n");
|
||||
ret = -ENXIO;
|
||||
goto err_dev;
|
||||
}
|
||||
|
||||
ret = snd_card_new(&dev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
|
||||
THIS_MODULE, 0, &card);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
strlcpy(card->driver, dev->dev.driver->name, sizeof(card->driver));
|
||||
|
||||
ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = pxa2xx_ac97_hw_probe(dev);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus);
|
||||
if (ret)
|
||||
goto err_remove;
|
||||
memset(&ac97_template, 0, sizeof(ac97_template));
|
||||
ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97);
|
||||
if (ret)
|
||||
goto err_remove;
|
||||
|
||||
snprintf(card->shortname, sizeof(card->shortname),
|
||||
"%s", snd_ac97_get_short_name(pxa2xx_ac97_ac97));
|
||||
snprintf(card->longname, sizeof(card->longname),
|
||||
"%s (%s)", dev->dev.driver->name, card->mixername);
|
||||
|
||||
if (pdata && pdata->codec_pdata[0])
|
||||
snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata[0]);
|
||||
ret = snd_card_register(card);
|
||||
if (ret == 0) {
|
||||
platform_set_drvdata(dev, card);
|
||||
return 0;
|
||||
}
|
||||
|
||||
err_remove:
|
||||
pxa2xx_ac97_hw_remove(dev);
|
||||
err:
|
||||
if (card)
|
||||
snd_card_free(card);
|
||||
err_dev:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pxa2xx_ac97_remove(struct platform_device *dev)
|
||||
{
|
||||
struct snd_card *card = platform_get_drvdata(dev);
|
||||
|
||||
if (card) {
|
||||
snd_card_free(card);
|
||||
pxa2xx_ac97_hw_remove(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pxa2xx_ac97_driver = {
|
||||
.probe = pxa2xx_ac97_probe,
|
||||
.remove = pxa2xx_ac97_remove,
|
||||
.driver = {
|
||||
.name = "pxa2xx-ac97",
|
||||
.owner = THIS_MODULE,
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
.pm = &pxa2xx_ac97_pm_ops,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(pxa2xx_ac97_driver);
|
||||
|
||||
MODULE_AUTHOR("Nicolas Pitre");
|
||||
MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:pxa2xx-ac97");
|
||||
321
sound/arm/pxa2xx-pcm-lib.c
Normal file
321
sound/arm/pxa2xx-pcm-lib.c
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/pxa2xx-lib.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
#include <mach/dma.h>
|
||||
|
||||
#include "pxa2xx-pcm.h"
|
||||
|
||||
static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE,
|
||||
.period_bytes_min = 32,
|
||||
.period_bytes_max = 8192 - 32,
|
||||
.periods_min = 1,
|
||||
.periods_max = PAGE_SIZE/sizeof(pxa_dma_desc),
|
||||
.buffer_bytes_max = 128 * 1024,
|
||||
.fifo_size = 32,
|
||||
};
|
||||
|
||||
int __pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct pxa2xx_runtime_data *rtd = runtime->private_data;
|
||||
size_t totsize = params_buffer_bytes(params);
|
||||
size_t period = params_period_bytes(params);
|
||||
pxa_dma_desc *dma_desc;
|
||||
dma_addr_t dma_buff_phys, next_desc_phys;
|
||||
u32 dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG;
|
||||
|
||||
/* temporary transition hack */
|
||||
switch (rtd->params->addr_width) {
|
||||
case DMA_SLAVE_BUSWIDTH_1_BYTE:
|
||||
dcmd |= DCMD_WIDTH1;
|
||||
break;
|
||||
case DMA_SLAVE_BUSWIDTH_2_BYTES:
|
||||
dcmd |= DCMD_WIDTH2;
|
||||
break;
|
||||
case DMA_SLAVE_BUSWIDTH_4_BYTES:
|
||||
dcmd |= DCMD_WIDTH4;
|
||||
break;
|
||||
default:
|
||||
/* can't happen */
|
||||
break;
|
||||
}
|
||||
|
||||
switch (rtd->params->maxburst) {
|
||||
case 8:
|
||||
dcmd |= DCMD_BURST8;
|
||||
break;
|
||||
case 16:
|
||||
dcmd |= DCMD_BURST16;
|
||||
break;
|
||||
case 32:
|
||||
dcmd |= DCMD_BURST32;
|
||||
break;
|
||||
}
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
runtime->dma_bytes = totsize;
|
||||
|
||||
dma_desc = rtd->dma_desc_array;
|
||||
next_desc_phys = rtd->dma_desc_array_phys;
|
||||
dma_buff_phys = runtime->dma_addr;
|
||||
do {
|
||||
next_desc_phys += sizeof(pxa_dma_desc);
|
||||
dma_desc->ddadr = next_desc_phys;
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
dma_desc->dsadr = dma_buff_phys;
|
||||
dma_desc->dtadr = rtd->params->addr;
|
||||
} else {
|
||||
dma_desc->dsadr = rtd->params->addr;
|
||||
dma_desc->dtadr = dma_buff_phys;
|
||||
}
|
||||
if (period > totsize)
|
||||
period = totsize;
|
||||
dma_desc->dcmd = dcmd | period | DCMD_ENDIRQEN;
|
||||
dma_desc++;
|
||||
dma_buff_phys += period;
|
||||
} while (totsize -= period);
|
||||
dma_desc[-1].ddadr = rtd->dma_desc_array_phys;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(__pxa2xx_pcm_hw_params);
|
||||
|
||||
int __pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct pxa2xx_runtime_data *rtd = substream->runtime->private_data;
|
||||
|
||||
if (rtd && rtd->params && rtd->params->filter_data) {
|
||||
unsigned long req = *(unsigned long *) rtd->params->filter_data;
|
||||
DRCMR(req) = 0;
|
||||
}
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(__pxa2xx_pcm_hw_free);
|
||||
|
||||
int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys;
|
||||
DCSR(prtd->dma_ch) = DCSR_RUN;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
DCSR(prtd->dma_ch) &= ~DCSR_RUN;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
DCSR(prtd->dma_ch) |= DCSR_RUN;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys;
|
||||
DCSR(prtd->dma_ch) |= DCSR_RUN;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_trigger);
|
||||
|
||||
snd_pcm_uframes_t
|
||||
pxa2xx_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct pxa2xx_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
dma_addr_t ptr = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
DSADR(prtd->dma_ch) : DTADR(prtd->dma_ch);
|
||||
snd_pcm_uframes_t x = bytes_to_frames(runtime, ptr - runtime->dma_addr);
|
||||
|
||||
if (x == runtime->buffer_size)
|
||||
x = 0;
|
||||
return x;
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_pointer);
|
||||
|
||||
int __pxa2xx_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
|
||||
unsigned long req;
|
||||
|
||||
if (!prtd || !prtd->params)
|
||||
return 0;
|
||||
|
||||
if (prtd->dma_ch == -1)
|
||||
return -EINVAL;
|
||||
|
||||
DCSR(prtd->dma_ch) &= ~DCSR_RUN;
|
||||
DCSR(prtd->dma_ch) = 0;
|
||||
DCMD(prtd->dma_ch) = 0;
|
||||
req = *(unsigned long *) prtd->params->filter_data;
|
||||
DRCMR(req) = prtd->dma_ch | DRCMR_MAPVLD;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(__pxa2xx_pcm_prepare);
|
||||
|
||||
void pxa2xx_pcm_dma_irq(int dma_ch, void *dev_id)
|
||||
{
|
||||
struct snd_pcm_substream *substream = dev_id;
|
||||
int dcsr;
|
||||
|
||||
dcsr = DCSR(dma_ch);
|
||||
DCSR(dma_ch) = dcsr & ~DCSR_STOPIRQEN;
|
||||
|
||||
if (dcsr & DCSR_ENDINTR) {
|
||||
snd_pcm_period_elapsed(substream);
|
||||
} else {
|
||||
printk(KERN_ERR "DMA error on channel %d (DCSR=%#x)\n",
|
||||
dma_ch, dcsr);
|
||||
snd_pcm_stream_lock(substream);
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
snd_pcm_stream_unlock(substream);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_dma_irq);
|
||||
|
||||
int __pxa2xx_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct pxa2xx_runtime_data *rtd;
|
||||
int ret;
|
||||
|
||||
runtime->hw = pxa2xx_pcm_hardware;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = -ENOMEM;
|
||||
rtd = kzalloc(sizeof(*rtd), GFP_KERNEL);
|
||||
if (!rtd)
|
||||
goto out;
|
||||
rtd->dma_desc_array =
|
||||
dma_alloc_writecombine(substream->pcm->card->dev, PAGE_SIZE,
|
||||
&rtd->dma_desc_array_phys, GFP_KERNEL);
|
||||
if (!rtd->dma_desc_array)
|
||||
goto err1;
|
||||
|
||||
rtd->dma_ch = -1;
|
||||
runtime->private_data = rtd;
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
kfree(rtd);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(__pxa2xx_pcm_open);
|
||||
|
||||
int __pxa2xx_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct pxa2xx_runtime_data *rtd = runtime->private_data;
|
||||
|
||||
dma_free_writecombine(substream->pcm->card->dev, PAGE_SIZE,
|
||||
rtd->dma_desc_array, rtd->dma_desc_array_phys);
|
||||
kfree(rtd);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(__pxa2xx_pcm_close);
|
||||
|
||||
int pxa2xx_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
return dma_mmap_writecombine(substream->pcm->card->dev, vma,
|
||||
runtime->dma_area,
|
||||
runtime->dma_addr,
|
||||
runtime->dma_bytes);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_mmap);
|
||||
|
||||
int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
{
|
||||
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = pxa2xx_pcm_hardware.buffer_bytes_max;
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
buf->area = dma_alloc_writecombine(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
buf->bytes = size;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_preallocate_dma_buffer);
|
||||
|
||||
void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_dma_buffer *buf;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
buf = &substream->dma_buffer;
|
||||
if (!buf->area)
|
||||
continue;
|
||||
dma_free_writecombine(pcm->card->dev, buf->bytes,
|
||||
buf->area, buf->addr);
|
||||
buf->area = NULL;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_free_dma_buffers);
|
||||
|
||||
MODULE_AUTHOR("Nicolas Pitre");
|
||||
MODULE_DESCRIPTION("Intel PXA2xx sound library");
|
||||
MODULE_LICENSE("GPL");
|
||||
135
sound/arm/pxa2xx-pcm.c
Normal file
135
sound/arm/pxa2xx-pcm.c
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* linux/sound/arm/pxa2xx-pcm.c -- ALSA PCM interface for the Intel PXA2xx chip
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: Nov 30, 2004
|
||||
* Copyright: (C) 2004 MontaVista Software, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
|
||||
#include <mach/dma.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pxa2xx-lib.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
#include "pxa2xx-pcm.h"
|
||||
|
||||
static int pxa2xx_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct pxa2xx_pcm_client *client = substream->private_data;
|
||||
|
||||
__pxa2xx_pcm_prepare(substream);
|
||||
|
||||
return client->prepare(substream);
|
||||
}
|
||||
|
||||
static int pxa2xx_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct pxa2xx_pcm_client *client = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct pxa2xx_runtime_data *rtd;
|
||||
int ret;
|
||||
|
||||
ret = __pxa2xx_pcm_open(substream);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
rtd = runtime->private_data;
|
||||
|
||||
rtd->params = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
client->playback_params : client->capture_params;
|
||||
ret = pxa_request_dma("dma", DMA_PRIO_LOW,
|
||||
pxa2xx_pcm_dma_irq, substream);
|
||||
if (ret < 0)
|
||||
goto err2;
|
||||
rtd->dma_ch = ret;
|
||||
|
||||
ret = client->startup(substream);
|
||||
if (!ret)
|
||||
goto out;
|
||||
|
||||
pxa_free_dma(rtd->dma_ch);
|
||||
err2:
|
||||
__pxa2xx_pcm_close(substream);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pxa2xx_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct pxa2xx_pcm_client *client = substream->private_data;
|
||||
struct pxa2xx_runtime_data *rtd = substream->runtime->private_data;
|
||||
|
||||
pxa_free_dma(rtd->dma_ch);
|
||||
client->shutdown(substream);
|
||||
|
||||
return __pxa2xx_pcm_close(substream);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops pxa2xx_pcm_ops = {
|
||||
.open = pxa2xx_pcm_open,
|
||||
.close = pxa2xx_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = __pxa2xx_pcm_hw_params,
|
||||
.hw_free = __pxa2xx_pcm_hw_free,
|
||||
.prepare = pxa2xx_pcm_prepare,
|
||||
.trigger = pxa2xx_pcm_trigger,
|
||||
.pointer = pxa2xx_pcm_pointer,
|
||||
.mmap = pxa2xx_pcm_mmap,
|
||||
};
|
||||
|
||||
int pxa2xx_pcm_new(struct snd_card *card, struct pxa2xx_pcm_client *client,
|
||||
struct snd_pcm **rpcm)
|
||||
{
|
||||
struct snd_pcm *pcm;
|
||||
int play = client->playback_params ? 1 : 0;
|
||||
int capt = client->capture_params ? 1 : 0;
|
||||
int ret;
|
||||
|
||||
ret = snd_pcm_new(card, "PXA2xx-PCM", 0, play, capt, &pcm);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
pcm->private_data = client;
|
||||
pcm->private_free = pxa2xx_pcm_free_dma_buffers;
|
||||
|
||||
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (play) {
|
||||
int stream = SNDRV_PCM_STREAM_PLAYBACK;
|
||||
snd_pcm_set_ops(pcm, stream, &pxa2xx_pcm_ops);
|
||||
ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, stream);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
if (capt) {
|
||||
int stream = SNDRV_PCM_STREAM_CAPTURE;
|
||||
snd_pcm_set_ops(pcm, stream, &pxa2xx_pcm_ops);
|
||||
ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, stream);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (rpcm)
|
||||
*rpcm = pcm;
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_new);
|
||||
|
||||
MODULE_AUTHOR("Nicolas Pitre");
|
||||
MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module");
|
||||
MODULE_LICENSE("GPL");
|
||||
29
sound/arm/pxa2xx-pcm.h
Normal file
29
sound/arm/pxa2xx-pcm.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* linux/sound/arm/pxa2xx-pcm.h -- ALSA PCM interface for the Intel PXA2xx chip
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: Nov 30, 2004
|
||||
* Copyright: MontaVista Software, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
struct pxa2xx_runtime_data {
|
||||
int dma_ch;
|
||||
struct snd_dmaengine_dai_dma_data *params;
|
||||
struct pxa_dma_desc *dma_desc_array;
|
||||
dma_addr_t dma_desc_array_phys;
|
||||
};
|
||||
|
||||
struct pxa2xx_pcm_client {
|
||||
struct snd_dmaengine_dai_dma_data *playback_params;
|
||||
struct snd_dmaengine_dai_dma_data *capture_params;
|
||||
int (*startup)(struct snd_pcm_substream *);
|
||||
void (*shutdown)(struct snd_pcm_substream *);
|
||||
int (*prepare)(struct snd_pcm_substream *);
|
||||
};
|
||||
|
||||
extern int pxa2xx_pcm_new(struct snd_card *, struct pxa2xx_pcm_client *, struct snd_pcm **);
|
||||
|
||||
19
sound/atmel/Kconfig
Normal file
19
sound/atmel/Kconfig
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
menu "Atmel devices (AVR32 and AT91)"
|
||||
depends on AVR32 || ARCH_AT91
|
||||
|
||||
config SND_ATMEL_ABDAC
|
||||
tristate "Atmel Audio Bitstream DAC (ABDAC) driver"
|
||||
select SND_PCM
|
||||
depends on DW_DMAC && AVR32
|
||||
help
|
||||
ALSA sound driver for the Atmel Audio Bitstream DAC (ABDAC).
|
||||
|
||||
config SND_ATMEL_AC97C
|
||||
tristate "Atmel AC97 Controller (AC97C) driver"
|
||||
select SND_PCM
|
||||
select SND_AC97_CODEC
|
||||
depends on (DW_DMAC && AVR32) || ARCH_AT91
|
||||
help
|
||||
ALSA sound driver for the Atmel AC97 controller.
|
||||
|
||||
endmenu
|
||||
5
sound/atmel/Makefile
Normal file
5
sound/atmel/Makefile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
snd-atmel-abdac-objs := abdac.o
|
||||
snd-atmel-ac97c-objs := ac97c.o
|
||||
|
||||
obj-$(CONFIG_SND_ATMEL_ABDAC) += snd-atmel-abdac.o
|
||||
obj-$(CONFIG_SND_ATMEL_AC97C) += snd-atmel-ac97c.o
|
||||
611
sound/atmel/abdac.c
Normal file
611
sound/atmel/abdac.c
Normal file
|
|
@ -0,0 +1,611 @@
|
|||
/*
|
||||
* Driver for the Atmel on-chip Audio Bitstream DAC (ABDAC)
|
||||
*
|
||||
* Copyright (C) 2006-2009 Atmel Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/atmel-abdac.h>
|
||||
|
||||
#include <linux/platform_data/dma-dw.h>
|
||||
#include <linux/dma/dw.h>
|
||||
|
||||
/* DAC register offsets */
|
||||
#define DAC_DATA 0x0000
|
||||
#define DAC_CTRL 0x0008
|
||||
#define DAC_INT_MASK 0x000c
|
||||
#define DAC_INT_EN 0x0010
|
||||
#define DAC_INT_DIS 0x0014
|
||||
#define DAC_INT_CLR 0x0018
|
||||
#define DAC_INT_STATUS 0x001c
|
||||
|
||||
/* Bitfields in CTRL */
|
||||
#define DAC_SWAP_OFFSET 30
|
||||
#define DAC_SWAP_SIZE 1
|
||||
#define DAC_EN_OFFSET 31
|
||||
#define DAC_EN_SIZE 1
|
||||
|
||||
/* Bitfields in INT_MASK/INT_EN/INT_DIS/INT_STATUS/INT_CLR */
|
||||
#define DAC_UNDERRUN_OFFSET 28
|
||||
#define DAC_UNDERRUN_SIZE 1
|
||||
#define DAC_TX_READY_OFFSET 29
|
||||
#define DAC_TX_READY_SIZE 1
|
||||
|
||||
/* Bit manipulation macros */
|
||||
#define DAC_BIT(name) \
|
||||
(1 << DAC_##name##_OFFSET)
|
||||
#define DAC_BF(name, value) \
|
||||
(((value) & ((1 << DAC_##name##_SIZE) - 1)) \
|
||||
<< DAC_##name##_OFFSET)
|
||||
#define DAC_BFEXT(name, value) \
|
||||
(((value) >> DAC_##name##_OFFSET) \
|
||||
& ((1 << DAC_##name##_SIZE) - 1))
|
||||
#define DAC_BFINS(name, value, old) \
|
||||
(((old) & ~(((1 << DAC_##name##_SIZE) - 1) \
|
||||
<< DAC_##name##_OFFSET)) \
|
||||
| DAC_BF(name, value))
|
||||
|
||||
/* Register access macros */
|
||||
#define dac_readl(port, reg) \
|
||||
__raw_readl((port)->regs + DAC_##reg)
|
||||
#define dac_writel(port, reg, value) \
|
||||
__raw_writel((value), (port)->regs + DAC_##reg)
|
||||
|
||||
/*
|
||||
* ABDAC supports a maximum of 6 different rates from a generic clock. The
|
||||
* generic clock has a power of two divider, which gives 6 steps from 192 kHz
|
||||
* to 5112 Hz.
|
||||
*/
|
||||
#define MAX_NUM_RATES 6
|
||||
/* ALSA seems to use rates between 192000 Hz and 5112 Hz. */
|
||||
#define RATE_MAX 192000
|
||||
#define RATE_MIN 5112
|
||||
|
||||
enum {
|
||||
DMA_READY = 0,
|
||||
};
|
||||
|
||||
struct atmel_abdac_dma {
|
||||
struct dma_chan *chan;
|
||||
struct dw_cyclic_desc *cdesc;
|
||||
};
|
||||
|
||||
struct atmel_abdac {
|
||||
struct clk *pclk;
|
||||
struct clk *sample_clk;
|
||||
struct platform_device *pdev;
|
||||
struct atmel_abdac_dma dma;
|
||||
|
||||
struct snd_pcm_hw_constraint_list constraints_rates;
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_card *card;
|
||||
struct snd_pcm *pcm;
|
||||
|
||||
void __iomem *regs;
|
||||
unsigned long flags;
|
||||
unsigned int rates[MAX_NUM_RATES];
|
||||
unsigned int rates_num;
|
||||
int irq;
|
||||
};
|
||||
|
||||
#define get_dac(card) ((struct atmel_abdac *)(card)->private_data)
|
||||
|
||||
/* This function is called by the DMA driver. */
|
||||
static void atmel_abdac_dma_period_done(void *arg)
|
||||
{
|
||||
struct atmel_abdac *dac = arg;
|
||||
snd_pcm_period_elapsed(dac->substream);
|
||||
}
|
||||
|
||||
static int atmel_abdac_prepare_dma(struct atmel_abdac *dac,
|
||||
struct snd_pcm_substream *substream,
|
||||
enum dma_data_direction direction)
|
||||
{
|
||||
struct dma_chan *chan = dac->dma.chan;
|
||||
struct dw_cyclic_desc *cdesc;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned long buffer_len, period_len;
|
||||
|
||||
/*
|
||||
* We don't do DMA on "complex" transfers, i.e. with
|
||||
* non-halfword-aligned buffers or lengths.
|
||||
*/
|
||||
if (runtime->dma_addr & 1 || runtime->buffer_size & 1) {
|
||||
dev_dbg(&dac->pdev->dev, "too complex transfer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
buffer_len = frames_to_bytes(runtime, runtime->buffer_size);
|
||||
period_len = frames_to_bytes(runtime, runtime->period_size);
|
||||
|
||||
cdesc = dw_dma_cyclic_prep(chan, runtime->dma_addr, buffer_len,
|
||||
period_len, DMA_MEM_TO_DEV);
|
||||
if (IS_ERR(cdesc)) {
|
||||
dev_dbg(&dac->pdev->dev, "could not prepare cyclic DMA\n");
|
||||
return PTR_ERR(cdesc);
|
||||
}
|
||||
|
||||
cdesc->period_callback = atmel_abdac_dma_period_done;
|
||||
cdesc->period_callback_param = dac;
|
||||
|
||||
dac->dma.cdesc = cdesc;
|
||||
|
||||
set_bit(DMA_READY, &dac->flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_hardware atmel_abdac_hw = {
|
||||
.info = (SNDRV_PCM_INFO_MMAP
|
||||
| SNDRV_PCM_INFO_MMAP_VALID
|
||||
| SNDRV_PCM_INFO_INTERLEAVED
|
||||
| SNDRV_PCM_INFO_BLOCK_TRANSFER
|
||||
| SNDRV_PCM_INFO_RESUME
|
||||
| SNDRV_PCM_INFO_PAUSE),
|
||||
.formats = (SNDRV_PCM_FMTBIT_S16_BE),
|
||||
.rates = (SNDRV_PCM_RATE_KNOT),
|
||||
.rate_min = RATE_MIN,
|
||||
.rate_max = RATE_MAX,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = 64 * 4096,
|
||||
.period_bytes_min = 4096,
|
||||
.period_bytes_max = 4096,
|
||||
.periods_min = 6,
|
||||
.periods_max = 64,
|
||||
};
|
||||
|
||||
static int atmel_abdac_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
|
||||
|
||||
dac->substream = substream;
|
||||
atmel_abdac_hw.rate_max = dac->rates[dac->rates_num - 1];
|
||||
atmel_abdac_hw.rate_min = dac->rates[0];
|
||||
substream->runtime->hw = atmel_abdac_hw;
|
||||
|
||||
return snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE, &dac->constraints_rates);
|
||||
}
|
||||
|
||||
static int atmel_abdac_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
|
||||
dac->substream = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_abdac_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
|
||||
int retval;
|
||||
|
||||
retval = snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
/* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */
|
||||
if (retval == 1)
|
||||
if (test_and_clear_bit(DMA_READY, &dac->flags))
|
||||
dw_dma_cyclic_free(dac->dma.chan);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int atmel_abdac_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
|
||||
if (test_and_clear_bit(DMA_READY, &dac->flags))
|
||||
dw_dma_cyclic_free(dac->dma.chan);
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static int atmel_abdac_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
|
||||
int retval;
|
||||
|
||||
retval = clk_set_rate(dac->sample_clk, 256 * substream->runtime->rate);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (!test_bit(DMA_READY, &dac->flags))
|
||||
retval = atmel_abdac_prepare_dma(dac, substream, DMA_TO_DEVICE);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int atmel_abdac_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
|
||||
int retval = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */
|
||||
case SNDRV_PCM_TRIGGER_RESUME: /* fall through */
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
clk_enable(dac->sample_clk);
|
||||
retval = dw_dma_cyclic_start(dac->dma.chan);
|
||||
if (retval)
|
||||
goto out;
|
||||
dac_writel(dac, CTRL, DAC_BIT(EN));
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
dw_dma_cyclic_stop(dac->dma.chan);
|
||||
dac_writel(dac, DATA, 0);
|
||||
dac_writel(dac, CTRL, 0);
|
||||
clk_disable(dac->sample_clk);
|
||||
break;
|
||||
default:
|
||||
retval = -EINVAL;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t
|
||||
atmel_abdac_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
snd_pcm_uframes_t frames;
|
||||
unsigned long bytes;
|
||||
|
||||
bytes = dw_dma_get_src_addr(dac->dma.chan);
|
||||
bytes -= runtime->dma_addr;
|
||||
|
||||
frames = bytes_to_frames(runtime, bytes);
|
||||
if (frames >= runtime->buffer_size)
|
||||
frames -= runtime->buffer_size;
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
static irqreturn_t abdac_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct atmel_abdac *dac = dev_id;
|
||||
u32 status;
|
||||
|
||||
status = dac_readl(dac, INT_STATUS);
|
||||
if (status & DAC_BIT(UNDERRUN)) {
|
||||
dev_err(&dac->pdev->dev, "underrun detected\n");
|
||||
dac_writel(dac, INT_CLR, DAC_BIT(UNDERRUN));
|
||||
} else {
|
||||
dev_err(&dac->pdev->dev, "spurious interrupt (status=0x%x)\n",
|
||||
status);
|
||||
dac_writel(dac, INT_CLR, status);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops atmel_abdac_ops = {
|
||||
.open = atmel_abdac_open,
|
||||
.close = atmel_abdac_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = atmel_abdac_hw_params,
|
||||
.hw_free = atmel_abdac_hw_free,
|
||||
.prepare = atmel_abdac_prepare,
|
||||
.trigger = atmel_abdac_trigger,
|
||||
.pointer = atmel_abdac_pointer,
|
||||
};
|
||||
|
||||
static int atmel_abdac_pcm_new(struct atmel_abdac *dac)
|
||||
{
|
||||
struct snd_pcm_hardware hw = atmel_abdac_hw;
|
||||
struct snd_pcm *pcm;
|
||||
int retval;
|
||||
|
||||
retval = snd_pcm_new(dac->card, dac->card->shortname,
|
||||
dac->pdev->id, 1, 0, &pcm);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
strcpy(pcm->name, dac->card->shortname);
|
||||
pcm->private_data = dac;
|
||||
pcm->info_flags = 0;
|
||||
dac->pcm = pcm;
|
||||
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &atmel_abdac_ops);
|
||||
|
||||
retval = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&dac->pdev->dev, hw.periods_min * hw.period_bytes_min,
|
||||
hw.buffer_bytes_max);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static bool filter(struct dma_chan *chan, void *slave)
|
||||
{
|
||||
struct dw_dma_slave *dws = slave;
|
||||
|
||||
if (dws->dma_dev == chan->device->dev) {
|
||||
chan->private = dws;
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
static int set_sample_rates(struct atmel_abdac *dac)
|
||||
{
|
||||
long new_rate = RATE_MAX;
|
||||
int retval = -EINVAL;
|
||||
int index = 0;
|
||||
|
||||
/* we start at 192 kHz and work our way down to 5112 Hz */
|
||||
while (new_rate >= RATE_MIN && index < (MAX_NUM_RATES + 1)) {
|
||||
new_rate = clk_round_rate(dac->sample_clk, 256 * new_rate);
|
||||
if (new_rate <= 0)
|
||||
break;
|
||||
/* make sure we are below the ABDAC clock */
|
||||
if (index < MAX_NUM_RATES &&
|
||||
new_rate <= clk_get_rate(dac->pclk)) {
|
||||
dac->rates[index] = new_rate / 256;
|
||||
index++;
|
||||
}
|
||||
/* divide by 256 and then by two to get next rate */
|
||||
new_rate /= 256 * 2;
|
||||
}
|
||||
|
||||
if (index) {
|
||||
int i;
|
||||
|
||||
/* reverse array, smallest go first */
|
||||
for (i = 0; i < (index / 2); i++) {
|
||||
unsigned int tmp = dac->rates[index - 1 - i];
|
||||
dac->rates[index - 1 - i] = dac->rates[i];
|
||||
dac->rates[i] = tmp;
|
||||
}
|
||||
|
||||
dac->constraints_rates.count = index;
|
||||
dac->constraints_rates.list = dac->rates;
|
||||
dac->constraints_rates.mask = 0;
|
||||
dac->rates_num = index;
|
||||
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int atmel_abdac_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_card *card;
|
||||
struct atmel_abdac *dac;
|
||||
struct resource *regs;
|
||||
struct atmel_abdac_pdata *pdata;
|
||||
struct clk *pclk;
|
||||
struct clk *sample_clk;
|
||||
int retval;
|
||||
int irq;
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!regs) {
|
||||
dev_dbg(&pdev->dev, "no memory resource\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_dbg(&pdev->dev, "could not get IRQ number\n");
|
||||
return irq;
|
||||
}
|
||||
|
||||
pdata = pdev->dev.platform_data;
|
||||
if (!pdata) {
|
||||
dev_dbg(&pdev->dev, "no platform data\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
pclk = clk_get(&pdev->dev, "pclk");
|
||||
if (IS_ERR(pclk)) {
|
||||
dev_dbg(&pdev->dev, "no peripheral clock\n");
|
||||
return PTR_ERR(pclk);
|
||||
}
|
||||
sample_clk = clk_get(&pdev->dev, "sample_clk");
|
||||
if (IS_ERR(sample_clk)) {
|
||||
dev_dbg(&pdev->dev, "no sample clock\n");
|
||||
retval = PTR_ERR(sample_clk);
|
||||
goto out_put_pclk;
|
||||
}
|
||||
clk_enable(pclk);
|
||||
|
||||
retval = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1,
|
||||
SNDRV_DEFAULT_STR1, THIS_MODULE,
|
||||
sizeof(struct atmel_abdac), &card);
|
||||
if (retval) {
|
||||
dev_dbg(&pdev->dev, "could not create sound card device\n");
|
||||
goto out_put_sample_clk;
|
||||
}
|
||||
|
||||
dac = get_dac(card);
|
||||
|
||||
dac->irq = irq;
|
||||
dac->card = card;
|
||||
dac->pclk = pclk;
|
||||
dac->sample_clk = sample_clk;
|
||||
dac->pdev = pdev;
|
||||
|
||||
retval = set_sample_rates(dac);
|
||||
if (retval < 0) {
|
||||
dev_dbg(&pdev->dev, "could not set supported rates\n");
|
||||
goto out_free_card;
|
||||
}
|
||||
|
||||
dac->regs = ioremap(regs->start, resource_size(regs));
|
||||
if (!dac->regs) {
|
||||
dev_dbg(&pdev->dev, "could not remap register memory\n");
|
||||
retval = -ENOMEM;
|
||||
goto out_free_card;
|
||||
}
|
||||
|
||||
/* make sure the DAC is silent and disabled */
|
||||
dac_writel(dac, DATA, 0);
|
||||
dac_writel(dac, CTRL, 0);
|
||||
|
||||
retval = request_irq(irq, abdac_interrupt, 0, "abdac", dac);
|
||||
if (retval) {
|
||||
dev_dbg(&pdev->dev, "could not request irq\n");
|
||||
goto out_unmap_regs;
|
||||
}
|
||||
|
||||
if (pdata->dws.dma_dev) {
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
|
||||
dac->dma.chan = dma_request_channel(mask, filter, &pdata->dws);
|
||||
if (dac->dma.chan) {
|
||||
struct dma_slave_config dma_conf = {
|
||||
.dst_addr = regs->start + DAC_DATA,
|
||||
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
|
||||
.src_maxburst = 1,
|
||||
.dst_maxburst = 1,
|
||||
.direction = DMA_MEM_TO_DEV,
|
||||
.device_fc = false,
|
||||
};
|
||||
|
||||
dmaengine_slave_config(dac->dma.chan, &dma_conf);
|
||||
}
|
||||
}
|
||||
if (!pdata->dws.dma_dev || !dac->dma.chan) {
|
||||
dev_dbg(&pdev->dev, "DMA not available\n");
|
||||
retval = -ENODEV;
|
||||
goto out_unmap_regs;
|
||||
}
|
||||
|
||||
strcpy(card->driver, "Atmel ABDAC");
|
||||
strcpy(card->shortname, "Atmel ABDAC");
|
||||
sprintf(card->longname, "Atmel Audio Bitstream DAC");
|
||||
|
||||
retval = atmel_abdac_pcm_new(dac);
|
||||
if (retval) {
|
||||
dev_dbg(&pdev->dev, "could not register ABDAC pcm device\n");
|
||||
goto out_release_dma;
|
||||
}
|
||||
|
||||
retval = snd_card_register(card);
|
||||
if (retval) {
|
||||
dev_dbg(&pdev->dev, "could not register sound card\n");
|
||||
goto out_release_dma;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, card);
|
||||
|
||||
dev_info(&pdev->dev, "Atmel ABDAC at 0x%p using %s\n",
|
||||
dac->regs, dev_name(&dac->dma.chan->dev->device));
|
||||
|
||||
return retval;
|
||||
|
||||
out_release_dma:
|
||||
dma_release_channel(dac->dma.chan);
|
||||
dac->dma.chan = NULL;
|
||||
out_unmap_regs:
|
||||
iounmap(dac->regs);
|
||||
out_free_card:
|
||||
snd_card_free(card);
|
||||
out_put_sample_clk:
|
||||
clk_put(sample_clk);
|
||||
clk_disable(pclk);
|
||||
out_put_pclk:
|
||||
clk_put(pclk);
|
||||
return retval;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int atmel_abdac_suspend(struct device *pdev)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(pdev);
|
||||
struct atmel_abdac *dac = card->private_data;
|
||||
|
||||
dw_dma_cyclic_stop(dac->dma.chan);
|
||||
clk_disable(dac->sample_clk);
|
||||
clk_disable(dac->pclk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_abdac_resume(struct device *pdev)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(pdev);
|
||||
struct atmel_abdac *dac = card->private_data;
|
||||
|
||||
clk_enable(dac->pclk);
|
||||
clk_enable(dac->sample_clk);
|
||||
if (test_bit(DMA_READY, &dac->flags))
|
||||
dw_dma_cyclic_start(dac->dma.chan);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(atmel_abdac_pm, atmel_abdac_suspend, atmel_abdac_resume);
|
||||
#define ATMEL_ABDAC_PM_OPS &atmel_abdac_pm
|
||||
#else
|
||||
#define ATMEL_ABDAC_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
static int atmel_abdac_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_card *card = platform_get_drvdata(pdev);
|
||||
struct atmel_abdac *dac = get_dac(card);
|
||||
|
||||
clk_put(dac->sample_clk);
|
||||
clk_disable(dac->pclk);
|
||||
clk_put(dac->pclk);
|
||||
|
||||
dma_release_channel(dac->dma.chan);
|
||||
dac->dma.chan = NULL;
|
||||
iounmap(dac->regs);
|
||||
free_irq(dac->irq, dac);
|
||||
snd_card_free(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver atmel_abdac_driver = {
|
||||
.remove = atmel_abdac_remove,
|
||||
.driver = {
|
||||
.name = "atmel_abdac",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = ATMEL_ABDAC_PM_OPS,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init atmel_abdac_init(void)
|
||||
{
|
||||
return platform_driver_probe(&atmel_abdac_driver,
|
||||
atmel_abdac_probe);
|
||||
}
|
||||
module_init(atmel_abdac_init);
|
||||
|
||||
static void __exit atmel_abdac_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&atmel_abdac_driver);
|
||||
}
|
||||
module_exit(atmel_abdac_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Driver for Atmel Audio Bitstream DAC (ABDAC)");
|
||||
MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>");
|
||||
1214
sound/atmel/ac97c.c
Normal file
1214
sound/atmel/ac97c.c
Normal file
File diff suppressed because it is too large
Load diff
73
sound/atmel/ac97c.h
Normal file
73
sound/atmel/ac97c.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Register definitions for Atmel AC97C
|
||||
*
|
||||
* Copyright (C) 2005-2009 Atmel Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published
|
||||
* by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef __SOUND_ATMEL_AC97C_H
|
||||
#define __SOUND_ATMEL_AC97C_H
|
||||
|
||||
#define AC97C_MR 0x08
|
||||
#define AC97C_ICA 0x10
|
||||
#define AC97C_OCA 0x14
|
||||
#define AC97C_CARHR 0x20
|
||||
#define AC97C_CATHR 0x24
|
||||
#define AC97C_CASR 0x28
|
||||
#define AC97C_CAMR 0x2c
|
||||
#define AC97C_CORHR 0x40
|
||||
#define AC97C_COTHR 0x44
|
||||
#define AC97C_COSR 0x48
|
||||
#define AC97C_COMR 0x4c
|
||||
#define AC97C_SR 0x50
|
||||
#define AC97C_IER 0x54
|
||||
#define AC97C_IDR 0x58
|
||||
#define AC97C_IMR 0x5c
|
||||
#define AC97C_VERSION 0xfc
|
||||
|
||||
#define AC97C_CATPR PDC_TPR
|
||||
#define AC97C_CATCR PDC_TCR
|
||||
#define AC97C_CATNPR PDC_TNPR
|
||||
#define AC97C_CATNCR PDC_TNCR
|
||||
#define AC97C_CARPR PDC_RPR
|
||||
#define AC97C_CARCR PDC_RCR
|
||||
#define AC97C_CARNPR PDC_RNPR
|
||||
#define AC97C_CARNCR PDC_RNCR
|
||||
#define AC97C_PTCR PDC_PTCR
|
||||
|
||||
#define AC97C_MR_ENA (1 << 0)
|
||||
#define AC97C_MR_WRST (1 << 1)
|
||||
#define AC97C_MR_VRA (1 << 2)
|
||||
|
||||
#define AC97C_CSR_TXRDY (1 << 0)
|
||||
#define AC97C_CSR_TXEMPTY (1 << 1)
|
||||
#define AC97C_CSR_UNRUN (1 << 2)
|
||||
#define AC97C_CSR_RXRDY (1 << 4)
|
||||
#define AC97C_CSR_OVRUN (1 << 5)
|
||||
#define AC97C_CSR_ENDTX (1 << 10)
|
||||
#define AC97C_CSR_ENDRX (1 << 14)
|
||||
|
||||
#define AC97C_CMR_SIZE_20 (0 << 16)
|
||||
#define AC97C_CMR_SIZE_18 (1 << 16)
|
||||
#define AC97C_CMR_SIZE_16 (2 << 16)
|
||||
#define AC97C_CMR_SIZE_10 (3 << 16)
|
||||
#define AC97C_CMR_CEM_LITTLE (1 << 18)
|
||||
#define AC97C_CMR_CEM_BIG (0 << 18)
|
||||
#define AC97C_CMR_CENA (1 << 21)
|
||||
#define AC97C_CMR_DMAEN (1 << 22)
|
||||
|
||||
#define AC97C_SR_CAEVT (1 << 3)
|
||||
#define AC97C_SR_COEVT (1 << 2)
|
||||
#define AC97C_SR_WKUP (1 << 1)
|
||||
#define AC97C_SR_SOF (1 << 0)
|
||||
|
||||
#define AC97C_CH_MASK(slot) \
|
||||
(0x7 << (3 * (AC97_SLOT_##slot - 3)))
|
||||
#define AC97C_CH_ASSIGN(slot, channel) \
|
||||
(AC97C_CHANNEL_##channel << (3 * (AC97_SLOT_##slot - 3)))
|
||||
#define AC97C_CHANNEL_NONE 0x0
|
||||
#define AC97C_CHANNEL_A 0x1
|
||||
|
||||
#endif /* __SOUND_ATMEL_AC97C_H */
|
||||
231
sound/core/Kconfig
Normal file
231
sound/core/Kconfig
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
# ALSA soundcard-configuration
|
||||
config SND_TIMER
|
||||
tristate
|
||||
|
||||
config SND_PCM
|
||||
tristate
|
||||
select SND_TIMER
|
||||
|
||||
config SND_DMAENGINE_PCM
|
||||
tristate
|
||||
|
||||
config SND_HWDEP
|
||||
tristate
|
||||
|
||||
config SND_RAWMIDI
|
||||
tristate
|
||||
|
||||
config SND_COMPRESS_OFFLOAD
|
||||
tristate
|
||||
|
||||
# To be effective this also requires INPUT - users should say:
|
||||
# select SND_JACK if INPUT=y || INPUT=SND
|
||||
# to avoid having to force INPUT on.
|
||||
config SND_JACK
|
||||
bool
|
||||
|
||||
config SND_SEQUENCER
|
||||
tristate "Sequencer support"
|
||||
select SND_TIMER
|
||||
help
|
||||
Say Y or M to enable MIDI sequencer and router support. This
|
||||
feature allows routing and enqueueing of MIDI events. Events
|
||||
can be processed at a given time.
|
||||
|
||||
Many programs require this feature, so you should enable it
|
||||
unless you know what you're doing.
|
||||
|
||||
config SND_SEQ_DUMMY
|
||||
tristate "Sequencer dummy client"
|
||||
depends on SND_SEQUENCER
|
||||
help
|
||||
Say Y here to enable the dummy sequencer client. This client
|
||||
is a simple MIDI-through client: all normal input events are
|
||||
redirected to the output port immediately.
|
||||
|
||||
You don't need this unless you want to connect many MIDI
|
||||
devices or applications together.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-seq-dummy.
|
||||
|
||||
config SND_OSSEMUL
|
||||
select SOUND_OSS_CORE
|
||||
bool
|
||||
|
||||
config SND_MIXER_OSS
|
||||
tristate "OSS Mixer API"
|
||||
select SND_OSSEMUL
|
||||
help
|
||||
To enable OSS mixer API emulation (/dev/mixer*), say Y here
|
||||
and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
|
||||
|
||||
Many programs still use the OSS API, so say Y.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-mixer-oss.
|
||||
|
||||
config SND_PCM_OSS
|
||||
tristate "OSS PCM (digital audio) API"
|
||||
select SND_OSSEMUL
|
||||
select SND_PCM
|
||||
help
|
||||
To enable OSS digital audio (PCM) emulation (/dev/dsp*), say Y
|
||||
here and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
|
||||
|
||||
Many programs still use the OSS API, so say Y.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-pcm-oss.
|
||||
|
||||
config SND_PCM_OSS_PLUGINS
|
||||
bool "OSS PCM (digital audio) API - Include plugin system"
|
||||
depends on SND_PCM_OSS
|
||||
default y
|
||||
help
|
||||
If you disable this option, the ALSA's OSS PCM API will not
|
||||
support conversion of channels, formats and rates. It will
|
||||
behave like most of new OSS/Free drivers in 2.4/2.6 kernels.
|
||||
|
||||
config SND_SEQUENCER_OSS
|
||||
bool "OSS Sequencer API"
|
||||
depends on SND_SEQUENCER
|
||||
select SND_OSSEMUL
|
||||
help
|
||||
Say Y here to enable OSS sequencer emulation (both
|
||||
/dev/sequencer and /dev/music interfaces).
|
||||
|
||||
Many programs still use the OSS API, so say Y.
|
||||
|
||||
If you choose M in "Sequencer support" (SND_SEQUENCER),
|
||||
this will be compiled as a module. The module will be called
|
||||
snd-seq-oss.
|
||||
|
||||
config SND_HRTIMER
|
||||
tristate "HR-timer backend support"
|
||||
depends on HIGH_RES_TIMERS
|
||||
select SND_TIMER
|
||||
help
|
||||
Say Y here to enable HR-timer backend for ALSA timer. ALSA uses
|
||||
the hrtimer as a precise timing source. The ALSA sequencer code
|
||||
also can use this timing source.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-hrtimer.
|
||||
|
||||
config SND_SEQ_HRTIMER_DEFAULT
|
||||
bool "Use HR-timer as default sequencer timer"
|
||||
depends on SND_HRTIMER && SND_SEQUENCER
|
||||
default y
|
||||
help
|
||||
Say Y here to use the HR-timer backend as the default sequencer
|
||||
timer.
|
||||
|
||||
config SND_RTCTIMER
|
||||
tristate "RTC Timer support"
|
||||
depends on RTC
|
||||
select SND_TIMER
|
||||
help
|
||||
Say Y here to enable RTC timer support for ALSA. ALSA uses
|
||||
the RTC timer as a precise timing source and maps the RTC
|
||||
timer to ALSA's timer interface. The ALSA sequencer code also
|
||||
can use this timing source.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-rtctimer.
|
||||
|
||||
Note that this option is exclusive with the new RTC drivers
|
||||
(CONFIG_RTC_CLASS) since this requires the old API.
|
||||
|
||||
config SND_SEQ_RTCTIMER_DEFAULT
|
||||
bool "Use RTC as default sequencer timer"
|
||||
depends on SND_RTCTIMER && SND_SEQUENCER
|
||||
depends on !SND_SEQ_HRTIMER_DEFAULT
|
||||
default y
|
||||
help
|
||||
Say Y here to use the RTC timer as the default sequencer
|
||||
timer. This is strongly recommended because it ensures
|
||||
precise MIDI timing even when the system timer runs at less
|
||||
than 1000 Hz.
|
||||
|
||||
If in doubt, say Y.
|
||||
|
||||
config SND_DYNAMIC_MINORS
|
||||
bool "Dynamic device file minor numbers"
|
||||
help
|
||||
If you say Y here, the minor numbers of ALSA device files in
|
||||
/dev/snd/ are allocated dynamically. This allows you to have
|
||||
more than 8 sound cards, but requires a dynamic device file
|
||||
system like udev.
|
||||
|
||||
If you are unsure about this, say N here.
|
||||
|
||||
config SND_MAX_CARDS
|
||||
int "Max number of sound cards"
|
||||
range 4 256
|
||||
default 32
|
||||
depends on SND_DYNAMIC_MINORS
|
||||
help
|
||||
Specify the max number of sound cards that can be assigned
|
||||
on a single machine.
|
||||
|
||||
config SND_SUPPORT_OLD_API
|
||||
bool "Support old ALSA API"
|
||||
default y
|
||||
help
|
||||
Say Y here to support the obsolete ALSA PCM API (ver.0.9.0 rc3
|
||||
or older).
|
||||
|
||||
config SND_VERBOSE_PROCFS
|
||||
bool "Verbose procfs contents"
|
||||
depends on PROC_FS
|
||||
default y
|
||||
help
|
||||
Say Y here to include code for verbose procfs contents (provides
|
||||
useful information to developers when a problem occurs). On the
|
||||
other side, it makes the ALSA subsystem larger.
|
||||
|
||||
config SND_VERBOSE_PRINTK
|
||||
bool "Verbose printk"
|
||||
help
|
||||
Say Y here to enable verbose log messages. These messages
|
||||
will help to identify source file and position containing
|
||||
printed messages.
|
||||
|
||||
You don't need this unless you're debugging ALSA.
|
||||
|
||||
config SND_DEBUG
|
||||
bool "Debug"
|
||||
help
|
||||
Say Y here to enable ALSA debug code.
|
||||
|
||||
config SND_DEBUG_VERBOSE
|
||||
bool "More verbose debug"
|
||||
depends on SND_DEBUG
|
||||
help
|
||||
Say Y here to enable extra-verbose debugging messages.
|
||||
|
||||
Let me repeat: it enables EXTRA-VERBOSE DEBUGGING messages.
|
||||
So, say Y only if you are ready to be annoyed.
|
||||
|
||||
config SND_PCM_XRUN_DEBUG
|
||||
bool "Enable PCM ring buffer overrun/underrun debugging"
|
||||
default n
|
||||
depends on SND_DEBUG && SND_VERBOSE_PROCFS
|
||||
help
|
||||
Say Y to enable the PCM ring buffer overrun/underrun debugging.
|
||||
It is usually not required, but if you have trouble with
|
||||
sound clicking when system is loaded, it may help to determine
|
||||
the process or driver which causes the scheduling gaps.
|
||||
|
||||
config SND_VMASTER
|
||||
bool
|
||||
|
||||
config SND_KCTL_JACK
|
||||
bool
|
||||
|
||||
config SND_DMA_SGBUF
|
||||
def_bool y
|
||||
depends on X86
|
||||
|
||||
source "sound/core/seq/Kconfig"
|
||||
39
sound/core/Makefile
Normal file
39
sound/core/Makefile
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 1999,2001 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-y := sound.o init.o memory.o info.o control.o misc.o device.o
|
||||
snd-$(CONFIG_ISA_DMA_API) += isadma.o
|
||||
snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o info_oss.o
|
||||
snd-$(CONFIG_SND_VMASTER) += vmaster.o
|
||||
snd-$(CONFIG_SND_KCTL_JACK) += ctljack.o
|
||||
snd-$(CONFIG_SND_JACK) += jack.o
|
||||
|
||||
snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
|
||||
pcm_memory.o memalloc.o
|
||||
snd-pcm-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o
|
||||
|
||||
snd-pcm-dmaengine-objs := pcm_dmaengine.o
|
||||
|
||||
snd-rawmidi-objs := rawmidi.o
|
||||
snd-timer-objs := timer.o
|
||||
snd-hrtimer-objs := hrtimer.o
|
||||
snd-rtctimer-objs := rtctimer.o
|
||||
snd-hwdep-objs := hwdep.o
|
||||
|
||||
snd-compress-objs := compress_offload.o
|
||||
|
||||
obj-$(CONFIG_SND) += snd.o
|
||||
obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o
|
||||
obj-$(CONFIG_SND_TIMER) += snd-timer.o
|
||||
obj-$(CONFIG_SND_HRTIMER) += snd-hrtimer.o
|
||||
obj-$(CONFIG_SND_RTCTIMER) += snd-rtctimer.o
|
||||
obj-$(CONFIG_SND_PCM) += snd-pcm.o
|
||||
obj-$(CONFIG_SND_DMAENGINE_PCM) += snd-pcm-dmaengine.o
|
||||
obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o
|
||||
|
||||
obj-$(CONFIG_SND_OSSEMUL) += oss/
|
||||
obj-$(CONFIG_SND_SEQUENCER) += seq/
|
||||
|
||||
obj-$(CONFIG_SND_COMPRESS_OFFLOAD) += snd-compress.o
|
||||
1010
sound/core/compress_offload.c
Normal file
1010
sound/core/compress_offload.c
Normal file
File diff suppressed because it is too large
Load diff
1759
sound/core/control.c
Normal file
1759
sound/core/control.c
Normal file
File diff suppressed because it is too large
Load diff
448
sound/core/control_compat.c
Normal file
448
sound/core/control_compat.c
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
/*
|
||||
* compat ioctls for control API
|
||||
*
|
||||
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/* this file included from control.c */
|
||||
|
||||
#include <linux/compat.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct snd_ctl_elem_list32 {
|
||||
u32 offset;
|
||||
u32 space;
|
||||
u32 used;
|
||||
u32 count;
|
||||
u32 pids;
|
||||
unsigned char reserved[50];
|
||||
} /* don't set packed attribute here */;
|
||||
|
||||
static int snd_ctl_elem_list_compat(struct snd_card *card,
|
||||
struct snd_ctl_elem_list32 __user *data32)
|
||||
{
|
||||
struct snd_ctl_elem_list __user *data;
|
||||
compat_caddr_t ptr;
|
||||
int err;
|
||||
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
|
||||
/* offset, space, used, count */
|
||||
if (copy_in_user(data, data32, 4 * sizeof(u32)))
|
||||
return -EFAULT;
|
||||
/* pids */
|
||||
if (get_user(ptr, &data32->pids) ||
|
||||
put_user(compat_ptr(ptr), &data->pids))
|
||||
return -EFAULT;
|
||||
err = snd_ctl_elem_list(card, data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
/* copy the result */
|
||||
if (copy_in_user(data32, data, 4 * sizeof(u32)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* control element info
|
||||
* it uses union, so the things are not easy..
|
||||
*/
|
||||
|
||||
struct snd_ctl_elem_info32 {
|
||||
struct snd_ctl_elem_id id; // the size of struct is same
|
||||
s32 type;
|
||||
u32 access;
|
||||
u32 count;
|
||||
s32 owner;
|
||||
union {
|
||||
struct {
|
||||
s32 min;
|
||||
s32 max;
|
||||
s32 step;
|
||||
} integer;
|
||||
struct {
|
||||
u64 min;
|
||||
u64 max;
|
||||
u64 step;
|
||||
} integer64;
|
||||
struct {
|
||||
u32 items;
|
||||
u32 item;
|
||||
char name[64];
|
||||
u64 names_ptr;
|
||||
u32 names_length;
|
||||
} enumerated;
|
||||
unsigned char reserved[128];
|
||||
} value;
|
||||
unsigned char reserved[64];
|
||||
} __attribute__((packed));
|
||||
|
||||
static int snd_ctl_elem_info_compat(struct snd_ctl_file *ctl,
|
||||
struct snd_ctl_elem_info32 __user *data32)
|
||||
{
|
||||
struct snd_ctl_elem_info *data;
|
||||
int err;
|
||||
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (! data)
|
||||
return -ENOMEM;
|
||||
|
||||
err = -EFAULT;
|
||||
/* copy id */
|
||||
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
|
||||
goto error;
|
||||
/* we need to copy the item index.
|
||||
* hope this doesn't break anything..
|
||||
*/
|
||||
if (get_user(data->value.enumerated.item, &data32->value.enumerated.item))
|
||||
goto error;
|
||||
|
||||
snd_power_lock(ctl->card);
|
||||
err = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0);
|
||||
if (err >= 0)
|
||||
err = snd_ctl_elem_info(ctl, data);
|
||||
snd_power_unlock(ctl->card);
|
||||
|
||||
if (err < 0)
|
||||
goto error;
|
||||
/* restore info to 32bit */
|
||||
err = -EFAULT;
|
||||
/* id, type, access, count */
|
||||
if (copy_to_user(&data32->id, &data->id, sizeof(data->id)) ||
|
||||
copy_to_user(&data32->type, &data->type, 3 * sizeof(u32)))
|
||||
goto error;
|
||||
if (put_user(data->owner, &data32->owner))
|
||||
goto error;
|
||||
switch (data->type) {
|
||||
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER:
|
||||
if (put_user(data->value.integer.min, &data32->value.integer.min) ||
|
||||
put_user(data->value.integer.max, &data32->value.integer.max) ||
|
||||
put_user(data->value.integer.step, &data32->value.integer.step))
|
||||
goto error;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
|
||||
if (copy_to_user(&data32->value.integer64,
|
||||
&data->value.integer64,
|
||||
sizeof(data->value.integer64)))
|
||||
goto error;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
|
||||
if (copy_to_user(&data32->value.enumerated,
|
||||
&data->value.enumerated,
|
||||
sizeof(data->value.enumerated)))
|
||||
goto error;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
err = 0;
|
||||
error:
|
||||
kfree(data);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* read / write */
|
||||
struct snd_ctl_elem_value32 {
|
||||
struct snd_ctl_elem_id id;
|
||||
unsigned int indirect; /* bit-field causes misalignment */
|
||||
union {
|
||||
s32 integer[128];
|
||||
unsigned char data[512];
|
||||
#ifndef CONFIG_X86_64
|
||||
s64 integer64[64];
|
||||
#endif
|
||||
} value;
|
||||
unsigned char reserved[128];
|
||||
};
|
||||
|
||||
|
||||
/* get the value type and count of the control */
|
||||
static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id,
|
||||
int *countp)
|
||||
{
|
||||
struct snd_kcontrol *kctl;
|
||||
struct snd_ctl_elem_info *info;
|
||||
int err;
|
||||
|
||||
down_read(&card->controls_rwsem);
|
||||
kctl = snd_ctl_find_id(card, id);
|
||||
if (! kctl) {
|
||||
up_read(&card->controls_rwsem);
|
||||
return -ENXIO;
|
||||
}
|
||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||||
if (info == NULL) {
|
||||
up_read(&card->controls_rwsem);
|
||||
return -ENOMEM;
|
||||
}
|
||||
info->id = *id;
|
||||
err = kctl->info(kctl, info);
|
||||
up_read(&card->controls_rwsem);
|
||||
if (err >= 0) {
|
||||
err = info->type;
|
||||
*countp = info->count;
|
||||
}
|
||||
kfree(info);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int get_elem_size(int type, int count)
|
||||
{
|
||||
switch (type) {
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
|
||||
return sizeof(s64) * count;
|
||||
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
|
||||
return sizeof(int) * count;
|
||||
case SNDRV_CTL_ELEM_TYPE_BYTES:
|
||||
return 512;
|
||||
case SNDRV_CTL_ELEM_TYPE_IEC958:
|
||||
return sizeof(struct snd_aes_iec958);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int copy_ctl_value_from_user(struct snd_card *card,
|
||||
struct snd_ctl_elem_value *data,
|
||||
struct snd_ctl_elem_value32 __user *data32,
|
||||
int *typep, int *countp)
|
||||
{
|
||||
int i, type, size;
|
||||
int uninitialized_var(count);
|
||||
unsigned int indirect;
|
||||
|
||||
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
|
||||
return -EFAULT;
|
||||
if (get_user(indirect, &data32->indirect))
|
||||
return -EFAULT;
|
||||
if (indirect)
|
||||
return -EINVAL;
|
||||
type = get_ctl_type(card, &data->id, &count);
|
||||
if (type < 0)
|
||||
return type;
|
||||
|
||||
if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
|
||||
type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
|
||||
for (i = 0; i < count; i++) {
|
||||
int val;
|
||||
if (get_user(val, &data32->value.integer[i]))
|
||||
return -EFAULT;
|
||||
data->value.integer.value[i] = val;
|
||||
}
|
||||
} else {
|
||||
size = get_elem_size(type, count);
|
||||
if (size < 0) {
|
||||
dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (copy_from_user(data->value.bytes.data,
|
||||
data32->value.data, size))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
*typep = type;
|
||||
*countp = count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* restore the value to 32bit */
|
||||
static int copy_ctl_value_to_user(struct snd_ctl_elem_value32 __user *data32,
|
||||
struct snd_ctl_elem_value *data,
|
||||
int type, int count)
|
||||
{
|
||||
int i, size;
|
||||
|
||||
if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
|
||||
type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
|
||||
for (i = 0; i < count; i++) {
|
||||
int val;
|
||||
val = data->value.integer.value[i];
|
||||
if (put_user(val, &data32->value.integer[i]))
|
||||
return -EFAULT;
|
||||
}
|
||||
} else {
|
||||
size = get_elem_size(type, count);
|
||||
if (copy_to_user(data32->value.data,
|
||||
data->value.bytes.data, size))
|
||||
return -EFAULT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_ctl_elem_read_user_compat(struct snd_card *card,
|
||||
struct snd_ctl_elem_value32 __user *data32)
|
||||
{
|
||||
struct snd_ctl_elem_value *data;
|
||||
int err, type, count;
|
||||
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0)
|
||||
goto error;
|
||||
|
||||
snd_power_lock(card);
|
||||
err = snd_power_wait(card, SNDRV_CTL_POWER_D0);
|
||||
if (err >= 0)
|
||||
err = snd_ctl_elem_read(card, data);
|
||||
snd_power_unlock(card);
|
||||
if (err >= 0)
|
||||
err = copy_ctl_value_to_user(data32, data, type, count);
|
||||
error:
|
||||
kfree(data);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file,
|
||||
struct snd_ctl_elem_value32 __user *data32)
|
||||
{
|
||||
struct snd_ctl_elem_value *data;
|
||||
struct snd_card *card = file->card;
|
||||
int err, type, count;
|
||||
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0)
|
||||
goto error;
|
||||
|
||||
snd_power_lock(card);
|
||||
err = snd_power_wait(card, SNDRV_CTL_POWER_D0);
|
||||
if (err >= 0)
|
||||
err = snd_ctl_elem_write(card, file, data);
|
||||
snd_power_unlock(card);
|
||||
if (err >= 0)
|
||||
err = copy_ctl_value_to_user(data32, data, type, count);
|
||||
error:
|
||||
kfree(data);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* add or replace a user control */
|
||||
static int snd_ctl_elem_add_compat(struct snd_ctl_file *file,
|
||||
struct snd_ctl_elem_info32 __user *data32,
|
||||
int replace)
|
||||
{
|
||||
struct snd_ctl_elem_info *data;
|
||||
int err;
|
||||
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (! data)
|
||||
return -ENOMEM;
|
||||
|
||||
err = -EFAULT;
|
||||
/* id, type, access, count */ \
|
||||
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)) ||
|
||||
copy_from_user(&data->type, &data32->type, 3 * sizeof(u32)))
|
||||
goto error;
|
||||
if (get_user(data->owner, &data32->owner) ||
|
||||
get_user(data->type, &data32->type))
|
||||
goto error;
|
||||
switch (data->type) {
|
||||
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER:
|
||||
if (get_user(data->value.integer.min, &data32->value.integer.min) ||
|
||||
get_user(data->value.integer.max, &data32->value.integer.max) ||
|
||||
get_user(data->value.integer.step, &data32->value.integer.step))
|
||||
goto error;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
|
||||
if (copy_from_user(&data->value.integer64,
|
||||
&data32->value.integer64,
|
||||
sizeof(data->value.integer64)))
|
||||
goto error;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
|
||||
if (copy_from_user(&data->value.enumerated,
|
||||
&data32->value.enumerated,
|
||||
sizeof(data->value.enumerated)))
|
||||
goto error;
|
||||
data->value.enumerated.names_ptr =
|
||||
(uintptr_t)compat_ptr(data->value.enumerated.names_ptr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
err = snd_ctl_elem_add(file, data, replace);
|
||||
error:
|
||||
kfree(data);
|
||||
return err;
|
||||
}
|
||||
|
||||
enum {
|
||||
SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32),
|
||||
SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct snd_ctl_elem_info32),
|
||||
SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct snd_ctl_elem_value32),
|
||||
SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct snd_ctl_elem_value32),
|
||||
SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct snd_ctl_elem_info32),
|
||||
SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct snd_ctl_elem_info32),
|
||||
};
|
||||
|
||||
static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct snd_ctl_file *ctl;
|
||||
struct snd_kctl_ioctl *p;
|
||||
void __user *argp = compat_ptr(arg);
|
||||
int err;
|
||||
|
||||
ctl = file->private_data;
|
||||
if (snd_BUG_ON(!ctl || !ctl->card))
|
||||
return -ENXIO;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_CTL_IOCTL_PVERSION:
|
||||
case SNDRV_CTL_IOCTL_CARD_INFO:
|
||||
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
|
||||
case SNDRV_CTL_IOCTL_POWER:
|
||||
case SNDRV_CTL_IOCTL_POWER_STATE:
|
||||
case SNDRV_CTL_IOCTL_ELEM_LOCK:
|
||||
case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
|
||||
case SNDRV_CTL_IOCTL_ELEM_REMOVE:
|
||||
case SNDRV_CTL_IOCTL_TLV_READ:
|
||||
case SNDRV_CTL_IOCTL_TLV_WRITE:
|
||||
case SNDRV_CTL_IOCTL_TLV_COMMAND:
|
||||
return snd_ctl_ioctl(file, cmd, (unsigned long)argp);
|
||||
case SNDRV_CTL_IOCTL_ELEM_LIST32:
|
||||
return snd_ctl_elem_list_compat(ctl->card, argp);
|
||||
case SNDRV_CTL_IOCTL_ELEM_INFO32:
|
||||
return snd_ctl_elem_info_compat(ctl, argp);
|
||||
case SNDRV_CTL_IOCTL_ELEM_READ32:
|
||||
return snd_ctl_elem_read_user_compat(ctl->card, argp);
|
||||
case SNDRV_CTL_IOCTL_ELEM_WRITE32:
|
||||
return snd_ctl_elem_write_user_compat(ctl, argp);
|
||||
case SNDRV_CTL_IOCTL_ELEM_ADD32:
|
||||
return snd_ctl_elem_add_compat(ctl, argp, 0);
|
||||
case SNDRV_CTL_IOCTL_ELEM_REPLACE32:
|
||||
return snd_ctl_elem_add_compat(ctl, argp, 1);
|
||||
}
|
||||
|
||||
down_read(&snd_ioctl_rwsem);
|
||||
list_for_each_entry(p, &snd_control_compat_ioctls, list) {
|
||||
if (p->fioctl) {
|
||||
err = p->fioctl(ctl->card, ctl, cmd, arg);
|
||||
if (err != -ENOIOCTLCMD) {
|
||||
up_read(&snd_ioctl_rwsem);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
up_read(&snd_ioctl_rwsem);
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
56
sound/core/ctljack.c
Normal file
56
sound/core/ctljack.c
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Helper functions for jack-detection kcontrols
|
||||
*
|
||||
* Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/control.h>
|
||||
|
||||
#define jack_detect_kctl_info snd_ctl_boolean_mono_info
|
||||
|
||||
static int jack_detect_kctl_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.integer.value[0] = kcontrol->private_value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new jack_detect_kctl = {
|
||||
/* name is filled later */
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||
.info = jack_detect_kctl_info,
|
||||
.get = jack_detect_kctl_get,
|
||||
};
|
||||
|
||||
struct snd_kcontrol *
|
||||
snd_kctl_jack_new(const char *name, int idx, void *private_data)
|
||||
{
|
||||
struct snd_kcontrol *kctl;
|
||||
kctl = snd_ctl_new1(&jack_detect_kctl, private_data);
|
||||
if (!kctl)
|
||||
return NULL;
|
||||
snprintf(kctl->id.name, sizeof(kctl->id.name), "%s Jack", name);
|
||||
kctl->id.index = idx;
|
||||
kctl->private_value = 0;
|
||||
return kctl;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_kctl_jack_new);
|
||||
|
||||
void snd_kctl_jack_report(struct snd_card *card,
|
||||
struct snd_kcontrol *kctl, bool status)
|
||||
{
|
||||
if (kctl->private_value == status)
|
||||
return;
|
||||
kctl->private_value = status;
|
||||
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_kctl_jack_report);
|
||||
224
sound/core/device.c
Normal file
224
sound/core/device.c
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Device management routines
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/errno.h>
|
||||
#include <sound/core.h>
|
||||
|
||||
/**
|
||||
* snd_device_new - create an ALSA device component
|
||||
* @card: the card instance
|
||||
* @type: the device type, SNDRV_DEV_XXX
|
||||
* @device_data: the data pointer of this device
|
||||
* @ops: the operator table
|
||||
*
|
||||
* Creates a new device component for the given data pointer.
|
||||
* The device will be assigned to the card and managed together
|
||||
* by the card.
|
||||
*
|
||||
* The data pointer plays a role as the identifier, too, so the
|
||||
* pointer address must be unique and unchanged.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_device_new(struct snd_card *card, enum snd_device_type type,
|
||||
void *device_data, struct snd_device_ops *ops)
|
||||
{
|
||||
struct snd_device *dev;
|
||||
struct list_head *p;
|
||||
|
||||
if (snd_BUG_ON(!card || !device_data || !ops))
|
||||
return -ENXIO;
|
||||
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||||
if (dev == NULL) {
|
||||
dev_err(card->dev, "Cannot allocate device, type=%d\n", type);
|
||||
return -ENOMEM;
|
||||
}
|
||||
INIT_LIST_HEAD(&dev->list);
|
||||
dev->card = card;
|
||||
dev->type = type;
|
||||
dev->state = SNDRV_DEV_BUILD;
|
||||
dev->device_data = device_data;
|
||||
dev->ops = ops;
|
||||
|
||||
/* insert the entry in an incrementally sorted list */
|
||||
list_for_each_prev(p, &card->devices) {
|
||||
struct snd_device *pdev = list_entry(p, struct snd_device, list);
|
||||
if ((unsigned int)pdev->type <= (unsigned int)type)
|
||||
break;
|
||||
}
|
||||
|
||||
list_add(&dev->list, p);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_device_new);
|
||||
|
||||
static int __snd_device_disconnect(struct snd_device *dev)
|
||||
{
|
||||
if (dev->state == SNDRV_DEV_REGISTERED) {
|
||||
if (dev->ops->dev_disconnect &&
|
||||
dev->ops->dev_disconnect(dev))
|
||||
dev_err(dev->card->dev, "device disconnect failure\n");
|
||||
dev->state = SNDRV_DEV_DISCONNECTED;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __snd_device_free(struct snd_device *dev)
|
||||
{
|
||||
/* unlink */
|
||||
list_del(&dev->list);
|
||||
|
||||
__snd_device_disconnect(dev);
|
||||
if (dev->ops->dev_free) {
|
||||
if (dev->ops->dev_free(dev))
|
||||
dev_err(dev->card->dev, "device free failure\n");
|
||||
}
|
||||
kfree(dev);
|
||||
}
|
||||
|
||||
static struct snd_device *look_for_dev(struct snd_card *card, void *device_data)
|
||||
{
|
||||
struct snd_device *dev;
|
||||
|
||||
list_for_each_entry(dev, &card->devices, list)
|
||||
if (dev->device_data == device_data)
|
||||
return dev;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_device_free - release the device from the card
|
||||
* @card: the card instance
|
||||
* @device_data: the data pointer to release
|
||||
*
|
||||
* Removes the device from the list on the card and invokes the
|
||||
* callbacks, dev_disconnect and dev_free, corresponding to the state.
|
||||
* Then release the device.
|
||||
*/
|
||||
void snd_device_free(struct snd_card *card, void *device_data)
|
||||
{
|
||||
struct snd_device *dev;
|
||||
|
||||
if (snd_BUG_ON(!card || !device_data))
|
||||
return;
|
||||
dev = look_for_dev(card, device_data);
|
||||
if (dev)
|
||||
__snd_device_free(dev);
|
||||
else
|
||||
dev_dbg(card->dev, "device free %p (from %pF), not found\n",
|
||||
device_data, __builtin_return_address(0));
|
||||
}
|
||||
EXPORT_SYMBOL(snd_device_free);
|
||||
|
||||
static int __snd_device_register(struct snd_device *dev)
|
||||
{
|
||||
if (dev->state == SNDRV_DEV_BUILD) {
|
||||
if (dev->ops->dev_register) {
|
||||
int err = dev->ops->dev_register(dev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
dev->state = SNDRV_DEV_REGISTERED;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_device_register - register the device
|
||||
* @card: the card instance
|
||||
* @device_data: the data pointer to register
|
||||
*
|
||||
* Registers the device which was already created via
|
||||
* snd_device_new(). Usually this is called from snd_card_register(),
|
||||
* but it can be called later if any new devices are created after
|
||||
* invocation of snd_card_register().
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure or if the
|
||||
* device not found.
|
||||
*/
|
||||
int snd_device_register(struct snd_card *card, void *device_data)
|
||||
{
|
||||
struct snd_device *dev;
|
||||
|
||||
if (snd_BUG_ON(!card || !device_data))
|
||||
return -ENXIO;
|
||||
dev = look_for_dev(card, device_data);
|
||||
if (dev)
|
||||
return __snd_device_register(dev);
|
||||
snd_BUG();
|
||||
return -ENXIO;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_device_register);
|
||||
|
||||
/*
|
||||
* register all the devices on the card.
|
||||
* called from init.c
|
||||
*/
|
||||
int snd_device_register_all(struct snd_card *card)
|
||||
{
|
||||
struct snd_device *dev;
|
||||
int err;
|
||||
|
||||
if (snd_BUG_ON(!card))
|
||||
return -ENXIO;
|
||||
list_for_each_entry(dev, &card->devices, list) {
|
||||
err = __snd_device_register(dev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* disconnect all the devices on the card.
|
||||
* called from init.c
|
||||
*/
|
||||
int snd_device_disconnect_all(struct snd_card *card)
|
||||
{
|
||||
struct snd_device *dev;
|
||||
int err = 0;
|
||||
|
||||
if (snd_BUG_ON(!card))
|
||||
return -ENXIO;
|
||||
list_for_each_entry_reverse(dev, &card->devices, list) {
|
||||
if (__snd_device_disconnect(dev) < 0)
|
||||
err = -ENXIO;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* release all the devices on the card.
|
||||
* called from init.c
|
||||
*/
|
||||
void snd_device_free_all(struct snd_card *card)
|
||||
{
|
||||
struct snd_device *dev, *next;
|
||||
|
||||
if (snd_BUG_ON(!card))
|
||||
return;
|
||||
list_for_each_entry_safe_reverse(dev, next, &card->devices, list)
|
||||
__snd_device_free(dev);
|
||||
}
|
||||
167
sound/core/hrtimer.c
Normal file
167
sound/core/hrtimer.c
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* ALSA timer back-end using hrtimer
|
||||
* Copyright (C) 2008 Takashi Iwai
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/timer.h>
|
||||
|
||||
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
|
||||
MODULE_DESCRIPTION("ALSA hrtimer backend");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_HRTIMER));
|
||||
|
||||
#define NANO_SEC 1000000000UL /* 10^9 in sec */
|
||||
static unsigned int resolution;
|
||||
|
||||
struct snd_hrtimer {
|
||||
struct snd_timer *timer;
|
||||
struct hrtimer hrt;
|
||||
atomic_t running;
|
||||
};
|
||||
|
||||
static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
|
||||
{
|
||||
struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt);
|
||||
struct snd_timer *t = stime->timer;
|
||||
unsigned long oruns;
|
||||
|
||||
if (!atomic_read(&stime->running))
|
||||
return HRTIMER_NORESTART;
|
||||
|
||||
oruns = hrtimer_forward_now(hrt, ns_to_ktime(t->sticks * resolution));
|
||||
snd_timer_interrupt(stime->timer, t->sticks * oruns);
|
||||
|
||||
if (!atomic_read(&stime->running))
|
||||
return HRTIMER_NORESTART;
|
||||
return HRTIMER_RESTART;
|
||||
}
|
||||
|
||||
static int snd_hrtimer_open(struct snd_timer *t)
|
||||
{
|
||||
struct snd_hrtimer *stime;
|
||||
|
||||
stime = kmalloc(sizeof(*stime), GFP_KERNEL);
|
||||
if (!stime)
|
||||
return -ENOMEM;
|
||||
hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
stime->timer = t;
|
||||
stime->hrt.function = snd_hrtimer_callback;
|
||||
atomic_set(&stime->running, 0);
|
||||
t->private_data = stime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_hrtimer_close(struct snd_timer *t)
|
||||
{
|
||||
struct snd_hrtimer *stime = t->private_data;
|
||||
|
||||
if (stime) {
|
||||
hrtimer_cancel(&stime->hrt);
|
||||
kfree(stime);
|
||||
t->private_data = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_hrtimer_start(struct snd_timer *t)
|
||||
{
|
||||
struct snd_hrtimer *stime = t->private_data;
|
||||
|
||||
atomic_set(&stime->running, 0);
|
||||
hrtimer_try_to_cancel(&stime->hrt);
|
||||
hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution),
|
||||
HRTIMER_MODE_REL);
|
||||
atomic_set(&stime->running, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_hrtimer_stop(struct snd_timer *t)
|
||||
{
|
||||
struct snd_hrtimer *stime = t->private_data;
|
||||
atomic_set(&stime->running, 0);
|
||||
hrtimer_try_to_cancel(&stime->hrt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_timer_hardware hrtimer_hw = {
|
||||
.flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_TASKLET,
|
||||
.open = snd_hrtimer_open,
|
||||
.close = snd_hrtimer_close,
|
||||
.start = snd_hrtimer_start,
|
||||
.stop = snd_hrtimer_stop,
|
||||
};
|
||||
|
||||
/*
|
||||
* entry functions
|
||||
*/
|
||||
|
||||
static struct snd_timer *mytimer;
|
||||
|
||||
static int __init snd_hrtimer_init(void)
|
||||
{
|
||||
struct snd_timer *timer;
|
||||
struct timespec tp;
|
||||
int err;
|
||||
|
||||
hrtimer_get_res(CLOCK_MONOTONIC, &tp);
|
||||
if (tp.tv_sec > 0 || !tp.tv_nsec) {
|
||||
pr_err("snd-hrtimer: Invalid resolution %u.%09u",
|
||||
(unsigned)tp.tv_sec, (unsigned)tp.tv_nsec);
|
||||
return -EINVAL;
|
||||
}
|
||||
resolution = tp.tv_nsec;
|
||||
|
||||
/* Create a new timer and set up the fields */
|
||||
err = snd_timer_global_new("hrtimer", SNDRV_TIMER_GLOBAL_HRTIMER,
|
||||
&timer);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
timer->module = THIS_MODULE;
|
||||
strcpy(timer->name, "HR timer");
|
||||
timer->hw = hrtimer_hw;
|
||||
timer->hw.resolution = resolution;
|
||||
timer->hw.ticks = NANO_SEC / resolution;
|
||||
|
||||
err = snd_timer_global_register(timer);
|
||||
if (err < 0) {
|
||||
snd_timer_global_free(timer);
|
||||
return err;
|
||||
}
|
||||
mytimer = timer; /* remember this */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit snd_hrtimer_exit(void)
|
||||
{
|
||||
if (mytimer) {
|
||||
snd_timer_global_free(mytimer);
|
||||
mytimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
module_init(snd_hrtimer_init);
|
||||
module_exit(snd_hrtimer_exit);
|
||||
570
sound/core/hwdep.c
Normal file
570
sound/core/hwdep.c
Normal file
|
|
@ -0,0 +1,570 @@
|
|||
/*
|
||||
* Hardware dependent layer
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/major.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/minors.h>
|
||||
#include <sound/hwdep.h>
|
||||
#include <sound/info.h>
|
||||
|
||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
||||
MODULE_DESCRIPTION("Hardware dependent layer");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static LIST_HEAD(snd_hwdep_devices);
|
||||
static DEFINE_MUTEX(register_mutex);
|
||||
|
||||
static int snd_hwdep_free(struct snd_hwdep *hwdep);
|
||||
static int snd_hwdep_dev_free(struct snd_device *device);
|
||||
static int snd_hwdep_dev_register(struct snd_device *device);
|
||||
static int snd_hwdep_dev_disconnect(struct snd_device *device);
|
||||
|
||||
|
||||
static struct snd_hwdep *snd_hwdep_search(struct snd_card *card, int device)
|
||||
{
|
||||
struct snd_hwdep *hwdep;
|
||||
|
||||
list_for_each_entry(hwdep, &snd_hwdep_devices, list)
|
||||
if (hwdep->card == card && hwdep->device == device)
|
||||
return hwdep;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig)
|
||||
{
|
||||
struct snd_hwdep *hw = file->private_data;
|
||||
if (hw->ops.llseek)
|
||||
return hw->ops.llseek(hw, file, offset, orig);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static ssize_t snd_hwdep_read(struct file * file, char __user *buf,
|
||||
size_t count, loff_t *offset)
|
||||
{
|
||||
struct snd_hwdep *hw = file->private_data;
|
||||
if (hw->ops.read)
|
||||
return hw->ops.read(hw, buf, count, offset);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static ssize_t snd_hwdep_write(struct file * file, const char __user *buf,
|
||||
size_t count, loff_t *offset)
|
||||
{
|
||||
struct snd_hwdep *hw = file->private_data;
|
||||
if (hw->ops.write)
|
||||
return hw->ops.write(hw, buf, count, offset);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static int snd_hwdep_open(struct inode *inode, struct file * file)
|
||||
{
|
||||
int major = imajor(inode);
|
||||
struct snd_hwdep *hw;
|
||||
int err;
|
||||
wait_queue_t wait;
|
||||
|
||||
if (major == snd_major) {
|
||||
hw = snd_lookup_minor_data(iminor(inode),
|
||||
SNDRV_DEVICE_TYPE_HWDEP);
|
||||
#ifdef CONFIG_SND_OSSEMUL
|
||||
} else if (major == SOUND_MAJOR) {
|
||||
hw = snd_lookup_oss_minor_data(iminor(inode),
|
||||
SNDRV_OSS_DEVICE_TYPE_DMFM);
|
||||
#endif
|
||||
} else
|
||||
return -ENXIO;
|
||||
if (hw == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
if (!try_module_get(hw->card->module)) {
|
||||
snd_card_unref(hw->card);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
init_waitqueue_entry(&wait, current);
|
||||
add_wait_queue(&hw->open_wait, &wait);
|
||||
mutex_lock(&hw->open_mutex);
|
||||
while (1) {
|
||||
if (hw->exclusive && hw->used > 0) {
|
||||
err = -EBUSY;
|
||||
break;
|
||||
}
|
||||
if (!hw->ops.open) {
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
err = hw->ops.open(hw, file);
|
||||
if (err >= 0)
|
||||
break;
|
||||
if (err == -EAGAIN) {
|
||||
if (file->f_flags & O_NONBLOCK) {
|
||||
err = -EBUSY;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
break;
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
mutex_unlock(&hw->open_mutex);
|
||||
schedule();
|
||||
mutex_lock(&hw->open_mutex);
|
||||
if (hw->card->shutdown) {
|
||||
err = -ENODEV;
|
||||
break;
|
||||
}
|
||||
if (signal_pending(current)) {
|
||||
err = -ERESTARTSYS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
remove_wait_queue(&hw->open_wait, &wait);
|
||||
if (err >= 0) {
|
||||
err = snd_card_file_add(hw->card, file);
|
||||
if (err >= 0) {
|
||||
file->private_data = hw;
|
||||
hw->used++;
|
||||
} else {
|
||||
if (hw->ops.release)
|
||||
hw->ops.release(hw, file);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&hw->open_mutex);
|
||||
if (err < 0)
|
||||
module_put(hw->card->module);
|
||||
snd_card_unref(hw->card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_hwdep_release(struct inode *inode, struct file * file)
|
||||
{
|
||||
int err = 0;
|
||||
struct snd_hwdep *hw = file->private_data;
|
||||
struct module *mod = hw->card->module;
|
||||
|
||||
mutex_lock(&hw->open_mutex);
|
||||
if (hw->ops.release)
|
||||
err = hw->ops.release(hw, file);
|
||||
if (hw->used > 0)
|
||||
hw->used--;
|
||||
mutex_unlock(&hw->open_mutex);
|
||||
wake_up(&hw->open_wait);
|
||||
|
||||
snd_card_file_remove(hw->card, file);
|
||||
module_put(mod);
|
||||
return err;
|
||||
}
|
||||
|
||||
static unsigned int snd_hwdep_poll(struct file * file, poll_table * wait)
|
||||
{
|
||||
struct snd_hwdep *hw = file->private_data;
|
||||
if (hw->ops.poll)
|
||||
return hw->ops.poll(hw, file, wait);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_hwdep_info(struct snd_hwdep *hw,
|
||||
struct snd_hwdep_info __user *_info)
|
||||
{
|
||||
struct snd_hwdep_info info;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.card = hw->card->number;
|
||||
strlcpy(info.id, hw->id, sizeof(info.id));
|
||||
strlcpy(info.name, hw->name, sizeof(info.name));
|
||||
info.iface = hw->iface;
|
||||
if (copy_to_user(_info, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_hwdep_dsp_status(struct snd_hwdep *hw,
|
||||
struct snd_hwdep_dsp_status __user *_info)
|
||||
{
|
||||
struct snd_hwdep_dsp_status info;
|
||||
int err;
|
||||
|
||||
if (! hw->ops.dsp_status)
|
||||
return -ENXIO;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.dsp_loaded = hw->dsp_loaded;
|
||||
if ((err = hw->ops.dsp_status(hw, &info)) < 0)
|
||||
return err;
|
||||
if (copy_to_user(_info, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_hwdep_dsp_load(struct snd_hwdep *hw,
|
||||
struct snd_hwdep_dsp_image __user *_info)
|
||||
{
|
||||
struct snd_hwdep_dsp_image info;
|
||||
int err;
|
||||
|
||||
if (! hw->ops.dsp_load)
|
||||
return -ENXIO;
|
||||
memset(&info, 0, sizeof(info));
|
||||
if (copy_from_user(&info, _info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
/* check whether the dsp was already loaded */
|
||||
if (hw->dsp_loaded & (1 << info.index))
|
||||
return -EBUSY;
|
||||
if (!access_ok(VERIFY_READ, info.image, info.length))
|
||||
return -EFAULT;
|
||||
err = hw->ops.dsp_load(hw, &info);
|
||||
if (err < 0)
|
||||
return err;
|
||||
hw->dsp_loaded |= (1 << info.index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long snd_hwdep_ioctl(struct file * file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct snd_hwdep *hw = file->private_data;
|
||||
void __user *argp = (void __user *)arg;
|
||||
switch (cmd) {
|
||||
case SNDRV_HWDEP_IOCTL_PVERSION:
|
||||
return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);
|
||||
case SNDRV_HWDEP_IOCTL_INFO:
|
||||
return snd_hwdep_info(hw, argp);
|
||||
case SNDRV_HWDEP_IOCTL_DSP_STATUS:
|
||||
return snd_hwdep_dsp_status(hw, argp);
|
||||
case SNDRV_HWDEP_IOCTL_DSP_LOAD:
|
||||
return snd_hwdep_dsp_load(hw, argp);
|
||||
}
|
||||
if (hw->ops.ioctl)
|
||||
return hw->ops.ioctl(hw, file, cmd, arg);
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma)
|
||||
{
|
||||
struct snd_hwdep *hw = file->private_data;
|
||||
if (hw->ops.mmap)
|
||||
return hw->ops.mmap(hw, file, vma);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static int snd_hwdep_control_ioctl(struct snd_card *card,
|
||||
struct snd_ctl_file * control,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:
|
||||
{
|
||||
int device;
|
||||
|
||||
if (get_user(device, (int __user *)arg))
|
||||
return -EFAULT;
|
||||
mutex_lock(®ister_mutex);
|
||||
|
||||
if (device < 0)
|
||||
device = 0;
|
||||
else if (device < SNDRV_MINOR_HWDEPS)
|
||||
device++;
|
||||
else
|
||||
device = SNDRV_MINOR_HWDEPS;
|
||||
|
||||
while (device < SNDRV_MINOR_HWDEPS) {
|
||||
if (snd_hwdep_search(card, device))
|
||||
break;
|
||||
device++;
|
||||
}
|
||||
if (device >= SNDRV_MINOR_HWDEPS)
|
||||
device = -1;
|
||||
mutex_unlock(®ister_mutex);
|
||||
if (put_user(device, (int __user *)arg))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
case SNDRV_CTL_IOCTL_HWDEP_INFO:
|
||||
{
|
||||
struct snd_hwdep_info __user *info = (struct snd_hwdep_info __user *)arg;
|
||||
int device, err;
|
||||
struct snd_hwdep *hwdep;
|
||||
|
||||
if (get_user(device, &info->device))
|
||||
return -EFAULT;
|
||||
mutex_lock(®ister_mutex);
|
||||
hwdep = snd_hwdep_search(card, device);
|
||||
if (hwdep)
|
||||
err = snd_hwdep_info(hwdep, info);
|
||||
else
|
||||
err = -ENXIO;
|
||||
mutex_unlock(®ister_mutex);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
#include "hwdep_compat.c"
|
||||
#else
|
||||
#define snd_hwdep_ioctl_compat NULL
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
static const struct file_operations snd_hwdep_f_ops =
|
||||
{
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = snd_hwdep_llseek,
|
||||
.read = snd_hwdep_read,
|
||||
.write = snd_hwdep_write,
|
||||
.open = snd_hwdep_open,
|
||||
.release = snd_hwdep_release,
|
||||
.poll = snd_hwdep_poll,
|
||||
.unlocked_ioctl = snd_hwdep_ioctl,
|
||||
.compat_ioctl = snd_hwdep_ioctl_compat,
|
||||
.mmap = snd_hwdep_mmap,
|
||||
};
|
||||
|
||||
/**
|
||||
* snd_hwdep_new - create a new hwdep instance
|
||||
* @card: the card instance
|
||||
* @id: the id string
|
||||
* @device: the device index (zero-based)
|
||||
* @rhwdep: the pointer to store the new hwdep instance
|
||||
*
|
||||
* Creates a new hwdep instance with the given index on the card.
|
||||
* The callbacks (hwdep->ops) must be set on the returned instance
|
||||
* after this call manually by the caller.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_hwdep_new(struct snd_card *card, char *id, int device,
|
||||
struct snd_hwdep **rhwdep)
|
||||
{
|
||||
struct snd_hwdep *hwdep;
|
||||
int err;
|
||||
static struct snd_device_ops ops = {
|
||||
.dev_free = snd_hwdep_dev_free,
|
||||
.dev_register = snd_hwdep_dev_register,
|
||||
.dev_disconnect = snd_hwdep_dev_disconnect,
|
||||
};
|
||||
|
||||
if (snd_BUG_ON(!card))
|
||||
return -ENXIO;
|
||||
if (rhwdep)
|
||||
*rhwdep = NULL;
|
||||
hwdep = kzalloc(sizeof(*hwdep), GFP_KERNEL);
|
||||
if (hwdep == NULL) {
|
||||
dev_err(card->dev, "hwdep: cannot allocate\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
hwdep->card = card;
|
||||
hwdep->device = device;
|
||||
if (id)
|
||||
strlcpy(hwdep->id, id, sizeof(hwdep->id));
|
||||
#ifdef CONFIG_SND_OSSEMUL
|
||||
hwdep->oss_type = -1;
|
||||
#endif
|
||||
if ((err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops)) < 0) {
|
||||
snd_hwdep_free(hwdep);
|
||||
return err;
|
||||
}
|
||||
init_waitqueue_head(&hwdep->open_wait);
|
||||
mutex_init(&hwdep->open_mutex);
|
||||
if (rhwdep)
|
||||
*rhwdep = hwdep;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_hwdep_new);
|
||||
|
||||
static int snd_hwdep_free(struct snd_hwdep *hwdep)
|
||||
{
|
||||
if (!hwdep)
|
||||
return 0;
|
||||
if (hwdep->private_free)
|
||||
hwdep->private_free(hwdep);
|
||||
kfree(hwdep);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_hwdep_dev_free(struct snd_device *device)
|
||||
{
|
||||
struct snd_hwdep *hwdep = device->device_data;
|
||||
return snd_hwdep_free(hwdep);
|
||||
}
|
||||
|
||||
static int snd_hwdep_dev_register(struct snd_device *device)
|
||||
{
|
||||
struct snd_hwdep *hwdep = device->device_data;
|
||||
struct snd_card *card = hwdep->card;
|
||||
struct device *dev;
|
||||
int err;
|
||||
char name[32];
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
if (snd_hwdep_search(card, hwdep->device)) {
|
||||
mutex_unlock(®ister_mutex);
|
||||
return -EBUSY;
|
||||
}
|
||||
list_add_tail(&hwdep->list, &snd_hwdep_devices);
|
||||
sprintf(name, "hwC%iD%i", hwdep->card->number, hwdep->device);
|
||||
dev = hwdep->dev;
|
||||
if (!dev)
|
||||
dev = snd_card_get_device_link(hwdep->card);
|
||||
err = snd_register_device_for_dev(SNDRV_DEVICE_TYPE_HWDEP,
|
||||
hwdep->card, hwdep->device,
|
||||
&snd_hwdep_f_ops, hwdep, name, dev);
|
||||
if (err < 0) {
|
||||
dev_err(dev,
|
||||
"unable to register hardware dependent device %i:%i\n",
|
||||
card->number, hwdep->device);
|
||||
list_del(&hwdep->list);
|
||||
mutex_unlock(®ister_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (hwdep->groups) {
|
||||
struct device *d = snd_get_device(SNDRV_DEVICE_TYPE_HWDEP,
|
||||
hwdep->card, hwdep->device);
|
||||
if (d) {
|
||||
if (hwdep->private_data)
|
||||
dev_set_drvdata(d, hwdep->private_data);
|
||||
err = sysfs_create_groups(&d->kobj, hwdep->groups);
|
||||
if (err < 0)
|
||||
dev_warn(dev,
|
||||
"hwdep %d:%d: cannot create sysfs groups\n",
|
||||
card->number, hwdep->device);
|
||||
put_device(d);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SND_OSSEMUL
|
||||
hwdep->ossreg = 0;
|
||||
if (hwdep->oss_type >= 0) {
|
||||
if ((hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM) && (hwdep->device != 0)) {
|
||||
dev_warn(dev,
|
||||
"only hwdep device 0 can be registered as OSS direct FM device!\n");
|
||||
} else {
|
||||
if (snd_register_oss_device(hwdep->oss_type,
|
||||
card, hwdep->device,
|
||||
&snd_hwdep_f_ops, hwdep) < 0) {
|
||||
dev_err(dev,
|
||||
"unable to register OSS compatibility device %i:%i\n",
|
||||
card->number, hwdep->device);
|
||||
} else
|
||||
hwdep->ossreg = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
mutex_unlock(®ister_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_hwdep_dev_disconnect(struct snd_device *device)
|
||||
{
|
||||
struct snd_hwdep *hwdep = device->device_data;
|
||||
|
||||
if (snd_BUG_ON(!hwdep))
|
||||
return -ENXIO;
|
||||
mutex_lock(®ister_mutex);
|
||||
if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep) {
|
||||
mutex_unlock(®ister_mutex);
|
||||
return -EINVAL;
|
||||
}
|
||||
mutex_lock(&hwdep->open_mutex);
|
||||
wake_up(&hwdep->open_wait);
|
||||
#ifdef CONFIG_SND_OSSEMUL
|
||||
if (hwdep->ossreg)
|
||||
snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device);
|
||||
#endif
|
||||
snd_unregister_device(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card, hwdep->device);
|
||||
list_del_init(&hwdep->list);
|
||||
mutex_unlock(&hwdep->open_mutex);
|
||||
mutex_unlock(®ister_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/*
|
||||
* Info interface
|
||||
*/
|
||||
|
||||
static void snd_hwdep_proc_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_hwdep *hwdep;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
list_for_each_entry(hwdep, &snd_hwdep_devices, list)
|
||||
snd_iprintf(buffer, "%02i-%02i: %s\n",
|
||||
hwdep->card->number, hwdep->device, hwdep->name);
|
||||
mutex_unlock(®ister_mutex);
|
||||
}
|
||||
|
||||
static struct snd_info_entry *snd_hwdep_proc_entry;
|
||||
|
||||
static void __init snd_hwdep_proc_init(void)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) {
|
||||
entry->c.text.read = snd_hwdep_proc_read;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
snd_hwdep_proc_entry = entry;
|
||||
}
|
||||
|
||||
static void __exit snd_hwdep_proc_done(void)
|
||||
{
|
||||
snd_info_free_entry(snd_hwdep_proc_entry);
|
||||
}
|
||||
#else /* !CONFIG_PROC_FS */
|
||||
#define snd_hwdep_proc_init()
|
||||
#define snd_hwdep_proc_done()
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
|
||||
/*
|
||||
* ENTRY functions
|
||||
*/
|
||||
|
||||
static int __init alsa_hwdep_init(void)
|
||||
{
|
||||
snd_hwdep_proc_init();
|
||||
snd_ctl_register_ioctl(snd_hwdep_control_ioctl);
|
||||
snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_hwdep_exit(void)
|
||||
{
|
||||
snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl);
|
||||
snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl);
|
||||
snd_hwdep_proc_done();
|
||||
}
|
||||
|
||||
module_init(alsa_hwdep_init)
|
||||
module_exit(alsa_hwdep_exit)
|
||||
78
sound/core/hwdep_compat.c
Normal file
78
sound/core/hwdep_compat.c
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 32bit -> 64bit ioctl wrapper for hwdep API
|
||||
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
/* This file is included from hwdep.c */
|
||||
|
||||
#include <linux/compat.h>
|
||||
|
||||
struct snd_hwdep_dsp_image32 {
|
||||
u32 index;
|
||||
unsigned char name[64];
|
||||
u32 image; /* pointer */
|
||||
u32 length;
|
||||
u32 driver_data;
|
||||
} /* don't set packed attribute here */;
|
||||
|
||||
static int snd_hwdep_dsp_load_compat(struct snd_hwdep *hw,
|
||||
struct snd_hwdep_dsp_image32 __user *src)
|
||||
{
|
||||
struct snd_hwdep_dsp_image __user *dst;
|
||||
compat_caddr_t ptr;
|
||||
u32 val;
|
||||
|
||||
dst = compat_alloc_user_space(sizeof(*dst));
|
||||
|
||||
/* index and name */
|
||||
if (copy_in_user(dst, src, 4 + 64))
|
||||
return -EFAULT;
|
||||
if (get_user(ptr, &src->image) ||
|
||||
put_user(compat_ptr(ptr), &dst->image))
|
||||
return -EFAULT;
|
||||
if (get_user(val, &src->length) ||
|
||||
put_user(val, &dst->length))
|
||||
return -EFAULT;
|
||||
if (get_user(val, &src->driver_data) ||
|
||||
put_user(val, &dst->driver_data))
|
||||
return -EFAULT;
|
||||
|
||||
return snd_hwdep_dsp_load(hw, dst);
|
||||
}
|
||||
|
||||
enum {
|
||||
SNDRV_HWDEP_IOCTL_DSP_LOAD32 = _IOW('H', 0x03, struct snd_hwdep_dsp_image32)
|
||||
};
|
||||
|
||||
static long snd_hwdep_ioctl_compat(struct file * file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct snd_hwdep *hw = file->private_data;
|
||||
void __user *argp = compat_ptr(arg);
|
||||
switch (cmd) {
|
||||
case SNDRV_HWDEP_IOCTL_PVERSION:
|
||||
case SNDRV_HWDEP_IOCTL_INFO:
|
||||
case SNDRV_HWDEP_IOCTL_DSP_STATUS:
|
||||
return snd_hwdep_ioctl(file, cmd, (unsigned long)argp);
|
||||
case SNDRV_HWDEP_IOCTL_DSP_LOAD32:
|
||||
return snd_hwdep_dsp_load_compat(hw, argp);
|
||||
}
|
||||
if (hw->ops.ioctl_compat)
|
||||
return hw->ops.ioctl_compat(hw, file, cmd, arg);
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
1010
sound/core/info.c
Normal file
1010
sound/core/info.c
Normal file
File diff suppressed because it is too large
Load diff
138
sound/core/info_oss.c
Normal file
138
sound/core/info_oss.c
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Information interface for ALSA driver
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/minors.h>
|
||||
#include <sound/info.h>
|
||||
#include <linux/utsname.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
|
||||
|
||||
/*
|
||||
* OSS compatible part
|
||||
*/
|
||||
|
||||
static DEFINE_MUTEX(strings);
|
||||
static char *snd_sndstat_strings[SNDRV_CARDS][SNDRV_OSS_INFO_DEV_COUNT];
|
||||
static struct snd_info_entry *snd_sndstat_proc_entry;
|
||||
|
||||
int snd_oss_info_register(int dev, int num, char *string)
|
||||
{
|
||||
char *x;
|
||||
|
||||
if (snd_BUG_ON(dev < 0 || dev >= SNDRV_OSS_INFO_DEV_COUNT))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(num < 0 || num >= SNDRV_CARDS))
|
||||
return -ENXIO;
|
||||
mutex_lock(&strings);
|
||||
if (string == NULL) {
|
||||
if ((x = snd_sndstat_strings[num][dev]) != NULL) {
|
||||
kfree(x);
|
||||
x = NULL;
|
||||
}
|
||||
} else {
|
||||
x = kstrdup(string, GFP_KERNEL);
|
||||
if (x == NULL) {
|
||||
mutex_unlock(&strings);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
snd_sndstat_strings[num][dev] = x;
|
||||
mutex_unlock(&strings);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_oss_info_register);
|
||||
|
||||
static int snd_sndstat_show_strings(struct snd_info_buffer *buf, char *id, int dev)
|
||||
{
|
||||
int idx, ok = -1;
|
||||
char *str;
|
||||
|
||||
snd_iprintf(buf, "\n%s:", id);
|
||||
mutex_lock(&strings);
|
||||
for (idx = 0; idx < SNDRV_CARDS; idx++) {
|
||||
str = snd_sndstat_strings[idx][dev];
|
||||
if (str) {
|
||||
if (ok < 0) {
|
||||
snd_iprintf(buf, "\n");
|
||||
ok++;
|
||||
}
|
||||
snd_iprintf(buf, "%i: %s\n", idx, str);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&strings);
|
||||
if (ok < 0)
|
||||
snd_iprintf(buf, " NOT ENABLED IN CONFIG\n");
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void snd_sndstat_proc_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
snd_iprintf(buffer, "Sound Driver:3.8.1a-980706 (ALSA emulation code)\n");
|
||||
snd_iprintf(buffer, "Kernel: %s %s %s %s %s\n",
|
||||
init_utsname()->sysname,
|
||||
init_utsname()->nodename,
|
||||
init_utsname()->release,
|
||||
init_utsname()->version,
|
||||
init_utsname()->machine);
|
||||
snd_iprintf(buffer, "Config options: 0\n");
|
||||
snd_iprintf(buffer, "\nInstalled drivers: \n");
|
||||
snd_iprintf(buffer, "Type 10: ALSA emulation\n");
|
||||
snd_iprintf(buffer, "\nCard config: \n");
|
||||
snd_card_info_read_oss(buffer);
|
||||
snd_sndstat_show_strings(buffer, "Audio devices", SNDRV_OSS_INFO_DEV_AUDIO);
|
||||
snd_sndstat_show_strings(buffer, "Synth devices", SNDRV_OSS_INFO_DEV_SYNTH);
|
||||
snd_sndstat_show_strings(buffer, "Midi devices", SNDRV_OSS_INFO_DEV_MIDI);
|
||||
snd_sndstat_show_strings(buffer, "Timers", SNDRV_OSS_INFO_DEV_TIMERS);
|
||||
snd_sndstat_show_strings(buffer, "Mixers", SNDRV_OSS_INFO_DEV_MIXERS);
|
||||
}
|
||||
|
||||
int snd_info_minor_register(void)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
memset(snd_sndstat_strings, 0, sizeof(snd_sndstat_strings));
|
||||
if ((entry = snd_info_create_module_entry(THIS_MODULE, "sndstat", snd_oss_root)) != NULL) {
|
||||
entry->c.text.read = snd_sndstat_proc_read;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
snd_sndstat_proc_entry = entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_info_minor_unregister(void)
|
||||
{
|
||||
snd_info_free_entry(snd_sndstat_proc_entry);
|
||||
snd_sndstat_proc_entry = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SND_OSSEMUL */
|
||||
990
sound/core/init.c
Normal file
990
sound/core/init.c
Normal file
|
|
@ -0,0 +1,990 @@
|
|||
/*
|
||||
* Initialization routines
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/completion.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/info.h>
|
||||
|
||||
/* monitor files for graceful shutdown (hotplug) */
|
||||
struct snd_monitor_file {
|
||||
struct file *file;
|
||||
const struct file_operations *disconnected_f_op;
|
||||
struct list_head shutdown_list; /* still need to shutdown */
|
||||
struct list_head list; /* link of monitor files */
|
||||
};
|
||||
|
||||
static DEFINE_SPINLOCK(shutdown_lock);
|
||||
static LIST_HEAD(shutdown_files);
|
||||
|
||||
static const struct file_operations snd_shutdown_f_ops;
|
||||
|
||||
/* locked for registering/using */
|
||||
static DECLARE_BITMAP(snd_cards_lock, SNDRV_CARDS);
|
||||
struct snd_card *snd_cards[SNDRV_CARDS];
|
||||
EXPORT_SYMBOL(snd_cards);
|
||||
|
||||
static DEFINE_MUTEX(snd_card_mutex);
|
||||
|
||||
static char *slots[SNDRV_CARDS];
|
||||
module_param_array(slots, charp, NULL, 0444);
|
||||
MODULE_PARM_DESC(slots, "Module names assigned to the slots.");
|
||||
|
||||
/* return non-zero if the given index is reserved for the given
|
||||
* module via slots option
|
||||
*/
|
||||
static int module_slot_match(struct module *module, int idx)
|
||||
{
|
||||
int match = 1;
|
||||
#ifdef MODULE
|
||||
const char *s1, *s2;
|
||||
|
||||
if (!module || !*module->name || !slots[idx])
|
||||
return 0;
|
||||
|
||||
s1 = module->name;
|
||||
s2 = slots[idx];
|
||||
if (*s2 == '!') {
|
||||
match = 0; /* negative match */
|
||||
s2++;
|
||||
}
|
||||
/* compare module name strings
|
||||
* hyphens are handled as equivalent with underscore
|
||||
*/
|
||||
for (;;) {
|
||||
char c1 = *s1++;
|
||||
char c2 = *s2++;
|
||||
if (c1 == '-')
|
||||
c1 = '_';
|
||||
if (c2 == '-')
|
||||
c2 = '_';
|
||||
if (c1 != c2)
|
||||
return !match;
|
||||
if (!c1)
|
||||
break;
|
||||
}
|
||||
#endif /* MODULE */
|
||||
return match;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
|
||||
int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag);
|
||||
EXPORT_SYMBOL(snd_mixer_oss_notify_callback);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static void snd_card_id_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
snd_iprintf(buffer, "%s\n", entry->card->id);
|
||||
}
|
||||
|
||||
static inline int init_info_for_card(struct snd_card *card)
|
||||
{
|
||||
int err;
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
if ((err = snd_info_card_register(card)) < 0) {
|
||||
dev_dbg(card->dev, "unable to create card info\n");
|
||||
return err;
|
||||
}
|
||||
if ((entry = snd_info_create_card_entry(card, "id", card->proc_root)) == NULL) {
|
||||
dev_dbg(card->dev, "unable to create card entry\n");
|
||||
return err;
|
||||
}
|
||||
entry->c.text.read = snd_card_id_read;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
card->proc_id = entry;
|
||||
return 0;
|
||||
}
|
||||
#else /* !CONFIG_PROC_FS */
|
||||
#define init_info_for_card(card)
|
||||
#endif
|
||||
|
||||
static int check_empty_slot(struct module *module, int slot)
|
||||
{
|
||||
return !slots[slot] || !*slots[slot];
|
||||
}
|
||||
|
||||
/* return an empty slot number (>= 0) found in the given bitmask @mask.
|
||||
* @mask == -1 == 0xffffffff means: take any free slot up to 32
|
||||
* when no slot is available, return the original @mask as is.
|
||||
*/
|
||||
static int get_slot_from_bitmask(int mask, int (*check)(struct module *, int),
|
||||
struct module *module)
|
||||
{
|
||||
int slot;
|
||||
|
||||
for (slot = 0; slot < SNDRV_CARDS; slot++) {
|
||||
if (slot < 32 && !(mask & (1U << slot)))
|
||||
continue;
|
||||
if (!test_bit(slot, snd_cards_lock)) {
|
||||
if (check(module, slot))
|
||||
return slot; /* found */
|
||||
}
|
||||
}
|
||||
return mask; /* unchanged */
|
||||
}
|
||||
|
||||
static int snd_card_do_free(struct snd_card *card);
|
||||
static const struct attribute_group *card_dev_attr_groups[];
|
||||
|
||||
static void release_card_device(struct device *dev)
|
||||
{
|
||||
snd_card_do_free(dev_to_snd_card(dev));
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_card_new - create and initialize a soundcard structure
|
||||
* @parent: the parent device object
|
||||
* @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
|
||||
* @xid: card identification (ASCII string)
|
||||
* @module: top level module for locking
|
||||
* @extra_size: allocate this extra size after the main soundcard structure
|
||||
* @card_ret: the pointer to store the created card instance
|
||||
*
|
||||
* Creates and initializes a soundcard structure.
|
||||
*
|
||||
* The function allocates snd_card instance via kzalloc with the given
|
||||
* space for the driver to use freely. The allocated struct is stored
|
||||
* in the given card_ret pointer.
|
||||
*
|
||||
* Return: Zero if successful or a negative error code.
|
||||
*/
|
||||
int snd_card_new(struct device *parent, int idx, const char *xid,
|
||||
struct module *module, int extra_size,
|
||||
struct snd_card **card_ret)
|
||||
{
|
||||
struct snd_card *card;
|
||||
int err;
|
||||
|
||||
if (snd_BUG_ON(!card_ret))
|
||||
return -EINVAL;
|
||||
*card_ret = NULL;
|
||||
|
||||
if (extra_size < 0)
|
||||
extra_size = 0;
|
||||
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
|
||||
if (!card)
|
||||
return -ENOMEM;
|
||||
if (extra_size > 0)
|
||||
card->private_data = (char *)card + sizeof(struct snd_card);
|
||||
if (xid)
|
||||
strlcpy(card->id, xid, sizeof(card->id));
|
||||
err = 0;
|
||||
mutex_lock(&snd_card_mutex);
|
||||
if (idx < 0) /* first check the matching module-name slot */
|
||||
idx = get_slot_from_bitmask(idx, module_slot_match, module);
|
||||
if (idx < 0) /* if not matched, assign an empty slot */
|
||||
idx = get_slot_from_bitmask(idx, check_empty_slot, module);
|
||||
if (idx < 0)
|
||||
err = -ENODEV;
|
||||
else if (idx < snd_ecards_limit) {
|
||||
if (test_bit(idx, snd_cards_lock))
|
||||
err = -EBUSY; /* invalid */
|
||||
} else if (idx >= SNDRV_CARDS)
|
||||
err = -ENODEV;
|
||||
if (err < 0) {
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
dev_err(parent, "cannot find the slot for index %d (range 0-%i), error: %d\n",
|
||||
idx, snd_ecards_limit - 1, err);
|
||||
kfree(card);
|
||||
return err;
|
||||
}
|
||||
set_bit(idx, snd_cards_lock); /* lock it */
|
||||
if (idx >= snd_ecards_limit)
|
||||
snd_ecards_limit = idx + 1; /* increase the limit */
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
card->dev = parent;
|
||||
card->number = idx;
|
||||
card->module = module;
|
||||
INIT_LIST_HEAD(&card->devices);
|
||||
init_rwsem(&card->controls_rwsem);
|
||||
rwlock_init(&card->ctl_files_rwlock);
|
||||
mutex_init(&card->user_ctl_lock);
|
||||
INIT_LIST_HEAD(&card->controls);
|
||||
INIT_LIST_HEAD(&card->ctl_files);
|
||||
spin_lock_init(&card->files_lock);
|
||||
INIT_LIST_HEAD(&card->files_list);
|
||||
#ifdef CONFIG_PM
|
||||
mutex_init(&card->power_lock);
|
||||
init_waitqueue_head(&card->power_sleep);
|
||||
#endif
|
||||
|
||||
device_initialize(&card->card_dev);
|
||||
card->card_dev.parent = parent;
|
||||
card->card_dev.class = sound_class;
|
||||
card->card_dev.release = release_card_device;
|
||||
card->card_dev.groups = card_dev_attr_groups;
|
||||
err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);
|
||||
if (err < 0)
|
||||
goto __error;
|
||||
|
||||
/* the control interface cannot be accessed from the user space until */
|
||||
/* snd_cards_bitmask and snd_cards are set with snd_card_register */
|
||||
err = snd_ctl_create(card);
|
||||
if (err < 0) {
|
||||
dev_err(parent, "unable to register control minors\n");
|
||||
goto __error;
|
||||
}
|
||||
err = snd_info_card_create(card);
|
||||
if (err < 0) {
|
||||
dev_err(parent, "unable to create card info\n");
|
||||
goto __error_ctl;
|
||||
}
|
||||
*card_ret = card;
|
||||
return 0;
|
||||
|
||||
__error_ctl:
|
||||
snd_device_free_all(card);
|
||||
__error:
|
||||
put_device(&card->card_dev);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_card_new);
|
||||
|
||||
/* return non-zero if a card is already locked */
|
||||
int snd_card_locked(int card)
|
||||
{
|
||||
int locked;
|
||||
|
||||
mutex_lock(&snd_card_mutex);
|
||||
locked = test_bit(card, snd_cards_lock);
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
return locked;
|
||||
}
|
||||
|
||||
static loff_t snd_disconnect_llseek(struct file *file, loff_t offset, int orig)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static ssize_t snd_disconnect_read(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *offset)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static ssize_t snd_disconnect_write(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *offset)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int snd_disconnect_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct snd_monitor_file *df = NULL, *_df;
|
||||
|
||||
spin_lock(&shutdown_lock);
|
||||
list_for_each_entry(_df, &shutdown_files, shutdown_list) {
|
||||
if (_df->file == file) {
|
||||
df = _df;
|
||||
list_del_init(&df->shutdown_list);
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock(&shutdown_lock);
|
||||
|
||||
if (likely(df)) {
|
||||
if ((file->f_flags & FASYNC) && df->disconnected_f_op->fasync)
|
||||
df->disconnected_f_op->fasync(-1, file, 0);
|
||||
return df->disconnected_f_op->release(inode, file);
|
||||
}
|
||||
|
||||
panic("%s(%p, %p) failed!", __func__, inode, file);
|
||||
}
|
||||
|
||||
static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait)
|
||||
{
|
||||
return POLLERR | POLLNVAL;
|
||||
}
|
||||
|
||||
static long snd_disconnect_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int snd_disconnect_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int snd_disconnect_fasync(int fd, struct file *file, int on)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static const struct file_operations snd_shutdown_f_ops =
|
||||
{
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = snd_disconnect_llseek,
|
||||
.read = snd_disconnect_read,
|
||||
.write = snd_disconnect_write,
|
||||
.release = snd_disconnect_release,
|
||||
.poll = snd_disconnect_poll,
|
||||
.unlocked_ioctl = snd_disconnect_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = snd_disconnect_ioctl,
|
||||
#endif
|
||||
.mmap = snd_disconnect_mmap,
|
||||
.fasync = snd_disconnect_fasync
|
||||
};
|
||||
|
||||
/**
|
||||
* snd_card_disconnect - disconnect all APIs from the file-operations (user space)
|
||||
* @card: soundcard structure
|
||||
*
|
||||
* Disconnects all APIs from the file-operations (user space).
|
||||
*
|
||||
* Return: Zero, otherwise a negative error code.
|
||||
*
|
||||
* Note: The current implementation replaces all active file->f_op with special
|
||||
* dummy file operations (they do nothing except release).
|
||||
*/
|
||||
int snd_card_disconnect(struct snd_card *card)
|
||||
{
|
||||
struct snd_monitor_file *mfile;
|
||||
int err;
|
||||
|
||||
if (!card)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock(&card->files_lock);
|
||||
if (card->shutdown) {
|
||||
spin_unlock(&card->files_lock);
|
||||
return 0;
|
||||
}
|
||||
card->shutdown = 1;
|
||||
spin_unlock(&card->files_lock);
|
||||
|
||||
/* phase 1: disable fops (user space) operations for ALSA API */
|
||||
mutex_lock(&snd_card_mutex);
|
||||
snd_cards[card->number] = NULL;
|
||||
clear_bit(card->number, snd_cards_lock);
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
|
||||
/* phase 2: replace file->f_op with special dummy operations */
|
||||
|
||||
spin_lock(&card->files_lock);
|
||||
list_for_each_entry(mfile, &card->files_list, list) {
|
||||
/* it's critical part, use endless loop */
|
||||
/* we have no room to fail */
|
||||
mfile->disconnected_f_op = mfile->file->f_op;
|
||||
|
||||
spin_lock(&shutdown_lock);
|
||||
list_add(&mfile->shutdown_list, &shutdown_files);
|
||||
spin_unlock(&shutdown_lock);
|
||||
|
||||
mfile->file->f_op = &snd_shutdown_f_ops;
|
||||
fops_get(mfile->file->f_op);
|
||||
}
|
||||
spin_unlock(&card->files_lock);
|
||||
|
||||
/* phase 3: notify all connected devices about disconnection */
|
||||
/* at this point, they cannot respond to any calls except release() */
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
|
||||
if (snd_mixer_oss_notify_callback)
|
||||
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT);
|
||||
#endif
|
||||
|
||||
/* notify all devices that we are disconnected */
|
||||
err = snd_device_disconnect_all(card);
|
||||
if (err < 0)
|
||||
dev_err(card->dev, "not all devices for card %i can be disconnected\n", card->number);
|
||||
|
||||
snd_info_card_disconnect(card);
|
||||
if (card->registered) {
|
||||
device_del(&card->card_dev);
|
||||
card->registered = false;
|
||||
}
|
||||
#ifdef CONFIG_PM
|
||||
wake_up(&card->power_sleep);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_card_disconnect);
|
||||
|
||||
/**
|
||||
* snd_card_free - frees given soundcard structure
|
||||
* @card: soundcard structure
|
||||
*
|
||||
* This function releases the soundcard structure and the all assigned
|
||||
* devices automatically. That is, you don't have to release the devices
|
||||
* by yourself.
|
||||
*
|
||||
* Return: Zero. Frees all associated devices and frees the control
|
||||
* interface associated to given soundcard.
|
||||
*/
|
||||
static int snd_card_do_free(struct snd_card *card)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
|
||||
if (snd_mixer_oss_notify_callback)
|
||||
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE);
|
||||
#endif
|
||||
snd_device_free_all(card);
|
||||
if (card->private_free)
|
||||
card->private_free(card);
|
||||
snd_info_free_entry(card->proc_id);
|
||||
if (snd_info_card_free(card) < 0) {
|
||||
dev_warn(card->dev, "unable to free card info\n");
|
||||
/* Not fatal error */
|
||||
}
|
||||
if (card->release_completion)
|
||||
complete(card->release_completion);
|
||||
kfree(card);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_card_free_when_closed(struct snd_card *card)
|
||||
{
|
||||
int ret = snd_card_disconnect(card);
|
||||
if (ret)
|
||||
return ret;
|
||||
put_device(&card->card_dev);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_card_free_when_closed);
|
||||
|
||||
int snd_card_free(struct snd_card *card)
|
||||
{
|
||||
struct completion released;
|
||||
int ret;
|
||||
|
||||
init_completion(&released);
|
||||
card->release_completion = &released;
|
||||
ret = snd_card_free_when_closed(card);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* wait, until all devices are ready for the free operation */
|
||||
wait_for_completion(&released);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_card_free);
|
||||
|
||||
/* retrieve the last word of shortname or longname */
|
||||
static const char *retrieve_id_from_card_name(const char *name)
|
||||
{
|
||||
const char *spos = name;
|
||||
|
||||
while (*name) {
|
||||
if (isspace(*name) && isalnum(name[1]))
|
||||
spos = name + 1;
|
||||
name++;
|
||||
}
|
||||
return spos;
|
||||
}
|
||||
|
||||
/* return true if the given id string doesn't conflict any other card ids */
|
||||
static bool card_id_ok(struct snd_card *card, const char *id)
|
||||
{
|
||||
int i;
|
||||
if (!snd_info_check_reserved_words(id))
|
||||
return false;
|
||||
for (i = 0; i < snd_ecards_limit; i++) {
|
||||
if (snd_cards[i] && snd_cards[i] != card &&
|
||||
!strcmp(snd_cards[i]->id, id))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* copy to card->id only with valid letters from nid */
|
||||
static void copy_valid_id_string(struct snd_card *card, const char *src,
|
||||
const char *nid)
|
||||
{
|
||||
char *id = card->id;
|
||||
|
||||
while (*nid && !isalnum(*nid))
|
||||
nid++;
|
||||
if (isdigit(*nid))
|
||||
*id++ = isalpha(*src) ? *src : 'D';
|
||||
while (*nid && (size_t)(id - card->id) < sizeof(card->id) - 1) {
|
||||
if (isalnum(*nid))
|
||||
*id++ = *nid;
|
||||
nid++;
|
||||
}
|
||||
*id = 0;
|
||||
}
|
||||
|
||||
/* Set card->id from the given string
|
||||
* If the string conflicts with other ids, add a suffix to make it unique.
|
||||
*/
|
||||
static void snd_card_set_id_no_lock(struct snd_card *card, const char *src,
|
||||
const char *nid)
|
||||
{
|
||||
int len, loops;
|
||||
bool is_default = false;
|
||||
char *id;
|
||||
|
||||
copy_valid_id_string(card, src, nid);
|
||||
id = card->id;
|
||||
|
||||
again:
|
||||
/* use "Default" for obviously invalid strings
|
||||
* ("card" conflicts with proc directories)
|
||||
*/
|
||||
if (!*id || !strncmp(id, "card", 4)) {
|
||||
strcpy(id, "Default");
|
||||
is_default = true;
|
||||
}
|
||||
|
||||
len = strlen(id);
|
||||
for (loops = 0; loops < SNDRV_CARDS; loops++) {
|
||||
char *spos;
|
||||
char sfxstr[5]; /* "_012" */
|
||||
int sfxlen;
|
||||
|
||||
if (card_id_ok(card, id))
|
||||
return; /* OK */
|
||||
|
||||
/* Add _XYZ suffix */
|
||||
sprintf(sfxstr, "_%X", loops + 1);
|
||||
sfxlen = strlen(sfxstr);
|
||||
if (len + sfxlen >= sizeof(card->id))
|
||||
spos = id + sizeof(card->id) - sfxlen - 1;
|
||||
else
|
||||
spos = id + len;
|
||||
strcpy(spos, sfxstr);
|
||||
}
|
||||
/* fallback to the default id */
|
||||
if (!is_default) {
|
||||
*id = 0;
|
||||
goto again;
|
||||
}
|
||||
/* last resort... */
|
||||
dev_err(card->dev, "unable to set card id (%s)\n", id);
|
||||
if (card->proc_root->name)
|
||||
strlcpy(card->id, card->proc_root->name, sizeof(card->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_card_set_id - set card identification name
|
||||
* @card: soundcard structure
|
||||
* @nid: new identification string
|
||||
*
|
||||
* This function sets the card identification and checks for name
|
||||
* collisions.
|
||||
*/
|
||||
void snd_card_set_id(struct snd_card *card, const char *nid)
|
||||
{
|
||||
/* check if user specified own card->id */
|
||||
if (card->id[0] != '\0')
|
||||
return;
|
||||
mutex_lock(&snd_card_mutex);
|
||||
snd_card_set_id_no_lock(card, nid, nid);
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_card_set_id);
|
||||
|
||||
static ssize_t
|
||||
card_id_show_attr(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct snd_card *card = container_of(dev, struct snd_card, card_dev);
|
||||
return snprintf(buf, PAGE_SIZE, "%s\n", card->id);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
card_id_store_attr(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct snd_card *card = container_of(dev, struct snd_card, card_dev);
|
||||
char buf1[sizeof(card->id)];
|
||||
size_t copy = count > sizeof(card->id) - 1 ?
|
||||
sizeof(card->id) - 1 : count;
|
||||
size_t idx;
|
||||
int c;
|
||||
|
||||
for (idx = 0; idx < copy; idx++) {
|
||||
c = buf[idx];
|
||||
if (!isalnum(c) && c != '_' && c != '-')
|
||||
return -EINVAL;
|
||||
}
|
||||
memcpy(buf1, buf, copy);
|
||||
buf1[copy] = '\0';
|
||||
mutex_lock(&snd_card_mutex);
|
||||
if (!card_id_ok(NULL, buf1)) {
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
return -EEXIST;
|
||||
}
|
||||
strcpy(card->id, buf1);
|
||||
snd_info_card_id_change(card);
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(id, S_IRUGO | S_IWUSR, card_id_show_attr, card_id_store_attr);
|
||||
|
||||
static ssize_t
|
||||
card_number_show_attr(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct snd_card *card = container_of(dev, struct snd_card, card_dev);
|
||||
return snprintf(buf, PAGE_SIZE, "%i\n", card->number);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(number, S_IRUGO, card_number_show_attr, NULL);
|
||||
|
||||
static struct attribute *card_dev_attrs[] = {
|
||||
&dev_attr_id.attr,
|
||||
&dev_attr_number.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group card_dev_attr_group = {
|
||||
.attrs = card_dev_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *card_dev_attr_groups[] = {
|
||||
&card_dev_attr_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* snd_card_register - register the soundcard
|
||||
* @card: soundcard structure
|
||||
*
|
||||
* This function registers all the devices assigned to the soundcard.
|
||||
* Until calling this, the ALSA control interface is blocked from the
|
||||
* external accesses. Thus, you should call this function at the end
|
||||
* of the initialization of the card.
|
||||
*
|
||||
* Return: Zero otherwise a negative error code if the registration failed.
|
||||
*/
|
||||
int snd_card_register(struct snd_card *card)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (snd_BUG_ON(!card))
|
||||
return -EINVAL;
|
||||
|
||||
if (!card->registered) {
|
||||
err = device_add(&card->card_dev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
card->registered = true;
|
||||
}
|
||||
|
||||
if ((err = snd_device_register_all(card)) < 0)
|
||||
return err;
|
||||
mutex_lock(&snd_card_mutex);
|
||||
if (snd_cards[card->number]) {
|
||||
/* already registered */
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
return 0;
|
||||
}
|
||||
if (*card->id) {
|
||||
/* make a unique id name from the given string */
|
||||
char tmpid[sizeof(card->id)];
|
||||
memcpy(tmpid, card->id, sizeof(card->id));
|
||||
snd_card_set_id_no_lock(card, tmpid, tmpid);
|
||||
} else {
|
||||
/* create an id from either shortname or longname */
|
||||
const char *src;
|
||||
src = *card->shortname ? card->shortname : card->longname;
|
||||
snd_card_set_id_no_lock(card, src,
|
||||
retrieve_id_from_card_name(src));
|
||||
}
|
||||
snd_cards[card->number] = card;
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
init_info_for_card(card);
|
||||
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
|
||||
if (snd_mixer_oss_notify_callback)
|
||||
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_card_register);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static struct snd_info_entry *snd_card_info_entry;
|
||||
|
||||
static void snd_card_info_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
int idx, count;
|
||||
struct snd_card *card;
|
||||
|
||||
for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
|
||||
mutex_lock(&snd_card_mutex);
|
||||
if ((card = snd_cards[idx]) != NULL) {
|
||||
count++;
|
||||
snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n",
|
||||
idx,
|
||||
card->id,
|
||||
card->driver,
|
||||
card->shortname);
|
||||
snd_iprintf(buffer, " %s\n",
|
||||
card->longname);
|
||||
}
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
}
|
||||
if (!count)
|
||||
snd_iprintf(buffer, "--- no soundcards ---\n");
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SND_OSSEMUL
|
||||
|
||||
void snd_card_info_read_oss(struct snd_info_buffer *buffer)
|
||||
{
|
||||
int idx, count;
|
||||
struct snd_card *card;
|
||||
|
||||
for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
|
||||
mutex_lock(&snd_card_mutex);
|
||||
if ((card = snd_cards[idx]) != NULL) {
|
||||
count++;
|
||||
snd_iprintf(buffer, "%s\n", card->longname);
|
||||
}
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
}
|
||||
if (!count) {
|
||||
snd_iprintf(buffer, "--- no soundcards ---\n");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MODULE
|
||||
static struct snd_info_entry *snd_card_module_info_entry;
|
||||
static void snd_card_module_info_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
int idx;
|
||||
struct snd_card *card;
|
||||
|
||||
for (idx = 0; idx < SNDRV_CARDS; idx++) {
|
||||
mutex_lock(&snd_card_mutex);
|
||||
if ((card = snd_cards[idx]) != NULL)
|
||||
snd_iprintf(buffer, "%2i %s\n",
|
||||
idx, card->module->name);
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int __init snd_card_info_init(void)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL);
|
||||
if (! entry)
|
||||
return -ENOMEM;
|
||||
entry->c.text.read = snd_card_info_read;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
return -ENOMEM;
|
||||
}
|
||||
snd_card_info_entry = entry;
|
||||
|
||||
#ifdef MODULE
|
||||
entry = snd_info_create_module_entry(THIS_MODULE, "modules", NULL);
|
||||
if (entry) {
|
||||
entry->c.text.read = snd_card_module_info_read;
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
else
|
||||
snd_card_module_info_entry = entry;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __exit snd_card_info_done(void)
|
||||
{
|
||||
snd_info_free_entry(snd_card_info_entry);
|
||||
#ifdef MODULE
|
||||
snd_info_free_entry(snd_card_module_info_entry);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
/**
|
||||
* snd_component_add - add a component string
|
||||
* @card: soundcard structure
|
||||
* @component: the component id string
|
||||
*
|
||||
* This function adds the component id string to the supported list.
|
||||
* The component can be referred from the alsa-lib.
|
||||
*
|
||||
* Return: Zero otherwise a negative error code.
|
||||
*/
|
||||
|
||||
int snd_component_add(struct snd_card *card, const char *component)
|
||||
{
|
||||
char *ptr;
|
||||
int len = strlen(component);
|
||||
|
||||
ptr = strstr(card->components, component);
|
||||
if (ptr != NULL) {
|
||||
if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */
|
||||
return 1;
|
||||
}
|
||||
if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) {
|
||||
snd_BUG();
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (card->components[0] != '\0')
|
||||
strcat(card->components, " ");
|
||||
strcat(card->components, component);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_component_add);
|
||||
|
||||
/**
|
||||
* snd_card_file_add - add the file to the file list of the card
|
||||
* @card: soundcard structure
|
||||
* @file: file pointer
|
||||
*
|
||||
* This function adds the file to the file linked-list of the card.
|
||||
* This linked-list is used to keep tracking the connection state,
|
||||
* and to avoid the release of busy resources by hotplug.
|
||||
*
|
||||
* Return: zero or a negative error code.
|
||||
*/
|
||||
int snd_card_file_add(struct snd_card *card, struct file *file)
|
||||
{
|
||||
struct snd_monitor_file *mfile;
|
||||
|
||||
mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
|
||||
if (mfile == NULL)
|
||||
return -ENOMEM;
|
||||
mfile->file = file;
|
||||
mfile->disconnected_f_op = NULL;
|
||||
INIT_LIST_HEAD(&mfile->shutdown_list);
|
||||
spin_lock(&card->files_lock);
|
||||
if (card->shutdown) {
|
||||
spin_unlock(&card->files_lock);
|
||||
kfree(mfile);
|
||||
return -ENODEV;
|
||||
}
|
||||
list_add(&mfile->list, &card->files_list);
|
||||
get_device(&card->card_dev);
|
||||
spin_unlock(&card->files_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_card_file_add);
|
||||
|
||||
/**
|
||||
* snd_card_file_remove - remove the file from the file list
|
||||
* @card: soundcard structure
|
||||
* @file: file pointer
|
||||
*
|
||||
* This function removes the file formerly added to the card via
|
||||
* snd_card_file_add() function.
|
||||
* If all files are removed and snd_card_free_when_closed() was
|
||||
* called beforehand, it processes the pending release of
|
||||
* resources.
|
||||
*
|
||||
* Return: Zero or a negative error code.
|
||||
*/
|
||||
int snd_card_file_remove(struct snd_card *card, struct file *file)
|
||||
{
|
||||
struct snd_monitor_file *mfile, *found = NULL;
|
||||
|
||||
spin_lock(&card->files_lock);
|
||||
list_for_each_entry(mfile, &card->files_list, list) {
|
||||
if (mfile->file == file) {
|
||||
list_del(&mfile->list);
|
||||
spin_lock(&shutdown_lock);
|
||||
list_del(&mfile->shutdown_list);
|
||||
spin_unlock(&shutdown_lock);
|
||||
if (mfile->disconnected_f_op)
|
||||
fops_put(mfile->disconnected_f_op);
|
||||
found = mfile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock(&card->files_lock);
|
||||
if (!found) {
|
||||
dev_err(card->dev, "card file remove problem (%p)\n", file);
|
||||
return -ENOENT;
|
||||
}
|
||||
kfree(found);
|
||||
put_device(&card->card_dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_card_file_remove);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
/**
|
||||
* snd_power_wait - wait until the power-state is changed.
|
||||
* @card: soundcard structure
|
||||
* @power_state: expected power state
|
||||
*
|
||||
* Waits until the power-state is changed.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code.
|
||||
*
|
||||
* Note: the power lock must be active before call.
|
||||
*/
|
||||
int snd_power_wait(struct snd_card *card, unsigned int power_state)
|
||||
{
|
||||
wait_queue_t wait;
|
||||
int result = 0;
|
||||
|
||||
/* fastpath */
|
||||
if (snd_power_get_state(card) == power_state)
|
||||
return 0;
|
||||
init_waitqueue_entry(&wait, current);
|
||||
add_wait_queue(&card->power_sleep, &wait);
|
||||
while (1) {
|
||||
if (card->shutdown) {
|
||||
result = -ENODEV;
|
||||
break;
|
||||
}
|
||||
if (snd_power_get_state(card) == power_state)
|
||||
break;
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
snd_power_unlock(card);
|
||||
schedule_timeout(30 * HZ);
|
||||
snd_power_lock(card);
|
||||
}
|
||||
remove_wait_queue(&card->power_sleep, &wait);
|
||||
return result;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_power_wait);
|
||||
#endif /* CONFIG_PM */
|
||||
117
sound/core/isadma.c
Normal file
117
sound/core/isadma.c
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* ISA DMA support functions
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Defining following add some delay. Maybe this helps for some broken
|
||||
* ISA DMA controllers.
|
||||
*/
|
||||
|
||||
#undef HAVE_REALLY_SLOW_DMA_CONTROLLER
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <sound/core.h>
|
||||
#include <asm/dma.h>
|
||||
|
||||
/**
|
||||
* snd_dma_program - program an ISA DMA transfer
|
||||
* @dma: the dma number
|
||||
* @addr: the physical address of the buffer
|
||||
* @size: the DMA transfer size
|
||||
* @mode: the DMA transfer mode, DMA_MODE_XXX
|
||||
*
|
||||
* Programs an ISA DMA transfer for the given buffer.
|
||||
*/
|
||||
void snd_dma_program(unsigned long dma,
|
||||
unsigned long addr, unsigned int size,
|
||||
unsigned short mode)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
flags = claim_dma_lock();
|
||||
disable_dma(dma);
|
||||
clear_dma_ff(dma);
|
||||
set_dma_mode(dma, mode);
|
||||
set_dma_addr(dma, addr);
|
||||
set_dma_count(dma, size);
|
||||
if (!(mode & DMA_MODE_NO_ENABLE))
|
||||
enable_dma(dma);
|
||||
release_dma_lock(flags);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_dma_program);
|
||||
|
||||
/**
|
||||
* snd_dma_disable - stop the ISA DMA transfer
|
||||
* @dma: the dma number
|
||||
*
|
||||
* Stops the ISA DMA transfer.
|
||||
*/
|
||||
void snd_dma_disable(unsigned long dma)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
flags = claim_dma_lock();
|
||||
clear_dma_ff(dma);
|
||||
disable_dma(dma);
|
||||
release_dma_lock(flags);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_dma_disable);
|
||||
|
||||
/**
|
||||
* snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes
|
||||
* @dma: the dma number
|
||||
* @size: the dma transfer size
|
||||
*
|
||||
* Return: The current pointer in DMA transfer buffer in bytes.
|
||||
*/
|
||||
unsigned int snd_dma_pointer(unsigned long dma, unsigned int size)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned int result, result1;
|
||||
|
||||
flags = claim_dma_lock();
|
||||
clear_dma_ff(dma);
|
||||
if (!isa_dma_bridge_buggy)
|
||||
disable_dma(dma);
|
||||
result = get_dma_residue(dma);
|
||||
/*
|
||||
* HACK - read the counter again and choose higher value in order to
|
||||
* avoid reading during counter lower byte roll over if the
|
||||
* isa_dma_bridge_buggy is set.
|
||||
*/
|
||||
result1 = get_dma_residue(dma);
|
||||
if (!isa_dma_bridge_buggy)
|
||||
enable_dma(dma);
|
||||
release_dma_lock(flags);
|
||||
if (unlikely(result < result1))
|
||||
result = result1;
|
||||
#ifdef CONFIG_SND_DEBUG
|
||||
if (result > size)
|
||||
pr_err("ALSA: pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size);
|
||||
#endif
|
||||
if (result >= size || result == 0)
|
||||
return 0;
|
||||
else
|
||||
return size - result;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_dma_pointer);
|
||||
260
sound/core/jack.c
Normal file
260
sound/core/jack.c
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Jack abstraction layer
|
||||
*
|
||||
* Copyright 2008 Wolfson Microelectronics
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/core.h>
|
||||
|
||||
static int jack_switch_types[SND_JACK_SWITCH_TYPES] = {
|
||||
SW_HEADPHONE_INSERT,
|
||||
SW_MICROPHONE_INSERT,
|
||||
SW_LINEOUT_INSERT,
|
||||
SW_JACK_PHYSICAL_INSERT,
|
||||
SW_VIDEOOUT_INSERT,
|
||||
SW_LINEIN_INSERT,
|
||||
};
|
||||
|
||||
static int snd_jack_dev_disconnect(struct snd_device *device)
|
||||
{
|
||||
struct snd_jack *jack = device->device_data;
|
||||
|
||||
if (!jack->input_dev)
|
||||
return 0;
|
||||
|
||||
/* If the input device is registered with the input subsystem
|
||||
* then we need to use a different deallocator. */
|
||||
if (jack->registered)
|
||||
input_unregister_device(jack->input_dev);
|
||||
else
|
||||
input_free_device(jack->input_dev);
|
||||
jack->input_dev = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_jack_dev_free(struct snd_device *device)
|
||||
{
|
||||
struct snd_jack *jack = device->device_data;
|
||||
|
||||
if (jack->private_free)
|
||||
jack->private_free(jack);
|
||||
|
||||
snd_jack_dev_disconnect(device);
|
||||
|
||||
kfree(jack->id);
|
||||
kfree(jack);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_jack_dev_register(struct snd_device *device)
|
||||
{
|
||||
struct snd_jack *jack = device->device_data;
|
||||
struct snd_card *card = device->card;
|
||||
int err, i;
|
||||
|
||||
snprintf(jack->name, sizeof(jack->name), "%s %s",
|
||||
card->shortname, jack->id);
|
||||
jack->input_dev->name = jack->name;
|
||||
|
||||
/* Default to the sound card device. */
|
||||
if (!jack->input_dev->dev.parent)
|
||||
jack->input_dev->dev.parent = snd_card_get_device_link(card);
|
||||
|
||||
/* Add capabilities for any keys that are enabled */
|
||||
for (i = 0; i < ARRAY_SIZE(jack->key); i++) {
|
||||
int testbit = SND_JACK_BTN_0 >> i;
|
||||
|
||||
if (!(jack->type & testbit))
|
||||
continue;
|
||||
|
||||
if (!jack->key[i])
|
||||
jack->key[i] = BTN_0 + i;
|
||||
|
||||
input_set_capability(jack->input_dev, EV_KEY, jack->key[i]);
|
||||
}
|
||||
|
||||
err = input_register_device(jack->input_dev);
|
||||
if (err == 0)
|
||||
jack->registered = 1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_jack_new - Create a new jack
|
||||
* @card: the card instance
|
||||
* @id: an identifying string for this jack
|
||||
* @type: a bitmask of enum snd_jack_type values that can be detected by
|
||||
* this jack
|
||||
* @jjack: Used to provide the allocated jack object to the caller.
|
||||
*
|
||||
* Creates a new jack object.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
* On success @jjack will be initialised.
|
||||
*/
|
||||
int snd_jack_new(struct snd_card *card, const char *id, int type,
|
||||
struct snd_jack **jjack)
|
||||
{
|
||||
struct snd_jack *jack;
|
||||
int err;
|
||||
int i;
|
||||
static struct snd_device_ops ops = {
|
||||
.dev_free = snd_jack_dev_free,
|
||||
.dev_register = snd_jack_dev_register,
|
||||
.dev_disconnect = snd_jack_dev_disconnect,
|
||||
};
|
||||
|
||||
jack = kzalloc(sizeof(struct snd_jack), GFP_KERNEL);
|
||||
if (jack == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
jack->id = kstrdup(id, GFP_KERNEL);
|
||||
|
||||
jack->input_dev = input_allocate_device();
|
||||
if (jack->input_dev == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto fail_input;
|
||||
}
|
||||
|
||||
jack->input_dev->phys = "ALSA";
|
||||
|
||||
jack->type = type;
|
||||
|
||||
for (i = 0; i < SND_JACK_SWITCH_TYPES; i++)
|
||||
if (type & (1 << i))
|
||||
input_set_capability(jack->input_dev, EV_SW,
|
||||
jack_switch_types[i]);
|
||||
|
||||
err = snd_device_new(card, SNDRV_DEV_JACK, jack, &ops);
|
||||
if (err < 0)
|
||||
goto fail_input;
|
||||
|
||||
*jjack = jack;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_input:
|
||||
input_free_device(jack->input_dev);
|
||||
kfree(jack->id);
|
||||
kfree(jack);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_jack_new);
|
||||
|
||||
/**
|
||||
* snd_jack_set_parent - Set the parent device for a jack
|
||||
*
|
||||
* @jack: The jack to configure
|
||||
* @parent: The device to set as parent for the jack.
|
||||
*
|
||||
* Set the parent for the jack devices in the device tree. This
|
||||
* function is only valid prior to registration of the jack. If no
|
||||
* parent is configured then the parent device will be the sound card.
|
||||
*/
|
||||
void snd_jack_set_parent(struct snd_jack *jack, struct device *parent)
|
||||
{
|
||||
WARN_ON(jack->registered);
|
||||
|
||||
jack->input_dev->dev.parent = parent;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_jack_set_parent);
|
||||
|
||||
/**
|
||||
* snd_jack_set_key - Set a key mapping on a jack
|
||||
*
|
||||
* @jack: The jack to configure
|
||||
* @type: Jack report type for this key
|
||||
* @keytype: Input layer key type to be reported
|
||||
*
|
||||
* Map a SND_JACK_BTN_ button type to an input layer key, allowing
|
||||
* reporting of keys on accessories via the jack abstraction. If no
|
||||
* mapping is provided but keys are enabled in the jack type then
|
||||
* BTN_n numeric buttons will be reported.
|
||||
*
|
||||
* If jacks are not reporting via the input API this call will have no
|
||||
* effect.
|
||||
*
|
||||
* Note that this is intended to be use by simple devices with small
|
||||
* numbers of keys that can be reported. It is also possible to
|
||||
* access the input device directly - devices with complex input
|
||||
* capabilities on accessories should consider doing this rather than
|
||||
* using this abstraction.
|
||||
*
|
||||
* This function may only be called prior to registration of the jack.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_jack_set_key(struct snd_jack *jack, enum snd_jack_types type,
|
||||
int keytype)
|
||||
{
|
||||
int key = fls(SND_JACK_BTN_0) - fls(type);
|
||||
|
||||
WARN_ON(jack->registered);
|
||||
|
||||
if (!keytype || key >= ARRAY_SIZE(jack->key))
|
||||
return -EINVAL;
|
||||
|
||||
jack->type |= type;
|
||||
jack->key[key] = keytype;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_jack_set_key);
|
||||
|
||||
/**
|
||||
* snd_jack_report - Report the current status of a jack
|
||||
*
|
||||
* @jack: The jack to report status for
|
||||
* @status: The current status of the jack
|
||||
*/
|
||||
void snd_jack_report(struct snd_jack *jack, int status)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!jack)
|
||||
return;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(jack->key); i++) {
|
||||
int testbit = SND_JACK_BTN_0 >> i;
|
||||
|
||||
if (jack->type & testbit)
|
||||
input_report_key(jack->input_dev, jack->key[i],
|
||||
status & testbit);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) {
|
||||
int testbit = 1 << i;
|
||||
if (jack->type & testbit)
|
||||
input_report_switch(jack->input_dev,
|
||||
jack_switch_types[i],
|
||||
status & testbit);
|
||||
}
|
||||
|
||||
input_sync(jack->input_dev);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_jack_report);
|
||||
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
||||
MODULE_DESCRIPTION("Jack detection support for ALSA");
|
||||
MODULE_LICENSE("GPL");
|
||||
299
sound/core/memalloc.c
Normal file
299
sound/core/memalloc.c
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
* Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* Generic memory allocators
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/genalloc.h>
|
||||
#include <sound/memalloc.h>
|
||||
|
||||
/*
|
||||
*
|
||||
* Generic memory allocators
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* snd_malloc_pages - allocate pages with the given size
|
||||
* @size: the size to allocate in bytes
|
||||
* @gfp_flags: the allocation conditions, GFP_XXX
|
||||
*
|
||||
* Allocates the physically contiguous pages with the given size.
|
||||
*
|
||||
* Return: The pointer of the buffer, or %NULL if no enough memory.
|
||||
*/
|
||||
void *snd_malloc_pages(size_t size, gfp_t gfp_flags)
|
||||
{
|
||||
int pg;
|
||||
|
||||
if (WARN_ON(!size))
|
||||
return NULL;
|
||||
if (WARN_ON(!gfp_flags))
|
||||
return NULL;
|
||||
gfp_flags |= __GFP_COMP; /* compound page lets parts be mapped */
|
||||
pg = get_order(size);
|
||||
return (void *) __get_free_pages(gfp_flags, pg);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_free_pages - release the pages
|
||||
* @ptr: the buffer pointer to release
|
||||
* @size: the allocated buffer size
|
||||
*
|
||||
* Releases the buffer allocated via snd_malloc_pages().
|
||||
*/
|
||||
void snd_free_pages(void *ptr, size_t size)
|
||||
{
|
||||
int pg;
|
||||
|
||||
if (ptr == NULL)
|
||||
return;
|
||||
pg = get_order(size);
|
||||
free_pages((unsigned long) ptr, pg);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Bus-specific memory allocators
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_HAS_DMA
|
||||
/* allocate the coherent DMA pages */
|
||||
static void *snd_malloc_dev_pages(struct device *dev, size_t size, dma_addr_t *dma)
|
||||
{
|
||||
int pg;
|
||||
gfp_t gfp_flags;
|
||||
|
||||
if (WARN_ON(!dma))
|
||||
return NULL;
|
||||
pg = get_order(size);
|
||||
gfp_flags = GFP_KERNEL
|
||||
| __GFP_COMP /* compound page lets parts be mapped */
|
||||
| __GFP_NORETRY /* don't trigger OOM-killer */
|
||||
| __GFP_NOWARN; /* no stack trace print - this call is non-critical */
|
||||
return dma_alloc_coherent(dev, PAGE_SIZE << pg, dma, gfp_flags);
|
||||
}
|
||||
|
||||
/* free the coherent DMA pages */
|
||||
static void snd_free_dev_pages(struct device *dev, size_t size, void *ptr,
|
||||
dma_addr_t dma)
|
||||
{
|
||||
int pg;
|
||||
|
||||
if (ptr == NULL)
|
||||
return;
|
||||
pg = get_order(size);
|
||||
dma_free_coherent(dev, PAGE_SIZE << pg, ptr, dma);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GENERIC_ALLOCATOR
|
||||
/**
|
||||
* snd_malloc_dev_iram - allocate memory from on-chip internal ram
|
||||
* @dmab: buffer allocation record to store the allocated data
|
||||
* @size: number of bytes to allocate from the iram
|
||||
*
|
||||
* This function requires iram phandle provided via of_node
|
||||
*/
|
||||
static void snd_malloc_dev_iram(struct snd_dma_buffer *dmab, size_t size)
|
||||
{
|
||||
struct device *dev = dmab->dev.dev;
|
||||
struct gen_pool *pool = NULL;
|
||||
|
||||
dmab->area = NULL;
|
||||
dmab->addr = 0;
|
||||
|
||||
if (dev->of_node)
|
||||
pool = of_get_named_gen_pool(dev->of_node, "iram", 0);
|
||||
|
||||
if (!pool)
|
||||
return;
|
||||
|
||||
/* Assign the pool into private_data field */
|
||||
dmab->private_data = pool;
|
||||
|
||||
dmab->area = gen_pool_dma_alloc(pool, size, &dmab->addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_free_dev_iram - free allocated specific memory from on-chip internal ram
|
||||
* @dmab: buffer allocation record to store the allocated data
|
||||
*/
|
||||
static void snd_free_dev_iram(struct snd_dma_buffer *dmab)
|
||||
{
|
||||
struct gen_pool *pool = dmab->private_data;
|
||||
|
||||
if (pool && dmab->area)
|
||||
gen_pool_free(pool, (unsigned long)dmab->area, dmab->bytes);
|
||||
}
|
||||
#endif /* CONFIG_GENERIC_ALLOCATOR */
|
||||
#endif /* CONFIG_HAS_DMA */
|
||||
|
||||
/*
|
||||
*
|
||||
* ALSA generic memory management
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* snd_dma_alloc_pages - allocate the buffer area according to the given type
|
||||
* @type: the DMA buffer type
|
||||
* @device: the device pointer
|
||||
* @size: the buffer size to allocate
|
||||
* @dmab: buffer allocation record to store the allocated data
|
||||
*
|
||||
* Calls the memory-allocator function for the corresponding
|
||||
* buffer type.
|
||||
*
|
||||
* Return: Zero if the buffer with the given size is allocated successfully,
|
||||
* otherwise a negative value on error.
|
||||
*/
|
||||
int snd_dma_alloc_pages(int type, struct device *device, size_t size,
|
||||
struct snd_dma_buffer *dmab)
|
||||
{
|
||||
if (WARN_ON(!size))
|
||||
return -ENXIO;
|
||||
if (WARN_ON(!dmab))
|
||||
return -ENXIO;
|
||||
|
||||
dmab->dev.type = type;
|
||||
dmab->dev.dev = device;
|
||||
dmab->bytes = 0;
|
||||
switch (type) {
|
||||
case SNDRV_DMA_TYPE_CONTINUOUS:
|
||||
dmab->area = snd_malloc_pages(size,
|
||||
(__force gfp_t)(unsigned long)device);
|
||||
dmab->addr = 0;
|
||||
break;
|
||||
#ifdef CONFIG_HAS_DMA
|
||||
#ifdef CONFIG_GENERIC_ALLOCATOR
|
||||
case SNDRV_DMA_TYPE_DEV_IRAM:
|
||||
snd_malloc_dev_iram(dmab, size);
|
||||
if (dmab->area)
|
||||
break;
|
||||
/* Internal memory might have limited size and no enough space,
|
||||
* so if we fail to malloc, try to fetch memory traditionally.
|
||||
*/
|
||||
dmab->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
#endif /* CONFIG_GENERIC_ALLOCATOR */
|
||||
case SNDRV_DMA_TYPE_DEV:
|
||||
dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr);
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
case SNDRV_DMA_TYPE_DEV_SG:
|
||||
snd_malloc_sgbuf_pages(device, size, dmab, NULL);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
pr_err("snd-malloc: invalid device type %d\n", type);
|
||||
dmab->area = NULL;
|
||||
dmab->addr = 0;
|
||||
return -ENXIO;
|
||||
}
|
||||
if (! dmab->area)
|
||||
return -ENOMEM;
|
||||
dmab->bytes = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback
|
||||
* @type: the DMA buffer type
|
||||
* @device: the device pointer
|
||||
* @size: the buffer size to allocate
|
||||
* @dmab: buffer allocation record to store the allocated data
|
||||
*
|
||||
* Calls the memory-allocator function for the corresponding
|
||||
* buffer type. When no space is left, this function reduces the size and
|
||||
* tries to allocate again. The size actually allocated is stored in
|
||||
* res_size argument.
|
||||
*
|
||||
* Return: Zero if the buffer with the given size is allocated successfully,
|
||||
* otherwise a negative value on error.
|
||||
*/
|
||||
int snd_dma_alloc_pages_fallback(int type, struct device *device, size_t size,
|
||||
struct snd_dma_buffer *dmab)
|
||||
{
|
||||
int err;
|
||||
|
||||
while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) {
|
||||
size_t aligned_size;
|
||||
if (err != -ENOMEM)
|
||||
return err;
|
||||
if (size <= PAGE_SIZE)
|
||||
return -ENOMEM;
|
||||
aligned_size = PAGE_SIZE << get_order(size);
|
||||
if (size != aligned_size)
|
||||
size = aligned_size;
|
||||
else
|
||||
size >>= 1;
|
||||
}
|
||||
if (! dmab->area)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* snd_dma_free_pages - release the allocated buffer
|
||||
* @dmab: the buffer allocation record to release
|
||||
*
|
||||
* Releases the allocated buffer via snd_dma_alloc_pages().
|
||||
*/
|
||||
void snd_dma_free_pages(struct snd_dma_buffer *dmab)
|
||||
{
|
||||
switch (dmab->dev.type) {
|
||||
case SNDRV_DMA_TYPE_CONTINUOUS:
|
||||
snd_free_pages(dmab->area, dmab->bytes);
|
||||
break;
|
||||
#ifdef CONFIG_HAS_DMA
|
||||
#ifdef CONFIG_GENERIC_ALLOCATOR
|
||||
case SNDRV_DMA_TYPE_DEV_IRAM:
|
||||
snd_free_dev_iram(dmab);
|
||||
break;
|
||||
#endif /* CONFIG_GENERIC_ALLOCATOR */
|
||||
case SNDRV_DMA_TYPE_DEV:
|
||||
snd_free_dev_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr);
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
case SNDRV_DMA_TYPE_DEV_SG:
|
||||
snd_free_sgbuf_pages(dmab);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
pr_err("snd-malloc: invalid device type %d\n", dmab->dev.type);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* exports
|
||||
*/
|
||||
EXPORT_SYMBOL(snd_dma_alloc_pages);
|
||||
EXPORT_SYMBOL(snd_dma_alloc_pages_fallback);
|
||||
EXPORT_SYMBOL(snd_dma_free_pages);
|
||||
|
||||
EXPORT_SYMBOL(snd_malloc_pages);
|
||||
EXPORT_SYMBOL(snd_free_pages);
|
||||
92
sound/core/memory.c
Normal file
92
sound/core/memory.c
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
* Misc memory accessors
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <sound/core.h>
|
||||
|
||||
/**
|
||||
* copy_to_user_fromio - copy data from mmio-space to user-space
|
||||
* @dst: the destination pointer on user-space
|
||||
* @src: the source pointer on mmio
|
||||
* @count: the data size to copy in bytes
|
||||
*
|
||||
* Copies the data from mmio-space to user-space.
|
||||
*
|
||||
* Return: Zero if successful, or non-zero on failure.
|
||||
*/
|
||||
int copy_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count)
|
||||
{
|
||||
#if defined(__i386__) || defined(CONFIG_SPARC32)
|
||||
return copy_to_user(dst, (const void __force*)src, count) ? -EFAULT : 0;
|
||||
#else
|
||||
char buf[256];
|
||||
while (count) {
|
||||
size_t c = count;
|
||||
if (c > sizeof(buf))
|
||||
c = sizeof(buf);
|
||||
memcpy_fromio(buf, (void __iomem *)src, c);
|
||||
if (copy_to_user(dst, buf, c))
|
||||
return -EFAULT;
|
||||
count -= c;
|
||||
dst += c;
|
||||
src += c;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(copy_to_user_fromio);
|
||||
|
||||
/**
|
||||
* copy_from_user_toio - copy data from user-space to mmio-space
|
||||
* @dst: the destination pointer on mmio-space
|
||||
* @src: the source pointer on user-space
|
||||
* @count: the data size to copy in bytes
|
||||
*
|
||||
* Copies the data from user-space to mmio-space.
|
||||
*
|
||||
* Return: Zero if successful, or non-zero on failure.
|
||||
*/
|
||||
int copy_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count)
|
||||
{
|
||||
#if defined(__i386__) || defined(CONFIG_SPARC32)
|
||||
return copy_from_user((void __force *)dst, src, count) ? -EFAULT : 0;
|
||||
#else
|
||||
char buf[256];
|
||||
while (count) {
|
||||
size_t c = count;
|
||||
if (c > sizeof(buf))
|
||||
c = sizeof(buf);
|
||||
if (copy_from_user(buf, src, c))
|
||||
return -EFAULT;
|
||||
memcpy_toio(dst, buf, c);
|
||||
count -= c;
|
||||
dst += c;
|
||||
src += c;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(copy_from_user_toio);
|
||||
155
sound/core/misc.c
Normal file
155
sound/core/misc.c
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Misc and compatibility things
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <sound/core.h>
|
||||
|
||||
#ifdef CONFIG_SND_DEBUG
|
||||
|
||||
#ifdef CONFIG_SND_DEBUG_VERBOSE
|
||||
#define DEFAULT_DEBUG_LEVEL 2
|
||||
#else
|
||||
#define DEFAULT_DEBUG_LEVEL 1
|
||||
#endif
|
||||
|
||||
static int debug = DEFAULT_DEBUG_LEVEL;
|
||||
module_param(debug, int, 0644);
|
||||
MODULE_PARM_DESC(debug, "Debug level (0 = disable)");
|
||||
|
||||
#endif /* CONFIG_SND_DEBUG */
|
||||
|
||||
void release_and_free_resource(struct resource *res)
|
||||
{
|
||||
if (res) {
|
||||
release_resource(res);
|
||||
kfree(res);
|
||||
}
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(release_and_free_resource);
|
||||
|
||||
#ifdef CONFIG_SND_VERBOSE_PRINTK
|
||||
/* strip the leading path if the given path is absolute */
|
||||
static const char *sanity_file_name(const char *path)
|
||||
{
|
||||
if (*path == '/')
|
||||
return strrchr(path, '/') + 1;
|
||||
else
|
||||
return path;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SND_DEBUG) || defined(CONFIG_SND_VERBOSE_PRINTK)
|
||||
void __snd_printk(unsigned int level, const char *path, int line,
|
||||
const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
#ifdef CONFIG_SND_VERBOSE_PRINTK
|
||||
int kern_level;
|
||||
struct va_format vaf;
|
||||
char verbose_fmt[] = KERN_DEFAULT "ALSA %s:%d %pV";
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SND_DEBUG
|
||||
if (debug < level)
|
||||
return;
|
||||
#endif
|
||||
|
||||
va_start(args, format);
|
||||
#ifdef CONFIG_SND_VERBOSE_PRINTK
|
||||
vaf.fmt = format;
|
||||
vaf.va = &args;
|
||||
|
||||
kern_level = printk_get_level(format);
|
||||
if (kern_level) {
|
||||
const char *end_of_header = printk_skip_level(format);
|
||||
memcpy(verbose_fmt, format, end_of_header - format);
|
||||
vaf.fmt = end_of_header;
|
||||
} else if (level)
|
||||
memcpy(verbose_fmt, KERN_DEBUG, sizeof(KERN_DEBUG) - 1);
|
||||
printk(verbose_fmt, sanity_file_name(path), line, &vaf);
|
||||
|
||||
#else
|
||||
vprintk(format, args);
|
||||
#endif
|
||||
va_end(args);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__snd_printk);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PCI
|
||||
#include <linux/pci.h>
|
||||
/**
|
||||
* snd_pci_quirk_lookup_id - look up a PCI SSID quirk list
|
||||
* @vendor: PCI SSV id
|
||||
* @device: PCI SSD id
|
||||
* @list: quirk list, terminated by a null entry
|
||||
*
|
||||
* Look through the given quirk list and finds a matching entry
|
||||
* with the same PCI SSID. When subdevice is 0, all subdevice
|
||||
* values may match.
|
||||
*
|
||||
* Returns the matched entry pointer, or NULL if nothing matched.
|
||||
*/
|
||||
const struct snd_pci_quirk *
|
||||
snd_pci_quirk_lookup_id(u16 vendor, u16 device,
|
||||
const struct snd_pci_quirk *list)
|
||||
{
|
||||
const struct snd_pci_quirk *q;
|
||||
|
||||
for (q = list; q->subvendor; q++) {
|
||||
if (q->subvendor != vendor)
|
||||
continue;
|
||||
if (!q->subdevice ||
|
||||
(device & q->subdevice_mask) == q->subdevice)
|
||||
return q;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pci_quirk_lookup_id);
|
||||
|
||||
/**
|
||||
* snd_pci_quirk_lookup - look up a PCI SSID quirk list
|
||||
* @pci: pci_dev handle
|
||||
* @list: quirk list, terminated by a null entry
|
||||
*
|
||||
* Look through the given quirk list and finds a matching entry
|
||||
* with the same PCI SSID. When subdevice is 0, all subdevice
|
||||
* values may match.
|
||||
*
|
||||
* Returns the matched entry pointer, or NULL if nothing matched.
|
||||
*/
|
||||
const struct snd_pci_quirk *
|
||||
snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list)
|
||||
{
|
||||
if (!pci)
|
||||
return NULL;
|
||||
return snd_pci_quirk_lookup_id(pci->subsystem_vendor,
|
||||
pci->subsystem_device,
|
||||
list);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pci_quirk_lookup);
|
||||
#endif
|
||||
13
sound/core/oss/Makefile
Normal file
13
sound/core/oss/Makefile
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-mixer-oss-objs := mixer_oss.o
|
||||
|
||||
snd-pcm-oss-y := pcm_oss.o
|
||||
snd-pcm-oss-$(CONFIG_SND_PCM_OSS_PLUGINS) += pcm_plugin.o \
|
||||
io.o copy.o linear.o mulaw.o route.o rate.o
|
||||
|
||||
obj-$(CONFIG_SND_MIXER_OSS) += snd-mixer-oss.o
|
||||
obj-$(CONFIG_SND_PCM_OSS) += snd-pcm-oss.o
|
||||
92
sound/core/oss/copy.c
Normal file
92
sound/core/oss/copy.c
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Linear conversion Plug-In
|
||||
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include "pcm_plugin.h"
|
||||
|
||||
static snd_pcm_sframes_t copy_transfer(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
unsigned int channel;
|
||||
unsigned int nchannels;
|
||||
|
||||
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
|
||||
return -ENXIO;
|
||||
if (frames == 0)
|
||||
return 0;
|
||||
nchannels = plugin->src_format.channels;
|
||||
for (channel = 0; channel < nchannels; channel++) {
|
||||
if (snd_BUG_ON(src_channels->area.first % 8 ||
|
||||
src_channels->area.step % 8))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(dst_channels->area.first % 8 ||
|
||||
dst_channels->area.step % 8))
|
||||
return -ENXIO;
|
||||
if (!src_channels->enabled) {
|
||||
if (dst_channels->wanted)
|
||||
snd_pcm_area_silence(&dst_channels->area, 0, frames, plugin->dst_format.format);
|
||||
dst_channels->enabled = 0;
|
||||
continue;
|
||||
}
|
||||
dst_channels->enabled = 1;
|
||||
snd_pcm_area_copy(&src_channels->area, 0, &dst_channels->area, 0, frames, plugin->src_format.format);
|
||||
src_channels++;
|
||||
dst_channels++;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
int snd_pcm_plugin_build_copy(struct snd_pcm_substream *plug,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin)
|
||||
{
|
||||
int err;
|
||||
struct snd_pcm_plugin *plugin;
|
||||
int width;
|
||||
|
||||
if (snd_BUG_ON(!r_plugin))
|
||||
return -ENXIO;
|
||||
*r_plugin = NULL;
|
||||
|
||||
if (snd_BUG_ON(src_format->format != dst_format->format))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(src_format->rate != dst_format->rate))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(src_format->channels != dst_format->channels))
|
||||
return -ENXIO;
|
||||
|
||||
width = snd_pcm_format_physical_width(src_format->format);
|
||||
if (snd_BUG_ON(width <= 0))
|
||||
return -ENXIO;
|
||||
|
||||
err = snd_pcm_plugin_build(plug, "copy", src_format, dst_format,
|
||||
0, &plugin);
|
||||
if (err < 0)
|
||||
return err;
|
||||
plugin->transfer = copy_transfer;
|
||||
*r_plugin = plugin;
|
||||
return 0;
|
||||
}
|
||||
141
sound/core/oss/io.c
Normal file
141
sound/core/oss/io.c
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* PCM I/O Plug-In Interface
|
||||
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include "pcm_plugin.h"
|
||||
|
||||
#define pcm_write(plug,buf,count) snd_pcm_oss_write3(plug,buf,count,1)
|
||||
#define pcm_writev(plug,vec,count) snd_pcm_oss_writev3(plug,vec,count,1)
|
||||
#define pcm_read(plug,buf,count) snd_pcm_oss_read3(plug,buf,count,1)
|
||||
#define pcm_readv(plug,vec,count) snd_pcm_oss_readv3(plug,vec,count,1)
|
||||
|
||||
/*
|
||||
* Basic io plugin
|
||||
*/
|
||||
|
||||
static snd_pcm_sframes_t io_playback_transfer(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
if (snd_BUG_ON(!plugin))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(!src_channels))
|
||||
return -ENXIO;
|
||||
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
|
||||
return pcm_write(plugin->plug, src_channels->area.addr, frames);
|
||||
} else {
|
||||
int channel, channels = plugin->dst_format.channels;
|
||||
void **bufs = (void**)plugin->extra_data;
|
||||
if (snd_BUG_ON(!bufs))
|
||||
return -ENXIO;
|
||||
for (channel = 0; channel < channels; channel++) {
|
||||
if (src_channels[channel].enabled)
|
||||
bufs[channel] = src_channels[channel].area.addr;
|
||||
else
|
||||
bufs[channel] = NULL;
|
||||
}
|
||||
return pcm_writev(plugin->plug, bufs, frames);
|
||||
}
|
||||
}
|
||||
|
||||
static snd_pcm_sframes_t io_capture_transfer(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
if (snd_BUG_ON(!plugin))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(!dst_channels))
|
||||
return -ENXIO;
|
||||
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
|
||||
return pcm_read(plugin->plug, dst_channels->area.addr, frames);
|
||||
} else {
|
||||
int channel, channels = plugin->dst_format.channels;
|
||||
void **bufs = (void**)plugin->extra_data;
|
||||
if (snd_BUG_ON(!bufs))
|
||||
return -ENXIO;
|
||||
for (channel = 0; channel < channels; channel++) {
|
||||
if (dst_channels[channel].enabled)
|
||||
bufs[channel] = dst_channels[channel].area.addr;
|
||||
else
|
||||
bufs[channel] = NULL;
|
||||
}
|
||||
return pcm_readv(plugin->plug, bufs, frames);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_sframes_t io_src_channels(struct snd_pcm_plugin *plugin,
|
||||
snd_pcm_uframes_t frames,
|
||||
struct snd_pcm_plugin_channel **channels)
|
||||
{
|
||||
int err;
|
||||
unsigned int channel;
|
||||
struct snd_pcm_plugin_channel *v;
|
||||
err = snd_pcm_plugin_client_channels(plugin, frames, &v);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*channels = v;
|
||||
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
|
||||
for (channel = 0; channel < plugin->src_format.channels; ++channel, ++v)
|
||||
v->wanted = 1;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
int snd_pcm_plugin_build_io(struct snd_pcm_substream *plug,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_plugin **r_plugin)
|
||||
{
|
||||
int err;
|
||||
struct snd_pcm_plugin_format format;
|
||||
struct snd_pcm_plugin *plugin;
|
||||
|
||||
if (snd_BUG_ON(!r_plugin))
|
||||
return -ENXIO;
|
||||
*r_plugin = NULL;
|
||||
if (snd_BUG_ON(!plug || !params))
|
||||
return -ENXIO;
|
||||
format.format = params_format(params);
|
||||
format.rate = params_rate(params);
|
||||
format.channels = params_channels(params);
|
||||
err = snd_pcm_plugin_build(plug, "I/O io",
|
||||
&format, &format,
|
||||
sizeof(void *) * format.channels,
|
||||
&plugin);
|
||||
if (err < 0)
|
||||
return err;
|
||||
plugin->access = params_access(params);
|
||||
if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
plugin->transfer = io_playback_transfer;
|
||||
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED)
|
||||
plugin->client_channels = io_src_channels;
|
||||
} else {
|
||||
plugin->transfer = io_capture_transfer;
|
||||
}
|
||||
|
||||
*r_plugin = plugin;
|
||||
return 0;
|
||||
}
|
||||
178
sound/core/oss/linear.c
Normal file
178
sound/core/oss/linear.c
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Linear conversion Plug-In
|
||||
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>,
|
||||
* Abramo Bagnara <abramo@alsa-project.org>
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include "pcm_plugin.h"
|
||||
|
||||
/*
|
||||
* Basic linear conversion plugin
|
||||
*/
|
||||
|
||||
struct linear_priv {
|
||||
int cvt_endian; /* need endian conversion? */
|
||||
unsigned int src_ofs; /* byte offset in source format */
|
||||
unsigned int dst_ofs; /* byte soffset in destination format */
|
||||
unsigned int copy_ofs; /* byte offset in temporary u32 data */
|
||||
unsigned int dst_bytes; /* byte size of destination format */
|
||||
unsigned int copy_bytes; /* bytes to copy per conversion */
|
||||
unsigned int flip; /* MSB flip for signeness, done after endian conv */
|
||||
};
|
||||
|
||||
static inline void do_convert(struct linear_priv *data,
|
||||
unsigned char *dst, unsigned char *src)
|
||||
{
|
||||
unsigned int tmp = 0;
|
||||
unsigned char *p = (unsigned char *)&tmp;
|
||||
|
||||
memcpy(p + data->copy_ofs, src + data->src_ofs, data->copy_bytes);
|
||||
if (data->cvt_endian)
|
||||
tmp = swab32(tmp);
|
||||
tmp ^= data->flip;
|
||||
memcpy(dst, p + data->dst_ofs, data->dst_bytes);
|
||||
}
|
||||
|
||||
static void convert(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
struct linear_priv *data = (struct linear_priv *)plugin->extra_data;
|
||||
int channel;
|
||||
int nchannels = plugin->src_format.channels;
|
||||
for (channel = 0; channel < nchannels; ++channel) {
|
||||
char *src;
|
||||
char *dst;
|
||||
int src_step, dst_step;
|
||||
snd_pcm_uframes_t frames1;
|
||||
if (!src_channels[channel].enabled) {
|
||||
if (dst_channels[channel].wanted)
|
||||
snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
|
||||
dst_channels[channel].enabled = 0;
|
||||
continue;
|
||||
}
|
||||
dst_channels[channel].enabled = 1;
|
||||
src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
|
||||
dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
|
||||
src_step = src_channels[channel].area.step / 8;
|
||||
dst_step = dst_channels[channel].area.step / 8;
|
||||
frames1 = frames;
|
||||
while (frames1-- > 0) {
|
||||
do_convert(data, dst, src);
|
||||
src += src_step;
|
||||
dst += dst_step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static snd_pcm_sframes_t linear_transfer(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
|
||||
return -ENXIO;
|
||||
if (frames == 0)
|
||||
return 0;
|
||||
#ifdef CONFIG_SND_DEBUG
|
||||
{
|
||||
unsigned int channel;
|
||||
for (channel = 0; channel < plugin->src_format.channels; channel++) {
|
||||
if (snd_BUG_ON(src_channels[channel].area.first % 8 ||
|
||||
src_channels[channel].area.step % 8))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(dst_channels[channel].area.first % 8 ||
|
||||
dst_channels[channel].area.step % 8))
|
||||
return -ENXIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
convert(plugin, src_channels, dst_channels, frames);
|
||||
return frames;
|
||||
}
|
||||
|
||||
static void init_data(struct linear_priv *data,
|
||||
snd_pcm_format_t src_format, snd_pcm_format_t dst_format)
|
||||
{
|
||||
int src_le, dst_le, src_bytes, dst_bytes;
|
||||
|
||||
src_bytes = snd_pcm_format_width(src_format) / 8;
|
||||
dst_bytes = snd_pcm_format_width(dst_format) / 8;
|
||||
src_le = snd_pcm_format_little_endian(src_format) > 0;
|
||||
dst_le = snd_pcm_format_little_endian(dst_format) > 0;
|
||||
|
||||
data->dst_bytes = dst_bytes;
|
||||
data->cvt_endian = src_le != dst_le;
|
||||
data->copy_bytes = src_bytes < dst_bytes ? src_bytes : dst_bytes;
|
||||
if (src_le) {
|
||||
data->copy_ofs = 4 - data->copy_bytes;
|
||||
data->src_ofs = src_bytes - data->copy_bytes;
|
||||
} else
|
||||
data->src_ofs = snd_pcm_format_physical_width(src_format) / 8 -
|
||||
src_bytes;
|
||||
if (dst_le)
|
||||
data->dst_ofs = 4 - data->dst_bytes;
|
||||
else
|
||||
data->dst_ofs = snd_pcm_format_physical_width(dst_format) / 8 -
|
||||
dst_bytes;
|
||||
if (snd_pcm_format_signed(src_format) !=
|
||||
snd_pcm_format_signed(dst_format)) {
|
||||
if (dst_le)
|
||||
data->flip = (__force u32)cpu_to_le32(0x80000000);
|
||||
else
|
||||
data->flip = (__force u32)cpu_to_be32(0x80000000);
|
||||
}
|
||||
}
|
||||
|
||||
int snd_pcm_plugin_build_linear(struct snd_pcm_substream *plug,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin)
|
||||
{
|
||||
int err;
|
||||
struct linear_priv *data;
|
||||
struct snd_pcm_plugin *plugin;
|
||||
|
||||
if (snd_BUG_ON(!r_plugin))
|
||||
return -ENXIO;
|
||||
*r_plugin = NULL;
|
||||
|
||||
if (snd_BUG_ON(src_format->rate != dst_format->rate))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(src_format->channels != dst_format->channels))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(!snd_pcm_format_linear(src_format->format) ||
|
||||
!snd_pcm_format_linear(dst_format->format)))
|
||||
return -ENXIO;
|
||||
|
||||
err = snd_pcm_plugin_build(plug, "linear format conversion",
|
||||
src_format, dst_format,
|
||||
sizeof(struct linear_priv), &plugin);
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = (struct linear_priv *)plugin->extra_data;
|
||||
init_data(data, src_format->format, dst_format->format);
|
||||
plugin->transfer = linear_transfer;
|
||||
*r_plugin = plugin;
|
||||
return 0;
|
||||
}
|
||||
1424
sound/core/oss/mixer_oss.c
Normal file
1424
sound/core/oss/mixer_oss.c
Normal file
File diff suppressed because it is too large
Load diff
344
sound/core/oss/mulaw.c
Normal file
344
sound/core/oss/mulaw.c
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
/*
|
||||
* Mu-Law conversion Plug-In Interface
|
||||
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
|
||||
* Uros Bizjak <uros@kss-loka.si>
|
||||
*
|
||||
* Based on reference implementation by Sun Microsystems, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include "pcm_plugin.h"
|
||||
|
||||
#define SIGN_BIT (0x80) /* Sign bit for a u-law byte. */
|
||||
#define QUANT_MASK (0xf) /* Quantization field mask. */
|
||||
#define NSEGS (8) /* Number of u-law segments. */
|
||||
#define SEG_SHIFT (4) /* Left shift for segment number. */
|
||||
#define SEG_MASK (0x70) /* Segment field mask. */
|
||||
|
||||
static inline int val_seg(int val)
|
||||
{
|
||||
int r = 0;
|
||||
val >>= 7;
|
||||
if (val & 0xf0) {
|
||||
val >>= 4;
|
||||
r += 4;
|
||||
}
|
||||
if (val & 0x0c) {
|
||||
val >>= 2;
|
||||
r += 2;
|
||||
}
|
||||
if (val & 0x02)
|
||||
r += 1;
|
||||
return r;
|
||||
}
|
||||
|
||||
#define BIAS (0x84) /* Bias for linear code. */
|
||||
|
||||
/*
|
||||
* linear2ulaw() - Convert a linear PCM value to u-law
|
||||
*
|
||||
* In order to simplify the encoding process, the original linear magnitude
|
||||
* is biased by adding 33 which shifts the encoding range from (0 - 8158) to
|
||||
* (33 - 8191). The result can be seen in the following encoding table:
|
||||
*
|
||||
* Biased Linear Input Code Compressed Code
|
||||
* ------------------------ ---------------
|
||||
* 00000001wxyza 000wxyz
|
||||
* 0000001wxyzab 001wxyz
|
||||
* 000001wxyzabc 010wxyz
|
||||
* 00001wxyzabcd 011wxyz
|
||||
* 0001wxyzabcde 100wxyz
|
||||
* 001wxyzabcdef 101wxyz
|
||||
* 01wxyzabcdefg 110wxyz
|
||||
* 1wxyzabcdefgh 111wxyz
|
||||
*
|
||||
* Each biased linear code has a leading 1 which identifies the segment
|
||||
* number. The value of the segment number is equal to 7 minus the number
|
||||
* of leading 0's. The quantization interval is directly available as the
|
||||
* four bits wxyz. * The trailing bits (a - h) are ignored.
|
||||
*
|
||||
* Ordinarily the complement of the resulting code word is used for
|
||||
* transmission, and so the code word is complemented before it is returned.
|
||||
*
|
||||
* For further information see John C. Bellamy's Digital Telephony, 1982,
|
||||
* John Wiley & Sons, pps 98-111 and 472-476.
|
||||
*/
|
||||
static unsigned char linear2ulaw(int pcm_val) /* 2's complement (16-bit range) */
|
||||
{
|
||||
int mask;
|
||||
int seg;
|
||||
unsigned char uval;
|
||||
|
||||
/* Get the sign and the magnitude of the value. */
|
||||
if (pcm_val < 0) {
|
||||
pcm_val = BIAS - pcm_val;
|
||||
mask = 0x7F;
|
||||
} else {
|
||||
pcm_val += BIAS;
|
||||
mask = 0xFF;
|
||||
}
|
||||
if (pcm_val > 0x7FFF)
|
||||
pcm_val = 0x7FFF;
|
||||
|
||||
/* Convert the scaled magnitude to segment number. */
|
||||
seg = val_seg(pcm_val);
|
||||
|
||||
/*
|
||||
* Combine the sign, segment, quantization bits;
|
||||
* and complement the code word.
|
||||
*/
|
||||
uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF);
|
||||
return uval ^ mask;
|
||||
}
|
||||
|
||||
/*
|
||||
* ulaw2linear() - Convert a u-law value to 16-bit linear PCM
|
||||
*
|
||||
* First, a biased linear code is derived from the code word. An unbiased
|
||||
* output can then be obtained by subtracting 33 from the biased code.
|
||||
*
|
||||
* Note that this function expects to be passed the complement of the
|
||||
* original code word. This is in keeping with ISDN conventions.
|
||||
*/
|
||||
static int ulaw2linear(unsigned char u_val)
|
||||
{
|
||||
int t;
|
||||
|
||||
/* Complement to obtain normal u-law value. */
|
||||
u_val = ~u_val;
|
||||
|
||||
/*
|
||||
* Extract and bias the quantization bits. Then
|
||||
* shift up by the segment number and subtract out the bias.
|
||||
*/
|
||||
t = ((u_val & QUANT_MASK) << 3) + BIAS;
|
||||
t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
|
||||
|
||||
return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
|
||||
}
|
||||
|
||||
/*
|
||||
* Basic Mu-Law plugin
|
||||
*/
|
||||
|
||||
typedef void (*mulaw_f)(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames);
|
||||
|
||||
struct mulaw_priv {
|
||||
mulaw_f func;
|
||||
int cvt_endian; /* need endian conversion? */
|
||||
unsigned int native_ofs; /* byte offset in native format */
|
||||
unsigned int copy_ofs; /* byte offset in s16 format */
|
||||
unsigned int native_bytes; /* byte size of the native format */
|
||||
unsigned int copy_bytes; /* bytes to copy per conversion */
|
||||
u16 flip; /* MSB flip for signedness, done after endian conversion */
|
||||
};
|
||||
|
||||
static inline void cvt_s16_to_native(struct mulaw_priv *data,
|
||||
unsigned char *dst, u16 sample)
|
||||
{
|
||||
sample ^= data->flip;
|
||||
if (data->cvt_endian)
|
||||
sample = swab16(sample);
|
||||
if (data->native_bytes > data->copy_bytes)
|
||||
memset(dst, 0, data->native_bytes);
|
||||
memcpy(dst + data->native_ofs, (char *)&sample + data->copy_ofs,
|
||||
data->copy_bytes);
|
||||
}
|
||||
|
||||
static void mulaw_decode(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data;
|
||||
int channel;
|
||||
int nchannels = plugin->src_format.channels;
|
||||
for (channel = 0; channel < nchannels; ++channel) {
|
||||
char *src;
|
||||
char *dst;
|
||||
int src_step, dst_step;
|
||||
snd_pcm_uframes_t frames1;
|
||||
if (!src_channels[channel].enabled) {
|
||||
if (dst_channels[channel].wanted)
|
||||
snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
|
||||
dst_channels[channel].enabled = 0;
|
||||
continue;
|
||||
}
|
||||
dst_channels[channel].enabled = 1;
|
||||
src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
|
||||
dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
|
||||
src_step = src_channels[channel].area.step / 8;
|
||||
dst_step = dst_channels[channel].area.step / 8;
|
||||
frames1 = frames;
|
||||
while (frames1-- > 0) {
|
||||
signed short sample = ulaw2linear(*src);
|
||||
cvt_s16_to_native(data, dst, sample);
|
||||
src += src_step;
|
||||
dst += dst_step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline signed short cvt_native_to_s16(struct mulaw_priv *data,
|
||||
unsigned char *src)
|
||||
{
|
||||
u16 sample = 0;
|
||||
memcpy((char *)&sample + data->copy_ofs, src + data->native_ofs,
|
||||
data->copy_bytes);
|
||||
if (data->cvt_endian)
|
||||
sample = swab16(sample);
|
||||
sample ^= data->flip;
|
||||
return (signed short)sample;
|
||||
}
|
||||
|
||||
static void mulaw_encode(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data;
|
||||
int channel;
|
||||
int nchannels = plugin->src_format.channels;
|
||||
for (channel = 0; channel < nchannels; ++channel) {
|
||||
char *src;
|
||||
char *dst;
|
||||
int src_step, dst_step;
|
||||
snd_pcm_uframes_t frames1;
|
||||
if (!src_channels[channel].enabled) {
|
||||
if (dst_channels[channel].wanted)
|
||||
snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
|
||||
dst_channels[channel].enabled = 0;
|
||||
continue;
|
||||
}
|
||||
dst_channels[channel].enabled = 1;
|
||||
src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
|
||||
dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
|
||||
src_step = src_channels[channel].area.step / 8;
|
||||
dst_step = dst_channels[channel].area.step / 8;
|
||||
frames1 = frames;
|
||||
while (frames1-- > 0) {
|
||||
signed short sample = cvt_native_to_s16(data, src);
|
||||
*dst = linear2ulaw(sample);
|
||||
src += src_step;
|
||||
dst += dst_step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static snd_pcm_sframes_t mulaw_transfer(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
struct mulaw_priv *data;
|
||||
|
||||
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
|
||||
return -ENXIO;
|
||||
if (frames == 0)
|
||||
return 0;
|
||||
#ifdef CONFIG_SND_DEBUG
|
||||
{
|
||||
unsigned int channel;
|
||||
for (channel = 0; channel < plugin->src_format.channels; channel++) {
|
||||
if (snd_BUG_ON(src_channels[channel].area.first % 8 ||
|
||||
src_channels[channel].area.step % 8))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(dst_channels[channel].area.first % 8 ||
|
||||
dst_channels[channel].area.step % 8))
|
||||
return -ENXIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
data = (struct mulaw_priv *)plugin->extra_data;
|
||||
data->func(plugin, src_channels, dst_channels, frames);
|
||||
return frames;
|
||||
}
|
||||
|
||||
static void init_data(struct mulaw_priv *data, snd_pcm_format_t format)
|
||||
{
|
||||
#ifdef SNDRV_LITTLE_ENDIAN
|
||||
data->cvt_endian = snd_pcm_format_big_endian(format) > 0;
|
||||
#else
|
||||
data->cvt_endian = snd_pcm_format_little_endian(format) > 0;
|
||||
#endif
|
||||
if (!snd_pcm_format_signed(format))
|
||||
data->flip = 0x8000;
|
||||
data->native_bytes = snd_pcm_format_physical_width(format) / 8;
|
||||
data->copy_bytes = data->native_bytes < 2 ? 1 : 2;
|
||||
if (snd_pcm_format_little_endian(format)) {
|
||||
data->native_ofs = data->native_bytes - data->copy_bytes;
|
||||
data->copy_ofs = 2 - data->copy_bytes;
|
||||
} else {
|
||||
/* S24 in 4bytes need an 1 byte offset */
|
||||
data->native_ofs = data->native_bytes -
|
||||
snd_pcm_format_width(format) / 8;
|
||||
}
|
||||
}
|
||||
|
||||
int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *plug,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin)
|
||||
{
|
||||
int err;
|
||||
struct mulaw_priv *data;
|
||||
struct snd_pcm_plugin *plugin;
|
||||
struct snd_pcm_plugin_format *format;
|
||||
mulaw_f func;
|
||||
|
||||
if (snd_BUG_ON(!r_plugin))
|
||||
return -ENXIO;
|
||||
*r_plugin = NULL;
|
||||
|
||||
if (snd_BUG_ON(src_format->rate != dst_format->rate))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(src_format->channels != dst_format->channels))
|
||||
return -ENXIO;
|
||||
|
||||
if (dst_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
|
||||
format = src_format;
|
||||
func = mulaw_encode;
|
||||
}
|
||||
else if (src_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
|
||||
format = dst_format;
|
||||
func = mulaw_decode;
|
||||
}
|
||||
else {
|
||||
snd_BUG();
|
||||
return -EINVAL;
|
||||
}
|
||||
if (snd_BUG_ON(!snd_pcm_format_linear(format->format)))
|
||||
return -ENXIO;
|
||||
|
||||
err = snd_pcm_plugin_build(plug, "Mu-Law<->linear conversion",
|
||||
src_format, dst_format,
|
||||
sizeof(struct mulaw_priv), &plugin);
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = (struct mulaw_priv *)plugin->extra_data;
|
||||
data->func = func;
|
||||
init_data(data, format->format);
|
||||
plugin->transfer = mulaw_transfer;
|
||||
*r_plugin = plugin;
|
||||
return 0;
|
||||
}
|
||||
3112
sound/core/oss/pcm_oss.c
Normal file
3112
sound/core/oss/pcm_oss.c
Normal file
File diff suppressed because it is too large
Load diff
758
sound/core/oss/pcm_plugin.c
Normal file
758
sound/core/oss/pcm_plugin.c
Normal file
|
|
@ -0,0 +1,758 @@
|
|||
/*
|
||||
* PCM Plug-In shared (kernel/library) code
|
||||
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
|
||||
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#if 0
|
||||
#define PLUGIN_DEBUG
|
||||
#endif
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include "pcm_plugin.h"
|
||||
|
||||
#define snd_pcm_plug_first(plug) ((plug)->runtime->oss.plugin_first)
|
||||
#define snd_pcm_plug_last(plug) ((plug)->runtime->oss.plugin_last)
|
||||
|
||||
/*
|
||||
* because some cards might have rates "very close", we ignore
|
||||
* all "resampling" requests within +-5%
|
||||
*/
|
||||
static int rate_match(unsigned int src_rate, unsigned int dst_rate)
|
||||
{
|
||||
unsigned int low = (src_rate * 95) / 100;
|
||||
unsigned int high = (src_rate * 105) / 100;
|
||||
return dst_rate >= low && dst_rate <= high;
|
||||
}
|
||||
|
||||
static int snd_pcm_plugin_alloc(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
|
||||
{
|
||||
struct snd_pcm_plugin_format *format;
|
||||
ssize_t width;
|
||||
size_t size;
|
||||
unsigned int channel;
|
||||
struct snd_pcm_plugin_channel *c;
|
||||
|
||||
if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
format = &plugin->src_format;
|
||||
} else {
|
||||
format = &plugin->dst_format;
|
||||
}
|
||||
if ((width = snd_pcm_format_physical_width(format->format)) < 0)
|
||||
return width;
|
||||
size = frames * format->channels * width;
|
||||
if (snd_BUG_ON(size % 8))
|
||||
return -ENXIO;
|
||||
size /= 8;
|
||||
if (plugin->buf_frames < frames) {
|
||||
vfree(plugin->buf);
|
||||
plugin->buf = vmalloc(size);
|
||||
plugin->buf_frames = frames;
|
||||
}
|
||||
if (!plugin->buf) {
|
||||
plugin->buf_frames = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
c = plugin->buf_channels;
|
||||
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
|
||||
for (channel = 0; channel < format->channels; channel++, c++) {
|
||||
c->frames = frames;
|
||||
c->enabled = 1;
|
||||
c->wanted = 0;
|
||||
c->area.addr = plugin->buf;
|
||||
c->area.first = channel * width;
|
||||
c->area.step = format->channels * width;
|
||||
}
|
||||
} else if (plugin->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) {
|
||||
if (snd_BUG_ON(size % format->channels))
|
||||
return -EINVAL;
|
||||
size /= format->channels;
|
||||
for (channel = 0; channel < format->channels; channel++, c++) {
|
||||
c->frames = frames;
|
||||
c->enabled = 1;
|
||||
c->wanted = 0;
|
||||
c->area.addr = plugin->buf + (channel * size);
|
||||
c->area.first = 0;
|
||||
c->area.step = width;
|
||||
}
|
||||
} else
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames)
|
||||
{
|
||||
int err;
|
||||
if (snd_BUG_ON(!snd_pcm_plug_first(plug)))
|
||||
return -ENXIO;
|
||||
if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
struct snd_pcm_plugin *plugin = snd_pcm_plug_first(plug);
|
||||
while (plugin->next) {
|
||||
if (plugin->dst_frames)
|
||||
frames = plugin->dst_frames(plugin, frames);
|
||||
if (snd_BUG_ON(frames <= 0))
|
||||
return -ENXIO;
|
||||
plugin = plugin->next;
|
||||
err = snd_pcm_plugin_alloc(plugin, frames);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
struct snd_pcm_plugin *plugin = snd_pcm_plug_last(plug);
|
||||
while (plugin->prev) {
|
||||
if (plugin->src_frames)
|
||||
frames = plugin->src_frames(plugin, frames);
|
||||
if (snd_BUG_ON(frames <= 0))
|
||||
return -ENXIO;
|
||||
plugin = plugin->prev;
|
||||
err = snd_pcm_plugin_alloc(plugin, frames);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin,
|
||||
snd_pcm_uframes_t frames,
|
||||
struct snd_pcm_plugin_channel **channels)
|
||||
{
|
||||
*channels = plugin->buf_channels;
|
||||
return frames;
|
||||
}
|
||||
|
||||
int snd_pcm_plugin_build(struct snd_pcm_substream *plug,
|
||||
const char *name,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
size_t extra,
|
||||
struct snd_pcm_plugin **ret)
|
||||
{
|
||||
struct snd_pcm_plugin *plugin;
|
||||
unsigned int channels;
|
||||
|
||||
if (snd_BUG_ON(!plug))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(!src_format || !dst_format))
|
||||
return -ENXIO;
|
||||
plugin = kzalloc(sizeof(*plugin) + extra, GFP_KERNEL);
|
||||
if (plugin == NULL)
|
||||
return -ENOMEM;
|
||||
plugin->name = name;
|
||||
plugin->plug = plug;
|
||||
plugin->stream = snd_pcm_plug_stream(plug);
|
||||
plugin->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
|
||||
plugin->src_format = *src_format;
|
||||
plugin->src_width = snd_pcm_format_physical_width(src_format->format);
|
||||
snd_BUG_ON(plugin->src_width <= 0);
|
||||
plugin->dst_format = *dst_format;
|
||||
plugin->dst_width = snd_pcm_format_physical_width(dst_format->format);
|
||||
snd_BUG_ON(plugin->dst_width <= 0);
|
||||
if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
channels = src_format->channels;
|
||||
else
|
||||
channels = dst_format->channels;
|
||||
plugin->buf_channels = kcalloc(channels, sizeof(*plugin->buf_channels), GFP_KERNEL);
|
||||
if (plugin->buf_channels == NULL) {
|
||||
snd_pcm_plugin_free(plugin);
|
||||
return -ENOMEM;
|
||||
}
|
||||
plugin->client_channels = snd_pcm_plugin_client_channels;
|
||||
*ret = plugin;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin)
|
||||
{
|
||||
if (! plugin)
|
||||
return 0;
|
||||
if (plugin->private_free)
|
||||
plugin->private_free(plugin);
|
||||
kfree(plugin->buf_channels);
|
||||
vfree(plugin->buf);
|
||||
kfree(plugin);
|
||||
return 0;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *plug, snd_pcm_uframes_t drv_frames)
|
||||
{
|
||||
struct snd_pcm_plugin *plugin, *plugin_prev, *plugin_next;
|
||||
int stream;
|
||||
|
||||
if (snd_BUG_ON(!plug))
|
||||
return -ENXIO;
|
||||
if (drv_frames == 0)
|
||||
return 0;
|
||||
stream = snd_pcm_plug_stream(plug);
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
plugin = snd_pcm_plug_last(plug);
|
||||
while (plugin && drv_frames > 0) {
|
||||
plugin_prev = plugin->prev;
|
||||
if (plugin->src_frames)
|
||||
drv_frames = plugin->src_frames(plugin, drv_frames);
|
||||
plugin = plugin_prev;
|
||||
}
|
||||
} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
plugin = snd_pcm_plug_first(plug);
|
||||
while (plugin && drv_frames > 0) {
|
||||
plugin_next = plugin->next;
|
||||
if (plugin->dst_frames)
|
||||
drv_frames = plugin->dst_frames(plugin, drv_frames);
|
||||
plugin = plugin_next;
|
||||
}
|
||||
} else
|
||||
snd_BUG();
|
||||
return drv_frames;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *plug, snd_pcm_uframes_t clt_frames)
|
||||
{
|
||||
struct snd_pcm_plugin *plugin, *plugin_prev, *plugin_next;
|
||||
snd_pcm_sframes_t frames;
|
||||
int stream;
|
||||
|
||||
if (snd_BUG_ON(!plug))
|
||||
return -ENXIO;
|
||||
if (clt_frames == 0)
|
||||
return 0;
|
||||
frames = clt_frames;
|
||||
stream = snd_pcm_plug_stream(plug);
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
plugin = snd_pcm_plug_first(plug);
|
||||
while (plugin && frames > 0) {
|
||||
plugin_next = plugin->next;
|
||||
if (plugin->dst_frames) {
|
||||
frames = plugin->dst_frames(plugin, frames);
|
||||
if (frames < 0)
|
||||
return frames;
|
||||
}
|
||||
plugin = plugin_next;
|
||||
}
|
||||
} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
plugin = snd_pcm_plug_last(plug);
|
||||
while (plugin) {
|
||||
plugin_prev = plugin->prev;
|
||||
if (plugin->src_frames) {
|
||||
frames = plugin->src_frames(plugin, frames);
|
||||
if (frames < 0)
|
||||
return frames;
|
||||
}
|
||||
plugin = plugin_prev;
|
||||
}
|
||||
} else
|
||||
snd_BUG();
|
||||
return frames;
|
||||
}
|
||||
|
||||
static int snd_pcm_plug_formats(struct snd_mask *mask, snd_pcm_format_t format)
|
||||
{
|
||||
struct snd_mask formats = *mask;
|
||||
u64 linfmts = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
|
||||
SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE |
|
||||
SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE |
|
||||
SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE |
|
||||
SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_S24_3LE |
|
||||
SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE |
|
||||
SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
|
||||
SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE);
|
||||
snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW);
|
||||
|
||||
if (formats.bits[0] & (u32)linfmts)
|
||||
formats.bits[0] |= (u32)linfmts;
|
||||
if (formats.bits[1] & (u32)(linfmts >> 32))
|
||||
formats.bits[1] |= (u32)(linfmts >> 32);
|
||||
return snd_mask_test(&formats, (__force int)format);
|
||||
}
|
||||
|
||||
static snd_pcm_format_t preferred_formats[] = {
|
||||
SNDRV_PCM_FORMAT_S16_LE,
|
||||
SNDRV_PCM_FORMAT_S16_BE,
|
||||
SNDRV_PCM_FORMAT_U16_LE,
|
||||
SNDRV_PCM_FORMAT_U16_BE,
|
||||
SNDRV_PCM_FORMAT_S24_3LE,
|
||||
SNDRV_PCM_FORMAT_S24_3BE,
|
||||
SNDRV_PCM_FORMAT_U24_3LE,
|
||||
SNDRV_PCM_FORMAT_U24_3BE,
|
||||
SNDRV_PCM_FORMAT_S24_LE,
|
||||
SNDRV_PCM_FORMAT_S24_BE,
|
||||
SNDRV_PCM_FORMAT_U24_LE,
|
||||
SNDRV_PCM_FORMAT_U24_BE,
|
||||
SNDRV_PCM_FORMAT_S32_LE,
|
||||
SNDRV_PCM_FORMAT_S32_BE,
|
||||
SNDRV_PCM_FORMAT_U32_LE,
|
||||
SNDRV_PCM_FORMAT_U32_BE,
|
||||
SNDRV_PCM_FORMAT_S8,
|
||||
SNDRV_PCM_FORMAT_U8
|
||||
};
|
||||
|
||||
snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format,
|
||||
struct snd_mask *format_mask)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (snd_mask_test(format_mask, (__force int)format))
|
||||
return format;
|
||||
if (!snd_pcm_plug_formats(format_mask, format))
|
||||
return (__force snd_pcm_format_t)-EINVAL;
|
||||
if (snd_pcm_format_linear(format)) {
|
||||
unsigned int width = snd_pcm_format_width(format);
|
||||
int unsignd = snd_pcm_format_unsigned(format) > 0;
|
||||
int big = snd_pcm_format_big_endian(format) > 0;
|
||||
unsigned int badness, best = -1;
|
||||
snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1;
|
||||
for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) {
|
||||
snd_pcm_format_t f = preferred_formats[i];
|
||||
unsigned int w;
|
||||
if (!snd_mask_test(format_mask, (__force int)f))
|
||||
continue;
|
||||
w = snd_pcm_format_width(f);
|
||||
if (w >= width)
|
||||
badness = w - width;
|
||||
else
|
||||
badness = width - w + 32;
|
||||
badness += snd_pcm_format_unsigned(f) != unsignd;
|
||||
badness += snd_pcm_format_big_endian(f) != big;
|
||||
if (badness < best) {
|
||||
best_format = f;
|
||||
best = badness;
|
||||
}
|
||||
}
|
||||
if ((__force int)best_format >= 0)
|
||||
return best_format;
|
||||
else
|
||||
return (__force snd_pcm_format_t)-EINVAL;
|
||||
} else {
|
||||
switch (format) {
|
||||
case SNDRV_PCM_FORMAT_MU_LAW:
|
||||
for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) {
|
||||
snd_pcm_format_t format1 = preferred_formats[i];
|
||||
if (snd_mask_test(format_mask, (__force int)format1))
|
||||
return format1;
|
||||
}
|
||||
default:
|
||||
return (__force snd_pcm_format_t)-EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int snd_pcm_plug_format_plugins(struct snd_pcm_substream *plug,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_params *slave_params)
|
||||
{
|
||||
struct snd_pcm_plugin_format tmpformat;
|
||||
struct snd_pcm_plugin_format dstformat;
|
||||
struct snd_pcm_plugin_format srcformat;
|
||||
snd_pcm_access_t src_access, dst_access;
|
||||
struct snd_pcm_plugin *plugin = NULL;
|
||||
int err;
|
||||
int stream = snd_pcm_plug_stream(plug);
|
||||
int slave_interleaved = (params_channels(slave_params) == 1 ||
|
||||
params_access(slave_params) == SNDRV_PCM_ACCESS_RW_INTERLEAVED);
|
||||
|
||||
switch (stream) {
|
||||
case SNDRV_PCM_STREAM_PLAYBACK:
|
||||
dstformat.format = params_format(slave_params);
|
||||
dstformat.rate = params_rate(slave_params);
|
||||
dstformat.channels = params_channels(slave_params);
|
||||
srcformat.format = params_format(params);
|
||||
srcformat.rate = params_rate(params);
|
||||
srcformat.channels = params_channels(params);
|
||||
src_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
|
||||
dst_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
|
||||
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
|
||||
break;
|
||||
case SNDRV_PCM_STREAM_CAPTURE:
|
||||
dstformat.format = params_format(params);
|
||||
dstformat.rate = params_rate(params);
|
||||
dstformat.channels = params_channels(params);
|
||||
srcformat.format = params_format(slave_params);
|
||||
srcformat.rate = params_rate(slave_params);
|
||||
srcformat.channels = params_channels(slave_params);
|
||||
src_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
|
||||
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
|
||||
dst_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
|
||||
break;
|
||||
default:
|
||||
snd_BUG();
|
||||
return -EINVAL;
|
||||
}
|
||||
tmpformat = srcformat;
|
||||
|
||||
pdprintf("srcformat: format=%i, rate=%i, channels=%i\n",
|
||||
srcformat.format,
|
||||
srcformat.rate,
|
||||
srcformat.channels);
|
||||
pdprintf("dstformat: format=%i, rate=%i, channels=%i\n",
|
||||
dstformat.format,
|
||||
dstformat.rate,
|
||||
dstformat.channels);
|
||||
|
||||
/* Format change (linearization) */
|
||||
if (! rate_match(srcformat.rate, dstformat.rate) &&
|
||||
! snd_pcm_format_linear(srcformat.format)) {
|
||||
if (srcformat.format != SNDRV_PCM_FORMAT_MU_LAW)
|
||||
return -EINVAL;
|
||||
tmpformat.format = SNDRV_PCM_FORMAT_S16;
|
||||
err = snd_pcm_plugin_build_mulaw(plug,
|
||||
&srcformat, &tmpformat,
|
||||
&plugin);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_pcm_plugin_append(plugin);
|
||||
if (err < 0) {
|
||||
snd_pcm_plugin_free(plugin);
|
||||
return err;
|
||||
}
|
||||
srcformat = tmpformat;
|
||||
src_access = dst_access;
|
||||
}
|
||||
|
||||
/* channels reduction */
|
||||
if (srcformat.channels > dstformat.channels) {
|
||||
tmpformat.channels = dstformat.channels;
|
||||
err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin);
|
||||
pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_pcm_plugin_append(plugin);
|
||||
if (err < 0) {
|
||||
snd_pcm_plugin_free(plugin);
|
||||
return err;
|
||||
}
|
||||
srcformat = tmpformat;
|
||||
src_access = dst_access;
|
||||
}
|
||||
|
||||
/* rate resampling */
|
||||
if (!rate_match(srcformat.rate, dstformat.rate)) {
|
||||
if (srcformat.format != SNDRV_PCM_FORMAT_S16) {
|
||||
/* convert to S16 for resampling */
|
||||
tmpformat.format = SNDRV_PCM_FORMAT_S16;
|
||||
err = snd_pcm_plugin_build_linear(plug,
|
||||
&srcformat, &tmpformat,
|
||||
&plugin);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_pcm_plugin_append(plugin);
|
||||
if (err < 0) {
|
||||
snd_pcm_plugin_free(plugin);
|
||||
return err;
|
||||
}
|
||||
srcformat = tmpformat;
|
||||
src_access = dst_access;
|
||||
}
|
||||
tmpformat.rate = dstformat.rate;
|
||||
err = snd_pcm_plugin_build_rate(plug,
|
||||
&srcformat, &tmpformat,
|
||||
&plugin);
|
||||
pdprintf("rate down resampling: src=%i, dst=%i returns %i\n", srcformat.rate, tmpformat.rate, err);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_pcm_plugin_append(plugin);
|
||||
if (err < 0) {
|
||||
snd_pcm_plugin_free(plugin);
|
||||
return err;
|
||||
}
|
||||
srcformat = tmpformat;
|
||||
src_access = dst_access;
|
||||
}
|
||||
|
||||
/* format change */
|
||||
if (srcformat.format != dstformat.format) {
|
||||
tmpformat.format = dstformat.format;
|
||||
if (srcformat.format == SNDRV_PCM_FORMAT_MU_LAW ||
|
||||
tmpformat.format == SNDRV_PCM_FORMAT_MU_LAW) {
|
||||
err = snd_pcm_plugin_build_mulaw(plug,
|
||||
&srcformat, &tmpformat,
|
||||
&plugin);
|
||||
}
|
||||
else if (snd_pcm_format_linear(srcformat.format) &&
|
||||
snd_pcm_format_linear(tmpformat.format)) {
|
||||
err = snd_pcm_plugin_build_linear(plug,
|
||||
&srcformat, &tmpformat,
|
||||
&plugin);
|
||||
}
|
||||
else
|
||||
return -EINVAL;
|
||||
pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_pcm_plugin_append(plugin);
|
||||
if (err < 0) {
|
||||
snd_pcm_plugin_free(plugin);
|
||||
return err;
|
||||
}
|
||||
srcformat = tmpformat;
|
||||
src_access = dst_access;
|
||||
}
|
||||
|
||||
/* channels extension */
|
||||
if (srcformat.channels < dstformat.channels) {
|
||||
tmpformat.channels = dstformat.channels;
|
||||
err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin);
|
||||
pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_pcm_plugin_append(plugin);
|
||||
if (err < 0) {
|
||||
snd_pcm_plugin_free(plugin);
|
||||
return err;
|
||||
}
|
||||
srcformat = tmpformat;
|
||||
src_access = dst_access;
|
||||
}
|
||||
|
||||
/* de-interleave */
|
||||
if (src_access != dst_access) {
|
||||
err = snd_pcm_plugin_build_copy(plug,
|
||||
&srcformat,
|
||||
&tmpformat,
|
||||
&plugin);
|
||||
pdprintf("interleave change (copy: returns %i)\n", err);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_pcm_plugin_append(plugin);
|
||||
if (err < 0) {
|
||||
snd_pcm_plugin_free(plugin);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *plug,
|
||||
char *buf,
|
||||
snd_pcm_uframes_t count,
|
||||
struct snd_pcm_plugin_channel **channels)
|
||||
{
|
||||
struct snd_pcm_plugin *plugin;
|
||||
struct snd_pcm_plugin_channel *v;
|
||||
struct snd_pcm_plugin_format *format;
|
||||
int width, nchannels, channel;
|
||||
int stream = snd_pcm_plug_stream(plug);
|
||||
|
||||
if (snd_BUG_ON(!buf))
|
||||
return -ENXIO;
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
plugin = snd_pcm_plug_first(plug);
|
||||
format = &plugin->src_format;
|
||||
} else {
|
||||
plugin = snd_pcm_plug_last(plug);
|
||||
format = &plugin->dst_format;
|
||||
}
|
||||
v = plugin->buf_channels;
|
||||
*channels = v;
|
||||
if ((width = snd_pcm_format_physical_width(format->format)) < 0)
|
||||
return width;
|
||||
nchannels = format->channels;
|
||||
if (snd_BUG_ON(plugin->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
|
||||
format->channels > 1))
|
||||
return -ENXIO;
|
||||
for (channel = 0; channel < nchannels; channel++, v++) {
|
||||
v->frames = count;
|
||||
v->enabled = 1;
|
||||
v->wanted = (stream == SNDRV_PCM_STREAM_CAPTURE);
|
||||
v->area.addr = buf;
|
||||
v->area.first = channel * width;
|
||||
v->area.step = nchannels * width;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *src_channels, snd_pcm_uframes_t size)
|
||||
{
|
||||
struct snd_pcm_plugin *plugin, *next;
|
||||
struct snd_pcm_plugin_channel *dst_channels;
|
||||
int err;
|
||||
snd_pcm_sframes_t frames = size;
|
||||
|
||||
plugin = snd_pcm_plug_first(plug);
|
||||
while (plugin && frames > 0) {
|
||||
if ((next = plugin->next) != NULL) {
|
||||
snd_pcm_sframes_t frames1 = frames;
|
||||
if (plugin->dst_frames)
|
||||
frames1 = plugin->dst_frames(plugin, frames);
|
||||
if ((err = next->client_channels(next, frames1, &dst_channels)) < 0) {
|
||||
return err;
|
||||
}
|
||||
if (err != frames1) {
|
||||
frames = err;
|
||||
if (plugin->src_frames)
|
||||
frames = plugin->src_frames(plugin, frames1);
|
||||
}
|
||||
} else
|
||||
dst_channels = NULL;
|
||||
pdprintf("write plugin: %s, %li\n", plugin->name, frames);
|
||||
if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
|
||||
return frames;
|
||||
src_channels = dst_channels;
|
||||
plugin = next;
|
||||
}
|
||||
return snd_pcm_plug_client_size(plug, frames);
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *dst_channels_final, snd_pcm_uframes_t size)
|
||||
{
|
||||
struct snd_pcm_plugin *plugin, *next;
|
||||
struct snd_pcm_plugin_channel *src_channels, *dst_channels;
|
||||
snd_pcm_sframes_t frames = size;
|
||||
int err;
|
||||
|
||||
frames = snd_pcm_plug_slave_size(plug, frames);
|
||||
if (frames < 0)
|
||||
return frames;
|
||||
|
||||
src_channels = NULL;
|
||||
plugin = snd_pcm_plug_first(plug);
|
||||
while (plugin && frames > 0) {
|
||||
if ((next = plugin->next) != NULL) {
|
||||
if ((err = plugin->client_channels(plugin, frames, &dst_channels)) < 0) {
|
||||
return err;
|
||||
}
|
||||
frames = err;
|
||||
} else {
|
||||
dst_channels = dst_channels_final;
|
||||
}
|
||||
pdprintf("read plugin: %s, %li\n", plugin->name, frames);
|
||||
if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
|
||||
return frames;
|
||||
plugin = next;
|
||||
src_channels = dst_channels;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_area, size_t dst_offset,
|
||||
size_t samples, snd_pcm_format_t format)
|
||||
{
|
||||
/* FIXME: sub byte resolution and odd dst_offset */
|
||||
unsigned char *dst;
|
||||
unsigned int dst_step;
|
||||
int width;
|
||||
const unsigned char *silence;
|
||||
if (!dst_area->addr)
|
||||
return 0;
|
||||
dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
|
||||
width = snd_pcm_format_physical_width(format);
|
||||
if (width <= 0)
|
||||
return -EINVAL;
|
||||
if (dst_area->step == (unsigned int) width && width >= 8)
|
||||
return snd_pcm_format_set_silence(format, dst, samples);
|
||||
silence = snd_pcm_format_silence_64(format);
|
||||
if (! silence)
|
||||
return -EINVAL;
|
||||
dst_step = dst_area->step / 8;
|
||||
if (width == 4) {
|
||||
/* Ima ADPCM */
|
||||
int dstbit = dst_area->first % 8;
|
||||
int dstbit_step = dst_area->step % 8;
|
||||
while (samples-- > 0) {
|
||||
if (dstbit)
|
||||
*dst &= 0xf0;
|
||||
else
|
||||
*dst &= 0x0f;
|
||||
dst += dst_step;
|
||||
dstbit += dstbit_step;
|
||||
if (dstbit == 8) {
|
||||
dst++;
|
||||
dstbit = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
width /= 8;
|
||||
while (samples-- > 0) {
|
||||
memcpy(dst, silence, width);
|
||||
dst += dst_step;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_area, size_t src_offset,
|
||||
const struct snd_pcm_channel_area *dst_area, size_t dst_offset,
|
||||
size_t samples, snd_pcm_format_t format)
|
||||
{
|
||||
/* FIXME: sub byte resolution and odd dst_offset */
|
||||
char *src, *dst;
|
||||
int width;
|
||||
int src_step, dst_step;
|
||||
src = src_area->addr + (src_area->first + src_area->step * src_offset) / 8;
|
||||
if (!src_area->addr)
|
||||
return snd_pcm_area_silence(dst_area, dst_offset, samples, format);
|
||||
dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
|
||||
if (!dst_area->addr)
|
||||
return 0;
|
||||
width = snd_pcm_format_physical_width(format);
|
||||
if (width <= 0)
|
||||
return -EINVAL;
|
||||
if (src_area->step == (unsigned int) width &&
|
||||
dst_area->step == (unsigned int) width && width >= 8) {
|
||||
size_t bytes = samples * width / 8;
|
||||
memcpy(dst, src, bytes);
|
||||
return 0;
|
||||
}
|
||||
src_step = src_area->step / 8;
|
||||
dst_step = dst_area->step / 8;
|
||||
if (width == 4) {
|
||||
/* Ima ADPCM */
|
||||
int srcbit = src_area->first % 8;
|
||||
int srcbit_step = src_area->step % 8;
|
||||
int dstbit = dst_area->first % 8;
|
||||
int dstbit_step = dst_area->step % 8;
|
||||
while (samples-- > 0) {
|
||||
unsigned char srcval;
|
||||
if (srcbit)
|
||||
srcval = *src & 0x0f;
|
||||
else
|
||||
srcval = (*src & 0xf0) >> 4;
|
||||
if (dstbit)
|
||||
*dst = (*dst & 0xf0) | srcval;
|
||||
else
|
||||
*dst = (*dst & 0x0f) | (srcval << 4);
|
||||
src += src_step;
|
||||
srcbit += srcbit_step;
|
||||
if (srcbit == 8) {
|
||||
src++;
|
||||
srcbit = 0;
|
||||
}
|
||||
dst += dst_step;
|
||||
dstbit += dstbit_step;
|
||||
if (dstbit == 8) {
|
||||
dst++;
|
||||
dstbit = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
width /= 8;
|
||||
while (samples-- > 0) {
|
||||
memcpy(dst, src, width);
|
||||
src += src_step;
|
||||
dst += dst_step;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
185
sound/core/oss/pcm_plugin.h
Normal file
185
sound/core/oss/pcm_plugin.h
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#ifndef __PCM_PLUGIN_H
|
||||
#define __PCM_PLUGIN_H
|
||||
|
||||
/*
|
||||
* Digital Audio (Plugin interface) abstract layer
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_SND_PCM_OSS_PLUGINS
|
||||
|
||||
#define snd_pcm_plug_stream(plug) ((plug)->stream)
|
||||
|
||||
enum snd_pcm_plugin_action {
|
||||
INIT = 0,
|
||||
PREPARE = 1,
|
||||
};
|
||||
|
||||
struct snd_pcm_channel_area {
|
||||
void *addr; /* base address of channel samples */
|
||||
unsigned int first; /* offset to first sample in bits */
|
||||
unsigned int step; /* samples distance in bits */
|
||||
};
|
||||
|
||||
struct snd_pcm_plugin_channel {
|
||||
void *aptr; /* pointer to the allocated area */
|
||||
struct snd_pcm_channel_area area;
|
||||
snd_pcm_uframes_t frames; /* allocated frames */
|
||||
unsigned int enabled:1; /* channel need to be processed */
|
||||
unsigned int wanted:1; /* channel is wanted */
|
||||
};
|
||||
|
||||
struct snd_pcm_plugin_format {
|
||||
snd_pcm_format_t format;
|
||||
unsigned int rate;
|
||||
unsigned int channels;
|
||||
};
|
||||
|
||||
struct snd_pcm_plugin {
|
||||
const char *name; /* plug-in name */
|
||||
int stream;
|
||||
struct snd_pcm_plugin_format src_format; /* source format */
|
||||
struct snd_pcm_plugin_format dst_format; /* destination format */
|
||||
int src_width; /* sample width in bits */
|
||||
int dst_width; /* sample width in bits */
|
||||
snd_pcm_access_t access;
|
||||
snd_pcm_sframes_t (*src_frames)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t dst_frames);
|
||||
snd_pcm_sframes_t (*dst_frames)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t src_frames);
|
||||
snd_pcm_sframes_t (*client_channels)(struct snd_pcm_plugin *plugin,
|
||||
snd_pcm_uframes_t frames,
|
||||
struct snd_pcm_plugin_channel **channels);
|
||||
snd_pcm_sframes_t (*transfer)(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames);
|
||||
int (*action)(struct snd_pcm_plugin *plugin,
|
||||
enum snd_pcm_plugin_action action,
|
||||
unsigned long data);
|
||||
struct snd_pcm_plugin *prev;
|
||||
struct snd_pcm_plugin *next;
|
||||
struct snd_pcm_substream *plug;
|
||||
void *private_data;
|
||||
void (*private_free)(struct snd_pcm_plugin *plugin);
|
||||
char *buf;
|
||||
snd_pcm_uframes_t buf_frames;
|
||||
struct snd_pcm_plugin_channel *buf_channels;
|
||||
char extra_data[0];
|
||||
};
|
||||
|
||||
int snd_pcm_plugin_build(struct snd_pcm_substream *handle,
|
||||
const char *name,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
size_t extra,
|
||||
struct snd_pcm_plugin **ret);
|
||||
int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin);
|
||||
int snd_pcm_plugin_clear(struct snd_pcm_plugin **first);
|
||||
int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames);
|
||||
snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size);
|
||||
snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size);
|
||||
|
||||
#define FULL ROUTE_PLUGIN_RESOLUTION
|
||||
#define HALF ROUTE_PLUGIN_RESOLUTION / 2
|
||||
|
||||
int snd_pcm_plugin_build_io(struct snd_pcm_substream *handle,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_plugin **r_plugin);
|
||||
int snd_pcm_plugin_build_linear(struct snd_pcm_substream *handle,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin);
|
||||
int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *handle,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin);
|
||||
int snd_pcm_plugin_build_rate(struct snd_pcm_substream *handle,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin);
|
||||
int snd_pcm_plugin_build_route(struct snd_pcm_substream *handle,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin);
|
||||
int snd_pcm_plugin_build_copy(struct snd_pcm_substream *handle,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin);
|
||||
|
||||
int snd_pcm_plug_format_plugins(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_params *slave_params);
|
||||
|
||||
snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format,
|
||||
struct snd_mask *format_mask);
|
||||
|
||||
int snd_pcm_plugin_append(struct snd_pcm_plugin *plugin);
|
||||
|
||||
snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *handle,
|
||||
struct snd_pcm_plugin_channel *src_channels,
|
||||
snd_pcm_uframes_t size);
|
||||
snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *handle,
|
||||
struct snd_pcm_plugin_channel *dst_channels_final,
|
||||
snd_pcm_uframes_t size);
|
||||
|
||||
snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *handle,
|
||||
char *buf, snd_pcm_uframes_t count,
|
||||
struct snd_pcm_plugin_channel **channels);
|
||||
|
||||
snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin,
|
||||
snd_pcm_uframes_t frames,
|
||||
struct snd_pcm_plugin_channel **channels);
|
||||
|
||||
int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_channel,
|
||||
size_t dst_offset,
|
||||
size_t samples, snd_pcm_format_t format);
|
||||
int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_channel,
|
||||
size_t src_offset,
|
||||
const struct snd_pcm_channel_area *dst_channel,
|
||||
size_t dst_offset,
|
||||
size_t samples, snd_pcm_format_t format);
|
||||
|
||||
void *snd_pcm_plug_buf_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t size);
|
||||
void snd_pcm_plug_buf_unlock(struct snd_pcm_substream *plug, void *ptr);
|
||||
snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream,
|
||||
const char *ptr, snd_pcm_uframes_t size,
|
||||
int in_kernel);
|
||||
snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream,
|
||||
char *ptr, snd_pcm_uframes_t size, int in_kernel);
|
||||
snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream,
|
||||
void **bufs, snd_pcm_uframes_t frames,
|
||||
int in_kernel);
|
||||
snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream,
|
||||
void **bufs, snd_pcm_uframes_t frames,
|
||||
int in_kernel);
|
||||
|
||||
#else
|
||||
|
||||
static inline snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size) { return drv_size; }
|
||||
static inline snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size) { return clt_size; }
|
||||
static inline int snd_pcm_plug_slave_format(int format, struct snd_mask *format_mask) { return format; }
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef PLUGIN_DEBUG
|
||||
#define pdprintf(fmt, args...) printk(KERN_DEBUG "plugin: " fmt, ##args)
|
||||
#else
|
||||
#define pdprintf(fmt, args...)
|
||||
#endif
|
||||
|
||||
#endif /* __PCM_PLUGIN_H */
|
||||
348
sound/core/oss/rate.c
Normal file
348
sound/core/oss/rate.c
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* Rate conversion Plug-In
|
||||
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include "pcm_plugin.h"
|
||||
|
||||
#define SHIFT 11
|
||||
#define BITS (1<<SHIFT)
|
||||
#define R_MASK (BITS-1)
|
||||
|
||||
/*
|
||||
* Basic rate conversion plugin
|
||||
*/
|
||||
|
||||
struct rate_channel {
|
||||
signed short last_S1;
|
||||
signed short last_S2;
|
||||
};
|
||||
|
||||
typedef void (*rate_f)(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
int src_frames, int dst_frames);
|
||||
|
||||
struct rate_priv {
|
||||
unsigned int pitch;
|
||||
unsigned int pos;
|
||||
rate_f func;
|
||||
snd_pcm_sframes_t old_src_frames, old_dst_frames;
|
||||
struct rate_channel channels[0];
|
||||
};
|
||||
|
||||
static void rate_init(struct snd_pcm_plugin *plugin)
|
||||
{
|
||||
unsigned int channel;
|
||||
struct rate_priv *data = (struct rate_priv *)plugin->extra_data;
|
||||
data->pos = 0;
|
||||
for (channel = 0; channel < plugin->src_format.channels; channel++) {
|
||||
data->channels[channel].last_S1 = 0;
|
||||
data->channels[channel].last_S2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void resample_expand(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
int src_frames, int dst_frames)
|
||||
{
|
||||
unsigned int pos = 0;
|
||||
signed int val;
|
||||
signed short S1, S2;
|
||||
signed short *src, *dst;
|
||||
unsigned int channel;
|
||||
int src_step, dst_step;
|
||||
int src_frames1, dst_frames1;
|
||||
struct rate_priv *data = (struct rate_priv *)plugin->extra_data;
|
||||
struct rate_channel *rchannels = data->channels;
|
||||
|
||||
for (channel = 0; channel < plugin->src_format.channels; channel++) {
|
||||
pos = data->pos;
|
||||
S1 = rchannels->last_S1;
|
||||
S2 = rchannels->last_S2;
|
||||
if (!src_channels[channel].enabled) {
|
||||
if (dst_channels[channel].wanted)
|
||||
snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
|
||||
dst_channels[channel].enabled = 0;
|
||||
continue;
|
||||
}
|
||||
dst_channels[channel].enabled = 1;
|
||||
src = (signed short *)src_channels[channel].area.addr +
|
||||
src_channels[channel].area.first / 8 / 2;
|
||||
dst = (signed short *)dst_channels[channel].area.addr +
|
||||
dst_channels[channel].area.first / 8 / 2;
|
||||
src_step = src_channels[channel].area.step / 8 / 2;
|
||||
dst_step = dst_channels[channel].area.step / 8 / 2;
|
||||
src_frames1 = src_frames;
|
||||
dst_frames1 = dst_frames;
|
||||
while (dst_frames1-- > 0) {
|
||||
if (pos & ~R_MASK) {
|
||||
pos &= R_MASK;
|
||||
S1 = S2;
|
||||
if (src_frames1-- > 0) {
|
||||
S2 = *src;
|
||||
src += src_step;
|
||||
}
|
||||
}
|
||||
val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
|
||||
if (val < -32768)
|
||||
val = -32768;
|
||||
else if (val > 32767)
|
||||
val = 32767;
|
||||
*dst = val;
|
||||
dst += dst_step;
|
||||
pos += data->pitch;
|
||||
}
|
||||
rchannels->last_S1 = S1;
|
||||
rchannels->last_S2 = S2;
|
||||
rchannels++;
|
||||
}
|
||||
data->pos = pos;
|
||||
}
|
||||
|
||||
static void resample_shrink(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
int src_frames, int dst_frames)
|
||||
{
|
||||
unsigned int pos = 0;
|
||||
signed int val;
|
||||
signed short S1, S2;
|
||||
signed short *src, *dst;
|
||||
unsigned int channel;
|
||||
int src_step, dst_step;
|
||||
int src_frames1, dst_frames1;
|
||||
struct rate_priv *data = (struct rate_priv *)plugin->extra_data;
|
||||
struct rate_channel *rchannels = data->channels;
|
||||
|
||||
for (channel = 0; channel < plugin->src_format.channels; ++channel) {
|
||||
pos = data->pos;
|
||||
S1 = rchannels->last_S1;
|
||||
S2 = rchannels->last_S2;
|
||||
if (!src_channels[channel].enabled) {
|
||||
if (dst_channels[channel].wanted)
|
||||
snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
|
||||
dst_channels[channel].enabled = 0;
|
||||
continue;
|
||||
}
|
||||
dst_channels[channel].enabled = 1;
|
||||
src = (signed short *)src_channels[channel].area.addr +
|
||||
src_channels[channel].area.first / 8 / 2;
|
||||
dst = (signed short *)dst_channels[channel].area.addr +
|
||||
dst_channels[channel].area.first / 8 / 2;
|
||||
src_step = src_channels[channel].area.step / 8 / 2;
|
||||
dst_step = dst_channels[channel].area.step / 8 / 2;
|
||||
src_frames1 = src_frames;
|
||||
dst_frames1 = dst_frames;
|
||||
while (dst_frames1 > 0) {
|
||||
S1 = S2;
|
||||
if (src_frames1-- > 0) {
|
||||
S2 = *src;
|
||||
src += src_step;
|
||||
}
|
||||
if (pos & ~R_MASK) {
|
||||
pos &= R_MASK;
|
||||
val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
|
||||
if (val < -32768)
|
||||
val = -32768;
|
||||
else if (val > 32767)
|
||||
val = 32767;
|
||||
*dst = val;
|
||||
dst += dst_step;
|
||||
dst_frames1--;
|
||||
}
|
||||
pos += data->pitch;
|
||||
}
|
||||
rchannels->last_S1 = S1;
|
||||
rchannels->last_S2 = S2;
|
||||
rchannels++;
|
||||
}
|
||||
data->pos = pos;
|
||||
}
|
||||
|
||||
static snd_pcm_sframes_t rate_src_frames(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
|
||||
{
|
||||
struct rate_priv *data;
|
||||
snd_pcm_sframes_t res;
|
||||
|
||||
if (snd_BUG_ON(!plugin))
|
||||
return -ENXIO;
|
||||
if (frames == 0)
|
||||
return 0;
|
||||
data = (struct rate_priv *)plugin->extra_data;
|
||||
if (plugin->src_format.rate < plugin->dst_format.rate) {
|
||||
res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
|
||||
} else {
|
||||
res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);
|
||||
}
|
||||
if (data->old_src_frames > 0) {
|
||||
snd_pcm_sframes_t frames1 = frames, res1 = data->old_dst_frames;
|
||||
while (data->old_src_frames < frames1) {
|
||||
frames1 >>= 1;
|
||||
res1 <<= 1;
|
||||
}
|
||||
while (data->old_src_frames > frames1) {
|
||||
frames1 <<= 1;
|
||||
res1 >>= 1;
|
||||
}
|
||||
if (data->old_src_frames == frames1)
|
||||
return res1;
|
||||
}
|
||||
data->old_src_frames = frames;
|
||||
data->old_dst_frames = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
static snd_pcm_sframes_t rate_dst_frames(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
|
||||
{
|
||||
struct rate_priv *data;
|
||||
snd_pcm_sframes_t res;
|
||||
|
||||
if (snd_BUG_ON(!plugin))
|
||||
return -ENXIO;
|
||||
if (frames == 0)
|
||||
return 0;
|
||||
data = (struct rate_priv *)plugin->extra_data;
|
||||
if (plugin->src_format.rate < plugin->dst_format.rate) {
|
||||
res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);
|
||||
} else {
|
||||
res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
|
||||
}
|
||||
if (data->old_dst_frames > 0) {
|
||||
snd_pcm_sframes_t frames1 = frames, res1 = data->old_src_frames;
|
||||
while (data->old_dst_frames < frames1) {
|
||||
frames1 >>= 1;
|
||||
res1 <<= 1;
|
||||
}
|
||||
while (data->old_dst_frames > frames1) {
|
||||
frames1 <<= 1;
|
||||
res1 >>= 1;
|
||||
}
|
||||
if (data->old_dst_frames == frames1)
|
||||
return res1;
|
||||
}
|
||||
data->old_dst_frames = frames;
|
||||
data->old_src_frames = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
static snd_pcm_sframes_t rate_transfer(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
snd_pcm_uframes_t dst_frames;
|
||||
struct rate_priv *data;
|
||||
|
||||
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
|
||||
return -ENXIO;
|
||||
if (frames == 0)
|
||||
return 0;
|
||||
#ifdef CONFIG_SND_DEBUG
|
||||
{
|
||||
unsigned int channel;
|
||||
for (channel = 0; channel < plugin->src_format.channels; channel++) {
|
||||
if (snd_BUG_ON(src_channels[channel].area.first % 8 ||
|
||||
src_channels[channel].area.step % 8))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(dst_channels[channel].area.first % 8 ||
|
||||
dst_channels[channel].area.step % 8))
|
||||
return -ENXIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
dst_frames = rate_dst_frames(plugin, frames);
|
||||
if (dst_frames > dst_channels[0].frames)
|
||||
dst_frames = dst_channels[0].frames;
|
||||
data = (struct rate_priv *)plugin->extra_data;
|
||||
data->func(plugin, src_channels, dst_channels, frames, dst_frames);
|
||||
return dst_frames;
|
||||
}
|
||||
|
||||
static int rate_action(struct snd_pcm_plugin *plugin,
|
||||
enum snd_pcm_plugin_action action,
|
||||
unsigned long udata)
|
||||
{
|
||||
if (snd_BUG_ON(!plugin))
|
||||
return -ENXIO;
|
||||
switch (action) {
|
||||
case INIT:
|
||||
case PREPARE:
|
||||
rate_init(plugin);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0; /* silenty ignore other actions */
|
||||
}
|
||||
|
||||
int snd_pcm_plugin_build_rate(struct snd_pcm_substream *plug,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin)
|
||||
{
|
||||
int err;
|
||||
struct rate_priv *data;
|
||||
struct snd_pcm_plugin *plugin;
|
||||
|
||||
if (snd_BUG_ON(!r_plugin))
|
||||
return -ENXIO;
|
||||
*r_plugin = NULL;
|
||||
|
||||
if (snd_BUG_ON(src_format->channels != dst_format->channels))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(src_format->channels <= 0))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(src_format->format != SNDRV_PCM_FORMAT_S16))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(dst_format->format != SNDRV_PCM_FORMAT_S16))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(src_format->rate == dst_format->rate))
|
||||
return -ENXIO;
|
||||
|
||||
err = snd_pcm_plugin_build(plug, "rate conversion",
|
||||
src_format, dst_format,
|
||||
sizeof(struct rate_priv) +
|
||||
src_format->channels * sizeof(struct rate_channel),
|
||||
&plugin);
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = (struct rate_priv *)plugin->extra_data;
|
||||
if (src_format->rate < dst_format->rate) {
|
||||
data->pitch = ((src_format->rate << SHIFT) + (dst_format->rate >> 1)) / dst_format->rate;
|
||||
data->func = resample_expand;
|
||||
} else {
|
||||
data->pitch = ((dst_format->rate << SHIFT) + (src_format->rate >> 1)) / src_format->rate;
|
||||
data->func = resample_shrink;
|
||||
}
|
||||
data->pos = 0;
|
||||
rate_init(plugin);
|
||||
data->old_src_frames = data->old_dst_frames = 0;
|
||||
plugin->transfer = rate_transfer;
|
||||
plugin->src_frames = rate_src_frames;
|
||||
plugin->dst_frames = rate_dst_frames;
|
||||
plugin->action = rate_action;
|
||||
*r_plugin = plugin;
|
||||
return 0;
|
||||
}
|
||||
109
sound/core/oss/route.c
Normal file
109
sound/core/oss/route.c
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Route Plug-In
|
||||
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include "pcm_plugin.h"
|
||||
|
||||
static void zero_areas(struct snd_pcm_plugin_channel *dvp, int ndsts,
|
||||
snd_pcm_uframes_t frames, snd_pcm_format_t format)
|
||||
{
|
||||
int dst = 0;
|
||||
for (; dst < ndsts; ++dst) {
|
||||
if (dvp->wanted)
|
||||
snd_pcm_area_silence(&dvp->area, 0, frames, format);
|
||||
dvp->enabled = 0;
|
||||
dvp++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void copy_area(const struct snd_pcm_plugin_channel *src_channel,
|
||||
struct snd_pcm_plugin_channel *dst_channel,
|
||||
snd_pcm_uframes_t frames, snd_pcm_format_t format)
|
||||
{
|
||||
dst_channel->enabled = 1;
|
||||
snd_pcm_area_copy(&src_channel->area, 0, &dst_channel->area, 0, frames, format);
|
||||
}
|
||||
|
||||
static snd_pcm_sframes_t route_transfer(struct snd_pcm_plugin *plugin,
|
||||
const struct snd_pcm_plugin_channel *src_channels,
|
||||
struct snd_pcm_plugin_channel *dst_channels,
|
||||
snd_pcm_uframes_t frames)
|
||||
{
|
||||
int nsrcs, ndsts, dst;
|
||||
struct snd_pcm_plugin_channel *dvp;
|
||||
snd_pcm_format_t format;
|
||||
|
||||
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
|
||||
return -ENXIO;
|
||||
if (frames == 0)
|
||||
return 0;
|
||||
|
||||
nsrcs = plugin->src_format.channels;
|
||||
ndsts = plugin->dst_format.channels;
|
||||
|
||||
format = plugin->dst_format.format;
|
||||
dvp = dst_channels;
|
||||
if (nsrcs <= 1) {
|
||||
/* expand to all channels */
|
||||
for (dst = 0; dst < ndsts; ++dst) {
|
||||
copy_area(src_channels, dvp, frames, format);
|
||||
dvp++;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
for (dst = 0; dst < ndsts && dst < nsrcs; ++dst) {
|
||||
copy_area(src_channels, dvp, frames, format);
|
||||
dvp++;
|
||||
src_channels++;
|
||||
}
|
||||
if (dst < ndsts)
|
||||
zero_areas(dvp, ndsts - dst, frames, format);
|
||||
return frames;
|
||||
}
|
||||
|
||||
int snd_pcm_plugin_build_route(struct snd_pcm_substream *plug,
|
||||
struct snd_pcm_plugin_format *src_format,
|
||||
struct snd_pcm_plugin_format *dst_format,
|
||||
struct snd_pcm_plugin **r_plugin)
|
||||
{
|
||||
struct snd_pcm_plugin *plugin;
|
||||
int err;
|
||||
|
||||
if (snd_BUG_ON(!r_plugin))
|
||||
return -ENXIO;
|
||||
*r_plugin = NULL;
|
||||
if (snd_BUG_ON(src_format->rate != dst_format->rate))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(src_format->format != dst_format->format))
|
||||
return -ENXIO;
|
||||
|
||||
err = snd_pcm_plugin_build(plug, "route conversion",
|
||||
src_format, dst_format, 0, &plugin);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
plugin->transfer = route_transfer;
|
||||
*r_plugin = plugin;
|
||||
return 0;
|
||||
}
|
||||
1257
sound/core/pcm.c
Normal file
1257
sound/core/pcm.c
Normal file
File diff suppressed because it is too large
Load diff
542
sound/core/pcm_compat.c
Normal file
542
sound/core/pcm_compat.c
Normal file
|
|
@ -0,0 +1,542 @@
|
|||
/*
|
||||
* 32bit -> 64bit ioctl wrapper for PCM API
|
||||
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
/* This file included from pcm_native.c */
|
||||
|
||||
#include <linux/compat.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static int snd_pcm_ioctl_delay_compat(struct snd_pcm_substream *substream,
|
||||
s32 __user *src)
|
||||
{
|
||||
snd_pcm_sframes_t delay;
|
||||
mm_segment_t fs;
|
||||
int err;
|
||||
|
||||
fs = snd_enter_user();
|
||||
err = snd_pcm_delay(substream, &delay);
|
||||
snd_leave_user(fs);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (put_user(delay, src))
|
||||
return -EFAULT;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_pcm_ioctl_rewind_compat(struct snd_pcm_substream *substream,
|
||||
u32 __user *src)
|
||||
{
|
||||
snd_pcm_uframes_t frames;
|
||||
int err;
|
||||
|
||||
if (get_user(frames, src))
|
||||
return -EFAULT;
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
err = snd_pcm_playback_rewind(substream, frames);
|
||||
else
|
||||
err = snd_pcm_capture_rewind(substream, frames);
|
||||
if (put_user(err, src))
|
||||
return -EFAULT;
|
||||
return err < 0 ? err : 0;
|
||||
}
|
||||
|
||||
static int snd_pcm_ioctl_forward_compat(struct snd_pcm_substream *substream,
|
||||
u32 __user *src)
|
||||
{
|
||||
snd_pcm_uframes_t frames;
|
||||
int err;
|
||||
|
||||
if (get_user(frames, src))
|
||||
return -EFAULT;
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
err = snd_pcm_playback_forward(substream, frames);
|
||||
else
|
||||
err = snd_pcm_capture_forward(substream, frames);
|
||||
if (put_user(err, src))
|
||||
return -EFAULT;
|
||||
return err < 0 ? err : 0;
|
||||
}
|
||||
|
||||
struct snd_pcm_hw_params32 {
|
||||
u32 flags;
|
||||
struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */
|
||||
struct snd_mask mres[5]; /* reserved masks */
|
||||
struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
|
||||
struct snd_interval ires[9]; /* reserved intervals */
|
||||
u32 rmask;
|
||||
u32 cmask;
|
||||
u32 info;
|
||||
u32 msbits;
|
||||
u32 rate_num;
|
||||
u32 rate_den;
|
||||
u32 fifo_size;
|
||||
unsigned char reserved[64];
|
||||
};
|
||||
|
||||
struct snd_pcm_sw_params32 {
|
||||
s32 tstamp_mode;
|
||||
u32 period_step;
|
||||
u32 sleep_min;
|
||||
u32 avail_min;
|
||||
u32 xfer_align;
|
||||
u32 start_threshold;
|
||||
u32 stop_threshold;
|
||||
u32 silence_threshold;
|
||||
u32 silence_size;
|
||||
u32 boundary;
|
||||
u32 proto;
|
||||
u32 tstamp_type;
|
||||
unsigned char reserved[56];
|
||||
};
|
||||
|
||||
/* recalcuate the boundary within 32bit */
|
||||
static snd_pcm_uframes_t recalculate_boundary(struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
snd_pcm_uframes_t boundary;
|
||||
|
||||
if (! runtime->buffer_size)
|
||||
return 0;
|
||||
boundary = runtime->buffer_size;
|
||||
while (boundary * 2 <= 0x7fffffffUL - runtime->buffer_size)
|
||||
boundary *= 2;
|
||||
return boundary;
|
||||
}
|
||||
|
||||
static int snd_pcm_ioctl_sw_params_compat(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_sw_params32 __user *src)
|
||||
{
|
||||
struct snd_pcm_sw_params params;
|
||||
snd_pcm_uframes_t boundary;
|
||||
int err;
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
if (get_user(params.tstamp_mode, &src->tstamp_mode) ||
|
||||
get_user(params.period_step, &src->period_step) ||
|
||||
get_user(params.sleep_min, &src->sleep_min) ||
|
||||
get_user(params.avail_min, &src->avail_min) ||
|
||||
get_user(params.xfer_align, &src->xfer_align) ||
|
||||
get_user(params.start_threshold, &src->start_threshold) ||
|
||||
get_user(params.stop_threshold, &src->stop_threshold) ||
|
||||
get_user(params.silence_threshold, &src->silence_threshold) ||
|
||||
get_user(params.silence_size, &src->silence_size) ||
|
||||
get_user(params.tstamp_type, &src->tstamp_type) ||
|
||||
get_user(params.proto, &src->proto))
|
||||
return -EFAULT;
|
||||
/*
|
||||
* Check silent_size parameter. Since we have 64bit boundary,
|
||||
* silence_size must be compared with the 32bit boundary.
|
||||
*/
|
||||
boundary = recalculate_boundary(substream->runtime);
|
||||
if (boundary && params.silence_size >= boundary)
|
||||
params.silence_size = substream->runtime->boundary;
|
||||
err = snd_pcm_sw_params(substream, ¶ms);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (boundary && put_user(boundary, &src->boundary))
|
||||
return -EFAULT;
|
||||
return err;
|
||||
}
|
||||
|
||||
struct snd_pcm_channel_info32 {
|
||||
u32 channel;
|
||||
u32 offset;
|
||||
u32 first;
|
||||
u32 step;
|
||||
};
|
||||
|
||||
static int snd_pcm_ioctl_channel_info_compat(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_channel_info32 __user *src)
|
||||
{
|
||||
struct snd_pcm_channel_info info;
|
||||
int err;
|
||||
|
||||
if (get_user(info.channel, &src->channel) ||
|
||||
get_user(info.offset, &src->offset) ||
|
||||
get_user(info.first, &src->first) ||
|
||||
get_user(info.step, &src->step))
|
||||
return -EFAULT;
|
||||
err = snd_pcm_channel_info(substream, &info);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (put_user(info.channel, &src->channel) ||
|
||||
put_user(info.offset, &src->offset) ||
|
||||
put_user(info.first, &src->first) ||
|
||||
put_user(info.step, &src->step))
|
||||
return -EFAULT;
|
||||
return err;
|
||||
}
|
||||
|
||||
struct snd_pcm_status32 {
|
||||
s32 state;
|
||||
struct compat_timespec trigger_tstamp;
|
||||
struct compat_timespec tstamp;
|
||||
u32 appl_ptr;
|
||||
u32 hw_ptr;
|
||||
s32 delay;
|
||||
u32 avail;
|
||||
u32 avail_max;
|
||||
u32 overrange;
|
||||
s32 suspended_state;
|
||||
u32 reserved_alignment;
|
||||
struct compat_timespec audio_tstamp;
|
||||
unsigned char reserved[56-sizeof(struct compat_timespec)];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
static int snd_pcm_status_user_compat(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_status32 __user *src)
|
||||
{
|
||||
struct snd_pcm_status status;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_status(substream, &status);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (clear_user(src, sizeof(*src)))
|
||||
return -EFAULT;
|
||||
if (put_user(status.state, &src->state) ||
|
||||
compat_put_timespec(&status.trigger_tstamp, &src->trigger_tstamp) ||
|
||||
compat_put_timespec(&status.tstamp, &src->tstamp) ||
|
||||
put_user(status.appl_ptr, &src->appl_ptr) ||
|
||||
put_user(status.hw_ptr, &src->hw_ptr) ||
|
||||
put_user(status.delay, &src->delay) ||
|
||||
put_user(status.avail, &src->avail) ||
|
||||
put_user(status.avail_max, &src->avail_max) ||
|
||||
put_user(status.overrange, &src->overrange) ||
|
||||
put_user(status.suspended_state, &src->suspended_state) ||
|
||||
compat_put_timespec(&status.audio_tstamp, &src->audio_tstamp))
|
||||
return -EFAULT;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* both for HW_PARAMS and HW_REFINE */
|
||||
static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream,
|
||||
int refine,
|
||||
struct snd_pcm_hw_params32 __user *data32)
|
||||
{
|
||||
struct snd_pcm_hw_params *data;
|
||||
struct snd_pcm_runtime *runtime;
|
||||
int err;
|
||||
|
||||
if (! (runtime = substream->runtime))
|
||||
return -ENOTTY;
|
||||
|
||||
/* only fifo_size is different, so just copy all */
|
||||
data = memdup_user(data32, sizeof(*data32));
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
if (refine)
|
||||
err = snd_pcm_hw_refine(substream, data);
|
||||
else
|
||||
err = snd_pcm_hw_params(substream, data);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
if (copy_to_user(data32, data, sizeof(*data32)) ||
|
||||
put_user(data->fifo_size, &data32->fifo_size)) {
|
||||
err = -EFAULT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (! refine) {
|
||||
unsigned int new_boundary = recalculate_boundary(runtime);
|
||||
if (new_boundary)
|
||||
runtime->boundary = new_boundary;
|
||||
}
|
||||
error:
|
||||
kfree(data);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*/
|
||||
struct snd_xferi32 {
|
||||
s32 result;
|
||||
u32 buf;
|
||||
u32 frames;
|
||||
};
|
||||
|
||||
static int snd_pcm_ioctl_xferi_compat(struct snd_pcm_substream *substream,
|
||||
int dir, struct snd_xferi32 __user *data32)
|
||||
{
|
||||
compat_caddr_t buf;
|
||||
u32 frames;
|
||||
int err;
|
||||
|
||||
if (! substream->runtime)
|
||||
return -ENOTTY;
|
||||
if (substream->stream != dir)
|
||||
return -EINVAL;
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
||||
return -EBADFD;
|
||||
|
||||
if (get_user(buf, &data32->buf) ||
|
||||
get_user(frames, &data32->frames))
|
||||
return -EFAULT;
|
||||
|
||||
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
err = snd_pcm_lib_write(substream, compat_ptr(buf), frames);
|
||||
else
|
||||
err = snd_pcm_lib_read(substream, compat_ptr(buf), frames);
|
||||
if (err < 0)
|
||||
return err;
|
||||
/* copy the result */
|
||||
if (put_user(err, &data32->result))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* snd_xfern needs remapping of bufs */
|
||||
struct snd_xfern32 {
|
||||
s32 result;
|
||||
u32 bufs; /* this is void **; */
|
||||
u32 frames;
|
||||
};
|
||||
|
||||
/*
|
||||
* xfern ioctl nees to copy (up to) 128 pointers on stack.
|
||||
* although we may pass the copied pointers through f_op->ioctl, but the ioctl
|
||||
* handler there expands again the same 128 pointers on stack, so it is better
|
||||
* to handle the function (calling pcm_readv/writev) directly in this handler.
|
||||
*/
|
||||
static int snd_pcm_ioctl_xfern_compat(struct snd_pcm_substream *substream,
|
||||
int dir, struct snd_xfern32 __user *data32)
|
||||
{
|
||||
compat_caddr_t buf;
|
||||
compat_caddr_t __user *bufptr;
|
||||
u32 frames;
|
||||
void __user **bufs;
|
||||
int err, ch, i;
|
||||
|
||||
if (! substream->runtime)
|
||||
return -ENOTTY;
|
||||
if (substream->stream != dir)
|
||||
return -EINVAL;
|
||||
|
||||
if ((ch = substream->runtime->channels) > 128)
|
||||
return -EINVAL;
|
||||
if (get_user(buf, &data32->bufs) ||
|
||||
get_user(frames, &data32->frames))
|
||||
return -EFAULT;
|
||||
bufptr = compat_ptr(buf);
|
||||
bufs = kmalloc(sizeof(void __user *) * ch, GFP_KERNEL);
|
||||
if (bufs == NULL)
|
||||
return -ENOMEM;
|
||||
for (i = 0; i < ch; i++) {
|
||||
u32 ptr;
|
||||
if (get_user(ptr, bufptr)) {
|
||||
kfree(bufs);
|
||||
return -EFAULT;
|
||||
}
|
||||
bufs[i] = compat_ptr(ptr);
|
||||
bufptr++;
|
||||
}
|
||||
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
err = snd_pcm_lib_writev(substream, bufs, frames);
|
||||
else
|
||||
err = snd_pcm_lib_readv(substream, bufs, frames);
|
||||
if (err >= 0) {
|
||||
if (put_user(err, &data32->result))
|
||||
err = -EFAULT;
|
||||
}
|
||||
kfree(bufs);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
struct snd_pcm_mmap_status32 {
|
||||
s32 state;
|
||||
s32 pad1;
|
||||
u32 hw_ptr;
|
||||
struct compat_timespec tstamp;
|
||||
s32 suspended_state;
|
||||
struct compat_timespec audio_tstamp;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct snd_pcm_mmap_control32 {
|
||||
u32 appl_ptr;
|
||||
u32 avail_min;
|
||||
};
|
||||
|
||||
struct snd_pcm_sync_ptr32 {
|
||||
u32 flags;
|
||||
union {
|
||||
struct snd_pcm_mmap_status32 status;
|
||||
unsigned char reserved[64];
|
||||
} s;
|
||||
union {
|
||||
struct snd_pcm_mmap_control32 control;
|
||||
unsigned char reserved[64];
|
||||
} c;
|
||||
} __attribute__((packed));
|
||||
|
||||
static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_sync_ptr32 __user *src)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
volatile struct snd_pcm_mmap_status *status;
|
||||
volatile struct snd_pcm_mmap_control *control;
|
||||
u32 sflags;
|
||||
struct snd_pcm_mmap_control scontrol;
|
||||
struct snd_pcm_mmap_status sstatus;
|
||||
snd_pcm_uframes_t boundary;
|
||||
int err;
|
||||
|
||||
if (snd_BUG_ON(!runtime))
|
||||
return -EINVAL;
|
||||
|
||||
if (get_user(sflags, &src->flags) ||
|
||||
get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
|
||||
get_user(scontrol.avail_min, &src->c.control.avail_min))
|
||||
return -EFAULT;
|
||||
if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
|
||||
err = snd_pcm_hwsync(substream);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
status = runtime->status;
|
||||
control = runtime->control;
|
||||
boundary = recalculate_boundary(runtime);
|
||||
if (! boundary)
|
||||
boundary = 0x7fffffff;
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
/* FIXME: we should consider the boundary for the sync from app */
|
||||
if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
|
||||
control->appl_ptr = scontrol.appl_ptr;
|
||||
else
|
||||
scontrol.appl_ptr = control->appl_ptr % boundary;
|
||||
if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
|
||||
control->avail_min = scontrol.avail_min;
|
||||
else
|
||||
scontrol.avail_min = control->avail_min;
|
||||
sstatus.state = status->state;
|
||||
sstatus.hw_ptr = status->hw_ptr % boundary;
|
||||
sstatus.tstamp = status->tstamp;
|
||||
sstatus.suspended_state = status->suspended_state;
|
||||
sstatus.audio_tstamp = status->audio_tstamp;
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (put_user(sstatus.state, &src->s.status.state) ||
|
||||
put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
|
||||
compat_put_timespec(&sstatus.tstamp, &src->s.status.tstamp) ||
|
||||
put_user(sstatus.suspended_state, &src->s.status.suspended_state) ||
|
||||
compat_put_timespec(&sstatus.audio_tstamp,
|
||||
&src->s.status.audio_tstamp) ||
|
||||
put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
|
||||
put_user(scontrol.avail_min, &src->c.control.avail_min))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*/
|
||||
enum {
|
||||
SNDRV_PCM_IOCTL_HW_REFINE32 = _IOWR('A', 0x10, struct snd_pcm_hw_params32),
|
||||
SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct snd_pcm_hw_params32),
|
||||
SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct snd_pcm_sw_params32),
|
||||
SNDRV_PCM_IOCTL_STATUS32 = _IOR('A', 0x20, struct snd_pcm_status32),
|
||||
SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32),
|
||||
SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct snd_pcm_channel_info32),
|
||||
SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32),
|
||||
SNDRV_PCM_IOCTL_FORWARD32 = _IOW('A', 0x49, u32),
|
||||
SNDRV_PCM_IOCTL_WRITEI_FRAMES32 = _IOW('A', 0x50, struct snd_xferi32),
|
||||
SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct snd_xferi32),
|
||||
SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct snd_xfern32),
|
||||
SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct snd_xfern32),
|
||||
SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct snd_pcm_sync_ptr32),
|
||||
|
||||
};
|
||||
|
||||
static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct snd_pcm_file *pcm_file;
|
||||
struct snd_pcm_substream *substream;
|
||||
void __user *argp = compat_ptr(arg);
|
||||
|
||||
pcm_file = file->private_data;
|
||||
if (! pcm_file)
|
||||
return -ENOTTY;
|
||||
substream = pcm_file->substream;
|
||||
if (! substream)
|
||||
return -ENOTTY;
|
||||
|
||||
/*
|
||||
* When PCM is used on 32bit mode, we need to disable
|
||||
* mmap of PCM status/control records because of the size
|
||||
* incompatibility.
|
||||
*/
|
||||
pcm_file->no_compat_mmap = 1;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_IOCTL_PVERSION:
|
||||
case SNDRV_PCM_IOCTL_INFO:
|
||||
case SNDRV_PCM_IOCTL_TSTAMP:
|
||||
case SNDRV_PCM_IOCTL_TTSTAMP:
|
||||
case SNDRV_PCM_IOCTL_HWSYNC:
|
||||
case SNDRV_PCM_IOCTL_PREPARE:
|
||||
case SNDRV_PCM_IOCTL_RESET:
|
||||
case SNDRV_PCM_IOCTL_START:
|
||||
case SNDRV_PCM_IOCTL_DROP:
|
||||
case SNDRV_PCM_IOCTL_DRAIN:
|
||||
case SNDRV_PCM_IOCTL_PAUSE:
|
||||
case SNDRV_PCM_IOCTL_HW_FREE:
|
||||
case SNDRV_PCM_IOCTL_RESUME:
|
||||
case SNDRV_PCM_IOCTL_XRUN:
|
||||
case SNDRV_PCM_IOCTL_LINK:
|
||||
case SNDRV_PCM_IOCTL_UNLINK:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
return snd_pcm_playback_ioctl1(file, substream, cmd, argp);
|
||||
else
|
||||
return snd_pcm_capture_ioctl1(file, substream, cmd, argp);
|
||||
case SNDRV_PCM_IOCTL_HW_REFINE32:
|
||||
return snd_pcm_ioctl_hw_params_compat(substream, 1, argp);
|
||||
case SNDRV_PCM_IOCTL_HW_PARAMS32:
|
||||
return snd_pcm_ioctl_hw_params_compat(substream, 0, argp);
|
||||
case SNDRV_PCM_IOCTL_SW_PARAMS32:
|
||||
return snd_pcm_ioctl_sw_params_compat(substream, argp);
|
||||
case SNDRV_PCM_IOCTL_STATUS32:
|
||||
return snd_pcm_status_user_compat(substream, argp);
|
||||
case SNDRV_PCM_IOCTL_SYNC_PTR32:
|
||||
return snd_pcm_ioctl_sync_ptr_compat(substream, argp);
|
||||
case SNDRV_PCM_IOCTL_CHANNEL_INFO32:
|
||||
return snd_pcm_ioctl_channel_info_compat(substream, argp);
|
||||
case SNDRV_PCM_IOCTL_WRITEI_FRAMES32:
|
||||
return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
|
||||
case SNDRV_PCM_IOCTL_READI_FRAMES32:
|
||||
return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
|
||||
case SNDRV_PCM_IOCTL_WRITEN_FRAMES32:
|
||||
return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
|
||||
case SNDRV_PCM_IOCTL_READN_FRAMES32:
|
||||
return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
|
||||
case SNDRV_PCM_IOCTL_DELAY32:
|
||||
return snd_pcm_ioctl_delay_compat(substream, argp);
|
||||
case SNDRV_PCM_IOCTL_REWIND32:
|
||||
return snd_pcm_ioctl_rewind_compat(substream, argp);
|
||||
case SNDRV_PCM_IOCTL_FORWARD32:
|
||||
return snd_pcm_ioctl_forward_compat(substream, argp);
|
||||
}
|
||||
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
371
sound/core/pcm_dmaengine.c
Normal file
371
sound/core/pcm_dmaengine.c
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* Copyright (C) 2012, Analog Devices Inc.
|
||||
* Author: Lars-Peter Clausen <lars@metafoo.de>
|
||||
*
|
||||
* Based on:
|
||||
* imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
||||
* mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc.
|
||||
* ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
|
||||
* Copyright (C) 2006 Applied Data Systems
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
struct dmaengine_pcm_runtime_data {
|
||||
struct dma_chan *dma_chan;
|
||||
dma_cookie_t cookie;
|
||||
|
||||
unsigned int pos;
|
||||
};
|
||||
|
||||
static inline struct dmaengine_pcm_runtime_data *substream_to_prtd(
|
||||
const struct snd_pcm_substream *substream)
|
||||
{
|
||||
return substream->runtime->private_data;
|
||||
}
|
||||
|
||||
struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
|
||||
|
||||
return prtd->dma_chan;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_chan);
|
||||
|
||||
/**
|
||||
* snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config
|
||||
* @substream: PCM substream
|
||||
* @params: hw_params
|
||||
* @slave_config: DMA slave config
|
||||
*
|
||||
* This function can be used to initialize a dma_slave_config from a substream
|
||||
* and hw_params in a dmaengine based PCM driver implementation.
|
||||
*/
|
||||
int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream,
|
||||
const struct snd_pcm_hw_params *params,
|
||||
struct dma_slave_config *slave_config)
|
||||
{
|
||||
enum dma_slave_buswidth buswidth;
|
||||
int bits;
|
||||
|
||||
bits = params_physical_width(params);
|
||||
if (bits < 8 || bits > 64)
|
||||
return -EINVAL;
|
||||
else if (bits == 8)
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||||
else if (bits == 16)
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
||||
else if (bits == 24)
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES;
|
||||
else if (bits <= 32)
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
else
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_8_BYTES;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
slave_config->direction = DMA_MEM_TO_DEV;
|
||||
slave_config->dst_addr_width = buswidth;
|
||||
} else {
|
||||
slave_config->direction = DMA_DEV_TO_MEM;
|
||||
slave_config->src_addr_width = buswidth;
|
||||
}
|
||||
|
||||
slave_config->device_fc = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hwparams_to_dma_slave_config);
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_set_config_from_dai_data() - Initializes a dma slave config
|
||||
* using DAI DMA data.
|
||||
* @substream: PCM substream
|
||||
* @dma_data: DAI DMA data
|
||||
* @slave_config: DMA slave configuration
|
||||
*
|
||||
* Initializes the {dst,src}_addr, {dst,src}_maxburst, {dst,src}_addr_width and
|
||||
* slave_id fields of the DMA slave config from the same fields of the DAI DMA
|
||||
* data struct. The src and dst fields will be initialized depending on the
|
||||
* direction of the substream. If the substream is a playback stream the dst
|
||||
* fields will be initialized, if it is a capture stream the src fields will be
|
||||
* initialized. The {dst,src}_addr_width field will only be initialized if the
|
||||
* addr_width field of the DAI DMA data struct is not equal to
|
||||
* DMA_SLAVE_BUSWIDTH_UNDEFINED.
|
||||
*/
|
||||
void snd_dmaengine_pcm_set_config_from_dai_data(
|
||||
const struct snd_pcm_substream *substream,
|
||||
const struct snd_dmaengine_dai_dma_data *dma_data,
|
||||
struct dma_slave_config *slave_config)
|
||||
{
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
slave_config->dst_addr = dma_data->addr;
|
||||
slave_config->dst_maxburst = dma_data->maxburst;
|
||||
if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
|
||||
slave_config->dst_addr_width = dma_data->addr_width;
|
||||
} else {
|
||||
slave_config->src_addr = dma_data->addr;
|
||||
slave_config->src_maxburst = dma_data->maxburst;
|
||||
if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
|
||||
slave_config->src_addr_width = dma_data->addr_width;
|
||||
}
|
||||
|
||||
slave_config->slave_id = dma_data->slave_id;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_set_config_from_dai_data);
|
||||
|
||||
static void dmaengine_pcm_dma_complete(void *arg)
|
||||
{
|
||||
struct snd_pcm_substream *substream = arg;
|
||||
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
|
||||
|
||||
prtd->pos += snd_pcm_lib_period_bytes(substream);
|
||||
if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream))
|
||||
prtd->pos = 0;
|
||||
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
|
||||
static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
|
||||
struct dma_chan *chan = prtd->dma_chan;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
enum dma_transfer_direction direction;
|
||||
unsigned long flags = DMA_CTRL_ACK;
|
||||
|
||||
direction = snd_pcm_substream_to_dma_direction(substream);
|
||||
|
||||
if (!substream->runtime->no_period_wakeup)
|
||||
flags |= DMA_PREP_INTERRUPT;
|
||||
|
||||
prtd->pos = 0;
|
||||
desc = dmaengine_prep_dma_cyclic(chan,
|
||||
substream->runtime->dma_addr,
|
||||
snd_pcm_lib_buffer_bytes(substream),
|
||||
snd_pcm_lib_period_bytes(substream), direction, flags);
|
||||
|
||||
if (!desc)
|
||||
return -ENOMEM;
|
||||
|
||||
desc->callback = dmaengine_pcm_dma_complete;
|
||||
desc->callback_param = substream;
|
||||
prtd->cookie = dmaengine_submit(desc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation
|
||||
* @substream: PCM substream
|
||||
* @cmd: Trigger command
|
||||
*
|
||||
* Returns 0 on success, a negative error code otherwise.
|
||||
*
|
||||
* This function can be used as the PCM trigger callback for dmaengine based PCM
|
||||
* driver implementations.
|
||||
*/
|
||||
int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int ret;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
ret = dmaengine_pcm_prepare_and_submit(substream);
|
||||
if (ret)
|
||||
return ret;
|
||||
dma_async_issue_pending(prtd->dma_chan);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
dmaengine_resume(prtd->dma_chan);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
if (runtime->info & SNDRV_PCM_INFO_PAUSE)
|
||||
dmaengine_pause(prtd->dma_chan);
|
||||
else
|
||||
dmaengine_terminate_all(prtd->dma_chan);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
dmaengine_pause(prtd->dma_chan);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
dmaengine_terminate_all(prtd->dma_chan);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger);
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_pointer_no_residue - dmaengine based PCM pointer implementation
|
||||
* @substream: PCM substream
|
||||
*
|
||||
* This function is deprecated and should not be used by new drivers, as its
|
||||
* results may be unreliable.
|
||||
*/
|
||||
snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
|
||||
return bytes_to_frames(substream->runtime, prtd->pos);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer_no_residue);
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation
|
||||
* @substream: PCM substream
|
||||
*
|
||||
* This function can be used as the PCM pointer callback for dmaengine based PCM
|
||||
* driver implementations.
|
||||
*/
|
||||
snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
|
||||
struct dma_tx_state state;
|
||||
enum dma_status status;
|
||||
unsigned int buf_size;
|
||||
unsigned int pos = 0;
|
||||
|
||||
status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state);
|
||||
if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) {
|
||||
buf_size = snd_pcm_lib_buffer_bytes(substream);
|
||||
if (state.residue > 0 && state.residue <= buf_size)
|
||||
pos = buf_size - state.residue;
|
||||
}
|
||||
|
||||
return bytes_to_frames(substream->runtime, pos);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer);
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM
|
||||
* @filter_fn: Filter function used to request the DMA channel
|
||||
* @filter_data: Data passed to the DMA filter function
|
||||
*
|
||||
* Returns NULL or the requested DMA channel.
|
||||
*
|
||||
* This function request a DMA channel for usage with dmaengine PCM.
|
||||
*/
|
||||
struct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn,
|
||||
void *filter_data)
|
||||
{
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
dma_cap_set(DMA_CYCLIC, mask);
|
||||
|
||||
return dma_request_channel(mask, filter_fn, filter_data);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel);
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_open - Open a dmaengine based PCM substream
|
||||
* @substream: PCM substream
|
||||
* @chan: DMA channel to use for data transfers
|
||||
*
|
||||
* Returns 0 on success, a negative error code otherwise.
|
||||
*
|
||||
* The function should usually be called from the pcm open callback. Note that
|
||||
* this function will use private_data field of the substream's runtime. So it
|
||||
* is not availabe to your pcm driver implementation.
|
||||
*/
|
||||
int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream,
|
||||
struct dma_chan *chan)
|
||||
{
|
||||
struct dmaengine_pcm_runtime_data *prtd;
|
||||
int ret;
|
||||
|
||||
if (!chan)
|
||||
return -ENXIO;
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
|
||||
if (!prtd)
|
||||
return -ENOMEM;
|
||||
|
||||
prtd->dma_chan = chan;
|
||||
|
||||
substream->runtime->private_data = prtd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open);
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_open_request_chan - Open a dmaengine based PCM substream and request channel
|
||||
* @substream: PCM substream
|
||||
* @filter_fn: Filter function used to request the DMA channel
|
||||
* @filter_data: Data passed to the DMA filter function
|
||||
*
|
||||
* Returns 0 on success, a negative error code otherwise.
|
||||
*
|
||||
* This function will request a DMA channel using the passed filter function and
|
||||
* data. The function should usually be called from the pcm open callback. Note
|
||||
* that this function will use private_data field of the substream's runtime. So
|
||||
* it is not availabe to your pcm driver implementation.
|
||||
*/
|
||||
int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream,
|
||||
dma_filter_fn filter_fn, void *filter_data)
|
||||
{
|
||||
return snd_dmaengine_pcm_open(substream,
|
||||
snd_dmaengine_pcm_request_channel(filter_fn, filter_data));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open_request_chan);
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_close - Close a dmaengine based PCM substream
|
||||
* @substream: PCM substream
|
||||
*/
|
||||
int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
|
||||
|
||||
kfree(prtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close);
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_release_chan_close - Close a dmaengine based PCM substream and release channel
|
||||
* @substream: PCM substream
|
||||
*
|
||||
* Releases the DMA channel associated with the PCM substream.
|
||||
*/
|
||||
int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
|
||||
|
||||
dma_release_channel(prtd->dma_chan);
|
||||
|
||||
return snd_dmaengine_pcm_close(substream);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
2600
sound/core/pcm_lib.c
Normal file
2600
sound/core/pcm_lib.c
Normal file
File diff suppressed because it is too large
Load diff
460
sound/core/pcm_memory.c
Normal file
460
sound/core/pcm_memory.c
Normal file
|
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
* Digital Audio (PCM) abstract layer
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/initval.h>
|
||||
|
||||
static int preallocate_dma = 1;
|
||||
module_param(preallocate_dma, int, 0444);
|
||||
MODULE_PARM_DESC(preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized.");
|
||||
|
||||
static int maximum_substreams = 4;
|
||||
module_param(maximum_substreams, int, 0444);
|
||||
MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory.");
|
||||
|
||||
static const size_t snd_minimum_buffer = 16384;
|
||||
|
||||
|
||||
/*
|
||||
* try to allocate as the large pages as possible.
|
||||
* stores the resultant memory size in *res_size.
|
||||
*
|
||||
* the minimum size is snd_minimum_buffer. it should be power of 2.
|
||||
*/
|
||||
static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t size)
|
||||
{
|
||||
struct snd_dma_buffer *dmab = &substream->dma_buffer;
|
||||
size_t orig_size = size;
|
||||
int err;
|
||||
|
||||
do {
|
||||
if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev,
|
||||
size, dmab)) < 0) {
|
||||
if (err != -ENOMEM)
|
||||
return err; /* fatal error */
|
||||
} else
|
||||
return 0;
|
||||
size >>= 1;
|
||||
} while (size >= snd_minimum_buffer);
|
||||
dmab->bytes = 0; /* tell error */
|
||||
pr_warn("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n",
|
||||
substream->pcm->card->number, substream->pcm->device,
|
||||
substream->stream ? 'c' : 'p', substream->number,
|
||||
substream->pcm->name, orig_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* release the preallocated buffer if not yet done.
|
||||
*/
|
||||
static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
if (substream->dma_buffer.area == NULL)
|
||||
return;
|
||||
snd_dma_free_pages(&substream->dma_buffer);
|
||||
substream->dma_buffer.area = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_preallocate_free - release the preallocated buffer of the specified substream.
|
||||
* @substream: the pcm substream instance
|
||||
*
|
||||
* Releases the pre-allocated buffer of the given substream.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_lib_preallocate_dma_free(substream);
|
||||
#ifdef CONFIG_SND_VERBOSE_PROCFS
|
||||
snd_info_free_entry(substream->proc_prealloc_max_entry);
|
||||
substream->proc_prealloc_max_entry = NULL;
|
||||
snd_info_free_entry(substream->proc_prealloc_entry);
|
||||
substream->proc_prealloc_entry = NULL;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm
|
||||
* @pcm: the pcm instance
|
||||
*
|
||||
* Releases all the pre-allocated buffers on the given pcm.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++)
|
||||
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
|
||||
snd_pcm_lib_preallocate_free(substream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all);
|
||||
|
||||
#ifdef CONFIG_SND_VERBOSE_PROCFS
|
||||
/*
|
||||
* read callback for prealloc proc file
|
||||
*
|
||||
* prints the current allocated size in kB.
|
||||
*/
|
||||
static void snd_pcm_lib_preallocate_proc_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_pcm_substream *substream = entry->private_data;
|
||||
snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_buffer.bytes / 1024);
|
||||
}
|
||||
|
||||
/*
|
||||
* read callback for prealloc_max proc file
|
||||
*
|
||||
* prints the maximum allowed size in kB.
|
||||
*/
|
||||
static void snd_pcm_lib_preallocate_max_proc_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_pcm_substream *substream = entry->private_data;
|
||||
snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_max / 1024);
|
||||
}
|
||||
|
||||
/*
|
||||
* write callback for prealloc proc file
|
||||
*
|
||||
* accepts the preallocation size in kB.
|
||||
*/
|
||||
static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_pcm_substream *substream = entry->private_data;
|
||||
char line[64], str[64];
|
||||
size_t size;
|
||||
struct snd_dma_buffer new_dmab;
|
||||
|
||||
if (substream->runtime) {
|
||||
buffer->error = -EBUSY;
|
||||
return;
|
||||
}
|
||||
if (!snd_info_get_line(buffer, line, sizeof(line))) {
|
||||
snd_info_get_str(str, line, sizeof(str));
|
||||
size = simple_strtoul(str, NULL, 10) * 1024;
|
||||
if ((size != 0 && size < 8192) || size > substream->dma_max) {
|
||||
buffer->error = -EINVAL;
|
||||
return;
|
||||
}
|
||||
if (substream->dma_buffer.bytes == size)
|
||||
return;
|
||||
memset(&new_dmab, 0, sizeof(new_dmab));
|
||||
new_dmab.dev = substream->dma_buffer.dev;
|
||||
if (size > 0) {
|
||||
if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
|
||||
substream->dma_buffer.dev.dev,
|
||||
size, &new_dmab) < 0) {
|
||||
buffer->error = -ENOMEM;
|
||||
return;
|
||||
}
|
||||
substream->buffer_bytes_max = size;
|
||||
} else {
|
||||
substream->buffer_bytes_max = UINT_MAX;
|
||||
}
|
||||
if (substream->dma_buffer.area)
|
||||
snd_dma_free_pages(&substream->dma_buffer);
|
||||
substream->dma_buffer = new_dmab;
|
||||
} else {
|
||||
buffer->error = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void preallocate_info_init(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) {
|
||||
entry->c.text.read = snd_pcm_lib_preallocate_proc_read;
|
||||
entry->c.text.write = snd_pcm_lib_preallocate_proc_write;
|
||||
entry->mode |= S_IWUSR;
|
||||
entry->private_data = substream;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
substream->proc_prealloc_entry = entry;
|
||||
if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max", substream->proc_root)) != NULL) {
|
||||
entry->c.text.read = snd_pcm_lib_preallocate_max_proc_read;
|
||||
entry->private_data = substream;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
substream->proc_prealloc_max_entry = entry;
|
||||
}
|
||||
|
||||
#else /* !CONFIG_SND_VERBOSE_PROCFS */
|
||||
#define preallocate_info_init(s)
|
||||
#endif /* CONFIG_SND_VERBOSE_PROCFS */
|
||||
|
||||
/*
|
||||
* pre-allocate the buffer and create a proc file for the substream
|
||||
*/
|
||||
static int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
|
||||
if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
|
||||
preallocate_pcm_pages(substream, size);
|
||||
|
||||
if (substream->dma_buffer.bytes > 0)
|
||||
substream->buffer_bytes_max = substream->dma_buffer.bytes;
|
||||
substream->dma_max = max;
|
||||
preallocate_info_init(substream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type
|
||||
* @substream: the pcm substream instance
|
||||
* @type: DMA type (SNDRV_DMA_TYPE_*)
|
||||
* @data: DMA type dependent data
|
||||
* @size: the requested pre-allocation size in bytes
|
||||
* @max: the max. allowed pre-allocation size
|
||||
*
|
||||
* Do pre-allocation for the given DMA buffer type.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
|
||||
int type, struct device *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
substream->dma_buffer.dev.type = type;
|
||||
substream->dma_buffer.dev.dev = data;
|
||||
return snd_pcm_lib_preallocate_pages1(substream, size, max);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continuous memory type (all substreams)
|
||||
* @pcm: the pcm instance
|
||||
* @type: DMA type (SNDRV_DMA_TYPE_*)
|
||||
* @data: DMA type dependent data
|
||||
* @size: the requested pre-allocation size in bytes
|
||||
* @max: the max. allowed pre-allocation size
|
||||
*
|
||||
* Do pre-allocation to all substreams of the given pcm for the
|
||||
* specified DMA type.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
|
||||
int type, void *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream, err;
|
||||
|
||||
for (stream = 0; stream < 2; stream++)
|
||||
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
|
||||
if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);
|
||||
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
/**
|
||||
* snd_pcm_sgbuf_ops_page - get the page struct at the given offset
|
||||
* @substream: the pcm substream instance
|
||||
* @offset: the buffer offset
|
||||
*
|
||||
* Used as the page callback of PCM ops.
|
||||
*
|
||||
* Return: The page struct at the given buffer offset. %NULL on failure.
|
||||
*/
|
||||
struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigned long offset)
|
||||
{
|
||||
struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
|
||||
|
||||
unsigned int idx = offset >> PAGE_SHIFT;
|
||||
if (idx >= (unsigned int)sgbuf->pages)
|
||||
return NULL;
|
||||
return sgbuf->page_table[idx];
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
|
||||
#endif /* CONFIG_SND_DMA_SGBUF */
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_malloc_pages - allocate the DMA buffer
|
||||
* @substream: the substream to allocate the DMA buffer to
|
||||
* @size: the requested buffer size in bytes
|
||||
*
|
||||
* Allocates the DMA buffer on the BUS type given earlier to
|
||||
* snd_pcm_lib_preallocate_xxx_pages().
|
||||
*
|
||||
* Return: 1 if the buffer is changed, 0 if not changed, or a negative
|
||||
* code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime;
|
||||
struct snd_dma_buffer *dmab = NULL;
|
||||
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -EINVAL;
|
||||
if (snd_BUG_ON(substream->dma_buffer.dev.type ==
|
||||
SNDRV_DMA_TYPE_UNKNOWN))
|
||||
return -EINVAL;
|
||||
runtime = substream->runtime;
|
||||
|
||||
if (runtime->dma_buffer_p) {
|
||||
/* perphaps, we might free the large DMA memory region
|
||||
to save some space here, but the actual solution
|
||||
costs us less time */
|
||||
if (runtime->dma_buffer_p->bytes >= size) {
|
||||
runtime->dma_bytes = size;
|
||||
return 0; /* ok, do not change */
|
||||
}
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
if (substream->dma_buffer.area != NULL &&
|
||||
substream->dma_buffer.bytes >= size) {
|
||||
dmab = &substream->dma_buffer; /* use the pre-allocated buffer */
|
||||
} else {
|
||||
dmab = kzalloc(sizeof(*dmab), GFP_KERNEL);
|
||||
if (! dmab)
|
||||
return -ENOMEM;
|
||||
dmab->dev = substream->dma_buffer.dev;
|
||||
if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
|
||||
substream->dma_buffer.dev.dev,
|
||||
size, dmab) < 0) {
|
||||
kfree(dmab);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
snd_pcm_set_runtime_buffer(substream, dmab);
|
||||
runtime->dma_bytes = size;
|
||||
return 1; /* area was changed */
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_lib_malloc_pages);
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_free_pages - release the allocated DMA buffer.
|
||||
* @substream: the substream to release the DMA buffer
|
||||
*
|
||||
* Releases the DMA buffer allocated via snd_pcm_lib_malloc_pages().
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime;
|
||||
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -EINVAL;
|
||||
runtime = substream->runtime;
|
||||
if (runtime->dma_area == NULL)
|
||||
return 0;
|
||||
if (runtime->dma_buffer_p != &substream->dma_buffer) {
|
||||
/* it's a newly allocated buffer. release it now. */
|
||||
snd_dma_free_pages(runtime->dma_buffer_p);
|
||||
kfree(runtime->dma_buffer_p);
|
||||
}
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_lib_free_pages);
|
||||
|
||||
int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream,
|
||||
size_t size, gfp_t gfp_flags)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime;
|
||||
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -EINVAL;
|
||||
runtime = substream->runtime;
|
||||
if (runtime->dma_area) {
|
||||
if (runtime->dma_bytes >= size)
|
||||
return 0; /* already large enough */
|
||||
vfree(runtime->dma_area);
|
||||
}
|
||||
runtime->dma_area = __vmalloc(size, gfp_flags, PAGE_KERNEL);
|
||||
if (!runtime->dma_area)
|
||||
return -ENOMEM;
|
||||
runtime->dma_bytes = size;
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL(_snd_pcm_lib_alloc_vmalloc_buffer);
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_free_vmalloc_buffer - free vmalloc buffer
|
||||
* @substream: the substream with a buffer allocated by
|
||||
* snd_pcm_lib_alloc_vmalloc_buffer()
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime;
|
||||
|
||||
if (PCM_RUNTIME_CHECK(substream))
|
||||
return -EINVAL;
|
||||
runtime = substream->runtime;
|
||||
vfree(runtime->dma_area);
|
||||
runtime->dma_area = NULL;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_lib_free_vmalloc_buffer);
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_get_vmalloc_page - map vmalloc buffer offset to page struct
|
||||
* @substream: the substream with a buffer allocated by
|
||||
* snd_pcm_lib_alloc_vmalloc_buffer()
|
||||
* @offset: offset in the buffer
|
||||
*
|
||||
* This function is to be used as the page callback in the PCM ops.
|
||||
*
|
||||
* Return: The page struct, or %NULL on failure.
|
||||
*/
|
||||
struct page *snd_pcm_lib_get_vmalloc_page(struct snd_pcm_substream *substream,
|
||||
unsigned long offset)
|
||||
{
|
||||
return vmalloc_to_page(substream->runtime->dma_area + offset);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_lib_get_vmalloc_page);
|
||||
567
sound/core/pcm_misc.c
Normal file
567
sound/core/pcm_misc.c
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
/*
|
||||
* PCM Interface - misc routines
|
||||
* Copyright (c) 1998 by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#define SND_PCM_FORMAT_UNKNOWN (-1)
|
||||
|
||||
/* NOTE: "signed" prefix must be given below since the default char is
|
||||
* unsigned on some architectures!
|
||||
*/
|
||||
struct pcm_format_data {
|
||||
unsigned char width; /* bit width */
|
||||
unsigned char phys; /* physical bit width */
|
||||
signed char le; /* 0 = big-endian, 1 = little-endian, -1 = others */
|
||||
signed char signd; /* 0 = unsigned, 1 = signed, -1 = others */
|
||||
unsigned char silence[8]; /* silence data to fill */
|
||||
};
|
||||
|
||||
/* we do lots of calculations on snd_pcm_format_t; shut up sparse */
|
||||
#define INT __force int
|
||||
|
||||
static struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = {
|
||||
[SNDRV_PCM_FORMAT_S8] = {
|
||||
.width = 8, .phys = 8, .le = -1, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U8] = {
|
||||
.width = 8, .phys = 8, .le = -1, .signd = 0,
|
||||
.silence = { 0x80 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S16_LE] = {
|
||||
.width = 16, .phys = 16, .le = 1, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S16_BE] = {
|
||||
.width = 16, .phys = 16, .le = 0, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U16_LE] = {
|
||||
.width = 16, .phys = 16, .le = 1, .signd = 0,
|
||||
.silence = { 0x00, 0x80 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U16_BE] = {
|
||||
.width = 16, .phys = 16, .le = 0, .signd = 0,
|
||||
.silence = { 0x80, 0x00 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S24_LE] = {
|
||||
.width = 24, .phys = 32, .le = 1, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S24_BE] = {
|
||||
.width = 24, .phys = 32, .le = 0, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U24_LE] = {
|
||||
.width = 24, .phys = 32, .le = 1, .signd = 0,
|
||||
.silence = { 0x00, 0x00, 0x80 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U24_BE] = {
|
||||
.width = 24, .phys = 32, .le = 0, .signd = 0,
|
||||
.silence = { 0x00, 0x80, 0x00, 0x00 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S32_LE] = {
|
||||
.width = 32, .phys = 32, .le = 1, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S32_BE] = {
|
||||
.width = 32, .phys = 32, .le = 0, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U32_LE] = {
|
||||
.width = 32, .phys = 32, .le = 1, .signd = 0,
|
||||
.silence = { 0x00, 0x00, 0x00, 0x80 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U32_BE] = {
|
||||
.width = 32, .phys = 32, .le = 0, .signd = 0,
|
||||
.silence = { 0x80, 0x00, 0x00, 0x00 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_FLOAT_LE] = {
|
||||
.width = 32, .phys = 32, .le = 1, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_FLOAT_BE] = {
|
||||
.width = 32, .phys = 32, .le = 0, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_FLOAT64_LE] = {
|
||||
.width = 64, .phys = 64, .le = 1, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_FLOAT64_BE] = {
|
||||
.width = 64, .phys = 64, .le = 0, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE] = {
|
||||
.width = 32, .phys = 32, .le = 1, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE] = {
|
||||
.width = 32, .phys = 32, .le = 0, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_MU_LAW] = {
|
||||
.width = 8, .phys = 8, .le = -1, .signd = -1,
|
||||
.silence = { 0x7f },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_A_LAW] = {
|
||||
.width = 8, .phys = 8, .le = -1, .signd = -1,
|
||||
.silence = { 0x55 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_IMA_ADPCM] = {
|
||||
.width = 4, .phys = 4, .le = -1, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_G723_24] = {
|
||||
.width = 3, .phys = 3, .le = -1, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_G723_40] = {
|
||||
.width = 5, .phys = 5, .le = -1, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_DSD_U8] = {
|
||||
.width = 8, .phys = 8, .le = 1, .signd = 0,
|
||||
.silence = { 0x69 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_DSD_U16_LE] = {
|
||||
.width = 16, .phys = 16, .le = 1, .signd = 0,
|
||||
.silence = { 0x69, 0x69 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_DSD_U32_LE] = {
|
||||
.width = 32, .phys = 32, .le = 1, .signd = 0,
|
||||
.silence = { 0x69, 0x69, 0x69, 0x69 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_DSD_U16_BE] = {
|
||||
.width = 16, .phys = 16, .le = 0, .signd = 0,
|
||||
.silence = { 0x69, 0x69 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_DSD_U32_BE] = {
|
||||
.width = 32, .phys = 32, .le = 0, .signd = 0,
|
||||
.silence = { 0x69, 0x69, 0x69, 0x69 },
|
||||
},
|
||||
/* FIXME: the following three formats are not defined properly yet */
|
||||
[SNDRV_PCM_FORMAT_MPEG] = {
|
||||
.le = -1, .signd = -1,
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_GSM] = {
|
||||
.le = -1, .signd = -1,
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_SPECIAL] = {
|
||||
.le = -1, .signd = -1,
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S24_3LE] = {
|
||||
.width = 24, .phys = 24, .le = 1, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S24_3BE] = {
|
||||
.width = 24, .phys = 24, .le = 0, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U24_3LE] = {
|
||||
.width = 24, .phys = 24, .le = 1, .signd = 0,
|
||||
.silence = { 0x00, 0x00, 0x80 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U24_3BE] = {
|
||||
.width = 24, .phys = 24, .le = 0, .signd = 0,
|
||||
.silence = { 0x80, 0x00, 0x00 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S20_3LE] = {
|
||||
.width = 20, .phys = 24, .le = 1, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S20_3BE] = {
|
||||
.width = 20, .phys = 24, .le = 0, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U20_3LE] = {
|
||||
.width = 20, .phys = 24, .le = 1, .signd = 0,
|
||||
.silence = { 0x00, 0x00, 0x08 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U20_3BE] = {
|
||||
.width = 20, .phys = 24, .le = 0, .signd = 0,
|
||||
.silence = { 0x08, 0x00, 0x00 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S18_3LE] = {
|
||||
.width = 18, .phys = 24, .le = 1, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_S18_3BE] = {
|
||||
.width = 18, .phys = 24, .le = 0, .signd = 1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U18_3LE] = {
|
||||
.width = 18, .phys = 24, .le = 1, .signd = 0,
|
||||
.silence = { 0x00, 0x00, 0x02 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_U18_3BE] = {
|
||||
.width = 18, .phys = 24, .le = 0, .signd = 0,
|
||||
.silence = { 0x02, 0x00, 0x00 },
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_G723_24_1B] = {
|
||||
.width = 3, .phys = 8, .le = -1, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
[SNDRV_PCM_FORMAT_G723_40_1B] = {
|
||||
.width = 5, .phys = 8, .le = -1, .signd = -1,
|
||||
.silence = {},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* snd_pcm_format_signed - Check the PCM format is signed linear
|
||||
* @format: the format to check
|
||||
*
|
||||
* Return: 1 if the given PCM format is signed linear, 0 if unsigned
|
||||
* linear, and a negative error code for non-linear formats.
|
||||
*/
|
||||
int snd_pcm_format_signed(snd_pcm_format_t format)
|
||||
{
|
||||
int val;
|
||||
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
|
||||
return -EINVAL;
|
||||
if ((val = pcm_formats[(INT)format].signd) < 0)
|
||||
return -EINVAL;
|
||||
return val;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_signed);
|
||||
|
||||
/**
|
||||
* snd_pcm_format_unsigned - Check the PCM format is unsigned linear
|
||||
* @format: the format to check
|
||||
*
|
||||
* Return: 1 if the given PCM format is unsigned linear, 0 if signed
|
||||
* linear, and a negative error code for non-linear formats.
|
||||
*/
|
||||
int snd_pcm_format_unsigned(snd_pcm_format_t format)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = snd_pcm_format_signed(format);
|
||||
if (val < 0)
|
||||
return val;
|
||||
return !val;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_unsigned);
|
||||
|
||||
/**
|
||||
* snd_pcm_format_linear - Check the PCM format is linear
|
||||
* @format: the format to check
|
||||
*
|
||||
* Return: 1 if the given PCM format is linear, 0 if not.
|
||||
*/
|
||||
int snd_pcm_format_linear(snd_pcm_format_t format)
|
||||
{
|
||||
return snd_pcm_format_signed(format) >= 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_linear);
|
||||
|
||||
/**
|
||||
* snd_pcm_format_little_endian - Check the PCM format is little-endian
|
||||
* @format: the format to check
|
||||
*
|
||||
* Return: 1 if the given PCM format is little-endian, 0 if
|
||||
* big-endian, or a negative error code if endian not specified.
|
||||
*/
|
||||
int snd_pcm_format_little_endian(snd_pcm_format_t format)
|
||||
{
|
||||
int val;
|
||||
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
|
||||
return -EINVAL;
|
||||
if ((val = pcm_formats[(INT)format].le) < 0)
|
||||
return -EINVAL;
|
||||
return val;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_little_endian);
|
||||
|
||||
/**
|
||||
* snd_pcm_format_big_endian - Check the PCM format is big-endian
|
||||
* @format: the format to check
|
||||
*
|
||||
* Return: 1 if the given PCM format is big-endian, 0 if
|
||||
* little-endian, or a negative error code if endian not specified.
|
||||
*/
|
||||
int snd_pcm_format_big_endian(snd_pcm_format_t format)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = snd_pcm_format_little_endian(format);
|
||||
if (val < 0)
|
||||
return val;
|
||||
return !val;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_big_endian);
|
||||
|
||||
/**
|
||||
* snd_pcm_format_width - return the bit-width of the format
|
||||
* @format: the format to check
|
||||
*
|
||||
* Return: The bit-width of the format, or a negative error code
|
||||
* if unknown format.
|
||||
*/
|
||||
int snd_pcm_format_width(snd_pcm_format_t format)
|
||||
{
|
||||
int val;
|
||||
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
|
||||
return -EINVAL;
|
||||
if ((val = pcm_formats[(INT)format].width) == 0)
|
||||
return -EINVAL;
|
||||
return val;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_width);
|
||||
|
||||
/**
|
||||
* snd_pcm_format_physical_width - return the physical bit-width of the format
|
||||
* @format: the format to check
|
||||
*
|
||||
* Return: The physical bit-width of the format, or a negative error code
|
||||
* if unknown format.
|
||||
*/
|
||||
int snd_pcm_format_physical_width(snd_pcm_format_t format)
|
||||
{
|
||||
int val;
|
||||
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
|
||||
return -EINVAL;
|
||||
if ((val = pcm_formats[(INT)format].phys) == 0)
|
||||
return -EINVAL;
|
||||
return val;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_physical_width);
|
||||
|
||||
/**
|
||||
* snd_pcm_format_size - return the byte size of samples on the given format
|
||||
* @format: the format to check
|
||||
* @samples: sampling rate
|
||||
*
|
||||
* Return: The byte size of the given samples for the format, or a
|
||||
* negative error code if unknown format.
|
||||
*/
|
||||
ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
|
||||
{
|
||||
int phys_width = snd_pcm_format_physical_width(format);
|
||||
if (phys_width < 0)
|
||||
return -EINVAL;
|
||||
return samples * phys_width / 8;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_size);
|
||||
|
||||
/**
|
||||
* snd_pcm_format_silence_64 - return the silent data in 8 bytes array
|
||||
* @format: the format to check
|
||||
*
|
||||
* Return: The format pattern to fill or %NULL if error.
|
||||
*/
|
||||
const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format)
|
||||
{
|
||||
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
|
||||
return NULL;
|
||||
if (! pcm_formats[(INT)format].phys)
|
||||
return NULL;
|
||||
return pcm_formats[(INT)format].silence;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_silence_64);
|
||||
|
||||
/**
|
||||
* snd_pcm_format_set_silence - set the silence data on the buffer
|
||||
* @format: the PCM format
|
||||
* @data: the buffer pointer
|
||||
* @samples: the number of samples to set silence
|
||||
*
|
||||
* Sets the silence data on the buffer for the given samples.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples)
|
||||
{
|
||||
int width;
|
||||
unsigned char *dst, *pat;
|
||||
|
||||
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
|
||||
return -EINVAL;
|
||||
if (samples == 0)
|
||||
return 0;
|
||||
width = pcm_formats[(INT)format].phys; /* physical width */
|
||||
pat = pcm_formats[(INT)format].silence;
|
||||
if (! width)
|
||||
return -EINVAL;
|
||||
/* signed or 1 byte data */
|
||||
if (pcm_formats[(INT)format].signd == 1 || width <= 8) {
|
||||
unsigned int bytes = samples * width / 8;
|
||||
memset(data, *pat, bytes);
|
||||
return 0;
|
||||
}
|
||||
/* non-zero samples, fill using a loop */
|
||||
width /= 8;
|
||||
dst = data;
|
||||
#if 0
|
||||
while (samples--) {
|
||||
memcpy(dst, pat, width);
|
||||
dst += width;
|
||||
}
|
||||
#else
|
||||
/* a bit optimization for constant width */
|
||||
switch (width) {
|
||||
case 2:
|
||||
while (samples--) {
|
||||
memcpy(dst, pat, 2);
|
||||
dst += 2;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
while (samples--) {
|
||||
memcpy(dst, pat, 3);
|
||||
dst += 3;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
while (samples--) {
|
||||
memcpy(dst, pat, 4);
|
||||
dst += 4;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
while (samples--) {
|
||||
memcpy(dst, pat, 8);
|
||||
dst += 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_format_set_silence);
|
||||
|
||||
/**
|
||||
* snd_pcm_limit_hw_rates - determine rate_min/rate_max fields
|
||||
* @runtime: the runtime instance
|
||||
*
|
||||
* Determines the rate_min and rate_max fields from the rates bits of
|
||||
* the given runtime->hw.
|
||||
*
|
||||
* Return: Zero if successful.
|
||||
*/
|
||||
int snd_pcm_limit_hw_rates(struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < (int)snd_pcm_known_rates.count; i++) {
|
||||
if (runtime->hw.rates & (1 << i)) {
|
||||
runtime->hw.rate_min = snd_pcm_known_rates.list[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = (int)snd_pcm_known_rates.count - 1; i >= 0; i--) {
|
||||
if (runtime->hw.rates & (1 << i)) {
|
||||
runtime->hw.rate_max = snd_pcm_known_rates.list[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_limit_hw_rates);
|
||||
|
||||
/**
|
||||
* snd_pcm_rate_to_rate_bit - converts sample rate to SNDRV_PCM_RATE_xxx bit
|
||||
* @rate: the sample rate to convert
|
||||
*
|
||||
* Return: The SNDRV_PCM_RATE_xxx flag that corresponds to the given rate, or
|
||||
* SNDRV_PCM_RATE_KNOT for an unknown rate.
|
||||
*/
|
||||
unsigned int snd_pcm_rate_to_rate_bit(unsigned int rate)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < snd_pcm_known_rates.count; i++)
|
||||
if (snd_pcm_known_rates.list[i] == rate)
|
||||
return 1u << i;
|
||||
return SNDRV_PCM_RATE_KNOT;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_rate_to_rate_bit);
|
||||
|
||||
/**
|
||||
* snd_pcm_rate_bit_to_rate - converts SNDRV_PCM_RATE_xxx bit to sample rate
|
||||
* @rate_bit: the rate bit to convert
|
||||
*
|
||||
* Return: The sample rate that corresponds to the given SNDRV_PCM_RATE_xxx flag
|
||||
* or 0 for an unknown rate bit.
|
||||
*/
|
||||
unsigned int snd_pcm_rate_bit_to_rate(unsigned int rate_bit)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < snd_pcm_known_rates.count; i++)
|
||||
if ((1u << i) == rate_bit)
|
||||
return snd_pcm_known_rates.list[i];
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_rate_bit_to_rate);
|
||||
|
||||
static unsigned int snd_pcm_rate_mask_sanitize(unsigned int rates)
|
||||
{
|
||||
if (rates & SNDRV_PCM_RATE_CONTINUOUS)
|
||||
return SNDRV_PCM_RATE_CONTINUOUS;
|
||||
else if (rates & SNDRV_PCM_RATE_KNOT)
|
||||
return SNDRV_PCM_RATE_KNOT;
|
||||
return rates;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_rate_mask_intersect - computes the intersection between two rate masks
|
||||
* @rates_a: The first rate mask
|
||||
* @rates_b: The second rate mask
|
||||
*
|
||||
* This function computes the rates that are supported by both rate masks passed
|
||||
* to the function. It will take care of the special handling of
|
||||
* SNDRV_PCM_RATE_CONTINUOUS and SNDRV_PCM_RATE_KNOT.
|
||||
*
|
||||
* Return: A rate mask containing the rates that are supported by both rates_a
|
||||
* and rates_b.
|
||||
*/
|
||||
unsigned int snd_pcm_rate_mask_intersect(unsigned int rates_a,
|
||||
unsigned int rates_b)
|
||||
{
|
||||
rates_a = snd_pcm_rate_mask_sanitize(rates_a);
|
||||
rates_b = snd_pcm_rate_mask_sanitize(rates_b);
|
||||
|
||||
if (rates_a & SNDRV_PCM_RATE_CONTINUOUS)
|
||||
return rates_b;
|
||||
else if (rates_b & SNDRV_PCM_RATE_CONTINUOUS)
|
||||
return rates_a;
|
||||
else if (rates_a & SNDRV_PCM_RATE_KNOT)
|
||||
return rates_b;
|
||||
else if (rates_b & SNDRV_PCM_RATE_KNOT)
|
||||
return rates_a;
|
||||
return rates_a & rates_b;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_rate_mask_intersect);
|
||||
3634
sound/core/pcm_native.c
Normal file
3634
sound/core/pcm_native.c
Normal file
File diff suppressed because it is too large
Load diff
143
sound/core/pcm_timer.c
Normal file
143
sound/core/pcm_timer.c
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Digital Audio (PCM) abstract layer
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <linux/gcd.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/timer.h>
|
||||
|
||||
/*
|
||||
* Timer functions
|
||||
*/
|
||||
|
||||
void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long rate, mult, fsize, l, post;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
mult = 1000000000;
|
||||
rate = runtime->rate;
|
||||
if (snd_BUG_ON(!rate))
|
||||
return;
|
||||
l = gcd(mult, rate);
|
||||
mult /= l;
|
||||
rate /= l;
|
||||
fsize = runtime->period_size;
|
||||
if (snd_BUG_ON(!fsize))
|
||||
return;
|
||||
l = gcd(rate, fsize);
|
||||
rate /= l;
|
||||
fsize /= l;
|
||||
post = 1;
|
||||
while ((mult * fsize) / fsize != mult) {
|
||||
mult /= 2;
|
||||
post *= 2;
|
||||
}
|
||||
if (rate == 0) {
|
||||
pcm_err(substream->pcm,
|
||||
"pcm timer resolution out of range (rate = %u, period_size = %lu)\n",
|
||||
runtime->rate, runtime->period_size);
|
||||
runtime->timer_resolution = -1;
|
||||
return;
|
||||
}
|
||||
runtime->timer_resolution = (mult * fsize / rate) * post;
|
||||
}
|
||||
|
||||
static unsigned long snd_pcm_timer_resolution(struct snd_timer * timer)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
|
||||
substream = timer->private_data;
|
||||
return substream->runtime ? substream->runtime->timer_resolution : 0;
|
||||
}
|
||||
|
||||
static int snd_pcm_timer_start(struct snd_timer * timer)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
|
||||
substream = snd_timer_chip(timer);
|
||||
substream->timer_running = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_pcm_timer_stop(struct snd_timer * timer)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
|
||||
substream = snd_timer_chip(timer);
|
||||
substream->timer_running = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_timer_hardware snd_pcm_timer =
|
||||
{
|
||||
.flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE,
|
||||
.resolution = 0,
|
||||
.ticks = 1,
|
||||
.c_resolution = snd_pcm_timer_resolution,
|
||||
.start = snd_pcm_timer_start,
|
||||
.stop = snd_pcm_timer_stop,
|
||||
};
|
||||
|
||||
/*
|
||||
* Init functions
|
||||
*/
|
||||
|
||||
static void snd_pcm_timer_free(struct snd_timer *timer)
|
||||
{
|
||||
struct snd_pcm_substream *substream = timer->private_data;
|
||||
substream->timer = NULL;
|
||||
}
|
||||
|
||||
void snd_pcm_timer_init(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_timer_id tid;
|
||||
struct snd_timer *timer;
|
||||
|
||||
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
|
||||
tid.dev_class = SNDRV_TIMER_CLASS_PCM;
|
||||
tid.card = substream->pcm->card->number;
|
||||
tid.device = substream->pcm->device;
|
||||
tid.subdevice = (substream->number << 1) | (substream->stream & 1);
|
||||
if (snd_timer_new(substream->pcm->card, "PCM", &tid, &timer) < 0)
|
||||
return;
|
||||
sprintf(timer->name, "PCM %s %i-%i-%i",
|
||||
substream->stream == SNDRV_PCM_STREAM_CAPTURE ?
|
||||
"capture" : "playback",
|
||||
tid.card, tid.device, tid.subdevice);
|
||||
timer->hw = snd_pcm_timer;
|
||||
if (snd_device_register(timer->card, timer) < 0) {
|
||||
snd_device_free(timer->card, timer);
|
||||
return;
|
||||
}
|
||||
timer->private_data = substream;
|
||||
timer->private_free = snd_pcm_timer_free;
|
||||
substream->timer = timer;
|
||||
}
|
||||
|
||||
void snd_pcm_timer_done(struct snd_pcm_substream *substream)
|
||||
{
|
||||
if (substream->timer) {
|
||||
snd_device_free(substream->pcm->card, substream->timer);
|
||||
substream->timer = NULL;
|
||||
}
|
||||
}
|
||||
1744
sound/core/rawmidi.c
Normal file
1744
sound/core/rawmidi.c
Normal file
File diff suppressed because it is too large
Load diff
120
sound/core/rawmidi_compat.c
Normal file
120
sound/core/rawmidi_compat.c
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 32bit -> 64bit ioctl wrapper for raw MIDI API
|
||||
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
/* This file included from rawmidi.c */
|
||||
|
||||
#include <linux/compat.h>
|
||||
|
||||
struct snd_rawmidi_params32 {
|
||||
s32 stream;
|
||||
u32 buffer_size;
|
||||
u32 avail_min;
|
||||
unsigned int no_active_sensing; /* avoid bit-field */
|
||||
unsigned char reserved[16];
|
||||
} __attribute__((packed));
|
||||
|
||||
static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile,
|
||||
struct snd_rawmidi_params32 __user *src)
|
||||
{
|
||||
struct snd_rawmidi_params params;
|
||||
unsigned int val;
|
||||
|
||||
if (rfile->output == NULL)
|
||||
return -EINVAL;
|
||||
if (get_user(params.stream, &src->stream) ||
|
||||
get_user(params.buffer_size, &src->buffer_size) ||
|
||||
get_user(params.avail_min, &src->avail_min) ||
|
||||
get_user(val, &src->no_active_sensing))
|
||||
return -EFAULT;
|
||||
params.no_active_sensing = val;
|
||||
switch (params.stream) {
|
||||
case SNDRV_RAWMIDI_STREAM_OUTPUT:
|
||||
return snd_rawmidi_output_params(rfile->output, ¶ms);
|
||||
case SNDRV_RAWMIDI_STREAM_INPUT:
|
||||
return snd_rawmidi_input_params(rfile->input, ¶ms);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
struct snd_rawmidi_status32 {
|
||||
s32 stream;
|
||||
struct compat_timespec tstamp;
|
||||
u32 avail;
|
||||
u32 xruns;
|
||||
unsigned char reserved[16];
|
||||
} __attribute__((packed));
|
||||
|
||||
static int snd_rawmidi_ioctl_status_compat(struct snd_rawmidi_file *rfile,
|
||||
struct snd_rawmidi_status32 __user *src)
|
||||
{
|
||||
int err;
|
||||
struct snd_rawmidi_status status;
|
||||
|
||||
if (rfile->output == NULL)
|
||||
return -EINVAL;
|
||||
if (get_user(status.stream, &src->stream))
|
||||
return -EFAULT;
|
||||
|
||||
switch (status.stream) {
|
||||
case SNDRV_RAWMIDI_STREAM_OUTPUT:
|
||||
err = snd_rawmidi_output_status(rfile->output, &status);
|
||||
break;
|
||||
case SNDRV_RAWMIDI_STREAM_INPUT:
|
||||
err = snd_rawmidi_input_status(rfile->input, &status);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
|
||||
put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
|
||||
put_user(status.avail, &src->avail) ||
|
||||
put_user(status.xruns, &src->xruns))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
SNDRV_RAWMIDI_IOCTL_PARAMS32 = _IOWR('W', 0x10, struct snd_rawmidi_params32),
|
||||
SNDRV_RAWMIDI_IOCTL_STATUS32 = _IOWR('W', 0x20, struct snd_rawmidi_status32),
|
||||
};
|
||||
|
||||
static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct snd_rawmidi_file *rfile;
|
||||
void __user *argp = compat_ptr(arg);
|
||||
|
||||
rfile = file->private_data;
|
||||
switch (cmd) {
|
||||
case SNDRV_RAWMIDI_IOCTL_PVERSION:
|
||||
case SNDRV_RAWMIDI_IOCTL_INFO:
|
||||
case SNDRV_RAWMIDI_IOCTL_DROP:
|
||||
case SNDRV_RAWMIDI_IOCTL_DRAIN:
|
||||
return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp);
|
||||
case SNDRV_RAWMIDI_IOCTL_PARAMS32:
|
||||
return snd_rawmidi_ioctl_params_compat(rfile, argp);
|
||||
case SNDRV_RAWMIDI_IOCTL_STATUS32:
|
||||
return snd_rawmidi_ioctl_status_compat(rfile, argp);
|
||||
}
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
187
sound/core/rtctimer.c
Normal file
187
sound/core/rtctimer.c
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* RTC based high-frequency timer
|
||||
*
|
||||
* Copyright (C) 2000 Takashi Iwai
|
||||
* based on rtctimer.c by Steve Ratcliffe
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/log2.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/timer.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_RTC)
|
||||
|
||||
#include <linux/mc146818rtc.h>
|
||||
|
||||
#define RTC_FREQ 1024 /* default frequency */
|
||||
#define NANO_SEC 1000000000L /* 10^9 in sec */
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static int rtctimer_open(struct snd_timer *t);
|
||||
static int rtctimer_close(struct snd_timer *t);
|
||||
static int rtctimer_start(struct snd_timer *t);
|
||||
static int rtctimer_stop(struct snd_timer *t);
|
||||
|
||||
|
||||
/*
|
||||
* The hardware dependent description for this timer.
|
||||
*/
|
||||
static struct snd_timer_hardware rtc_hw = {
|
||||
.flags = SNDRV_TIMER_HW_AUTO |
|
||||
SNDRV_TIMER_HW_FIRST |
|
||||
SNDRV_TIMER_HW_TASKLET,
|
||||
.ticks = 100000000L, /* FIXME: XXX */
|
||||
.open = rtctimer_open,
|
||||
.close = rtctimer_close,
|
||||
.start = rtctimer_start,
|
||||
.stop = rtctimer_stop,
|
||||
};
|
||||
|
||||
static int rtctimer_freq = RTC_FREQ; /* frequency */
|
||||
static struct snd_timer *rtctimer;
|
||||
static struct tasklet_struct rtc_tasklet;
|
||||
static rtc_task_t rtc_task;
|
||||
|
||||
|
||||
static int
|
||||
rtctimer_open(struct snd_timer *t)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = rtc_register(&rtc_task);
|
||||
if (err < 0)
|
||||
return err;
|
||||
t->private_data = &rtc_task;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rtctimer_close(struct snd_timer *t)
|
||||
{
|
||||
rtc_task_t *rtc = t->private_data;
|
||||
if (rtc) {
|
||||
rtc_unregister(rtc);
|
||||
tasklet_kill(&rtc_tasklet);
|
||||
t->private_data = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rtctimer_start(struct snd_timer *timer)
|
||||
{
|
||||
rtc_task_t *rtc = timer->private_data;
|
||||
if (snd_BUG_ON(!rtc))
|
||||
return -EINVAL;
|
||||
rtc_control(rtc, RTC_IRQP_SET, rtctimer_freq);
|
||||
rtc_control(rtc, RTC_PIE_ON, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rtctimer_stop(struct snd_timer *timer)
|
||||
{
|
||||
rtc_task_t *rtc = timer->private_data;
|
||||
if (snd_BUG_ON(!rtc))
|
||||
return -EINVAL;
|
||||
rtc_control(rtc, RTC_PIE_OFF, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rtctimer_tasklet(unsigned long data)
|
||||
{
|
||||
snd_timer_interrupt((struct snd_timer *)data, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* interrupt
|
||||
*/
|
||||
static void rtctimer_interrupt(void *private_data)
|
||||
{
|
||||
tasklet_schedule(private_data);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ENTRY functions
|
||||
*/
|
||||
static int __init rtctimer_init(void)
|
||||
{
|
||||
int err;
|
||||
struct snd_timer *timer;
|
||||
|
||||
if (rtctimer_freq < 2 || rtctimer_freq > 8192 ||
|
||||
!is_power_of_2(rtctimer_freq)) {
|
||||
pr_err("ALSA: rtctimer: invalid frequency %d\n", rtctimer_freq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Create a new timer and set up the fields */
|
||||
err = snd_timer_global_new("rtc", SNDRV_TIMER_GLOBAL_RTC, &timer);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
timer->module = THIS_MODULE;
|
||||
strcpy(timer->name, "RTC timer");
|
||||
timer->hw = rtc_hw;
|
||||
timer->hw.resolution = NANO_SEC / rtctimer_freq;
|
||||
|
||||
tasklet_init(&rtc_tasklet, rtctimer_tasklet, (unsigned long)timer);
|
||||
|
||||
/* set up RTC callback */
|
||||
rtc_task.func = rtctimer_interrupt;
|
||||
rtc_task.private_data = &rtc_tasklet;
|
||||
|
||||
err = snd_timer_global_register(timer);
|
||||
if (err < 0) {
|
||||
snd_timer_global_free(timer);
|
||||
return err;
|
||||
}
|
||||
rtctimer = timer; /* remember this */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit rtctimer_exit(void)
|
||||
{
|
||||
if (rtctimer) {
|
||||
snd_timer_global_free(rtctimer);
|
||||
rtctimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* exported stuff
|
||||
*/
|
||||
module_init(rtctimer_init)
|
||||
module_exit(rtctimer_exit)
|
||||
|
||||
module_param(rtctimer_freq, int, 0444);
|
||||
MODULE_PARM_DESC(rtctimer_freq, "timer frequency in Hz");
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_RTC));
|
||||
|
||||
#endif /* IS_ENABLED(CONFIG_RTC) */
|
||||
16
sound/core/seq/Kconfig
Normal file
16
sound/core/seq/Kconfig
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# define SND_XXX_SEQ to min(SND_SEQUENCER,SND_XXX)
|
||||
|
||||
config SND_RAWMIDI_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_RAWMIDI
|
||||
|
||||
config SND_OPL3_LIB_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_OPL3_LIB
|
||||
|
||||
config SND_OPL4_LIB_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_OPL4_LIB
|
||||
|
||||
config SND_SBAWE_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_SBAWE
|
||||
|
||||
config SND_EMU10K1_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_EMU10K1
|
||||
29
sound/core/seq/Makefile
Normal file
29
sound/core/seq/Makefile
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-seq-device-objs := seq_device.o
|
||||
snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \
|
||||
seq_fifo.o seq_prioq.o seq_timer.o \
|
||||
seq_system.o seq_ports.o seq_info.o
|
||||
snd-seq-midi-objs := seq_midi.o
|
||||
snd-seq-midi-emul-objs := seq_midi_emul.o
|
||||
snd-seq-midi-event-objs := seq_midi_event.o
|
||||
snd-seq-dummy-objs := seq_dummy.o
|
||||
snd-seq-virmidi-objs := seq_virmidi.o
|
||||
|
||||
obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o snd-seq-device.o
|
||||
ifeq ($(CONFIG_SND_SEQUENCER_OSS),y)
|
||||
obj-$(CONFIG_SND_SEQUENCER) += snd-seq-midi-event.o
|
||||
obj-$(CONFIG_SND_SEQUENCER) += oss/
|
||||
endif
|
||||
obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o
|
||||
|
||||
# Toplevel Module Dependency
|
||||
obj-$(CONFIG_SND_VIRMIDI) += snd-seq-virmidi.o snd-seq-midi-event.o
|
||||
obj-$(CONFIG_SND_RAWMIDI_SEQ) += snd-seq-midi.o snd-seq-midi-event.o
|
||||
obj-$(CONFIG_SND_OPL3_LIB_SEQ) += snd-seq-midi-event.o snd-seq-midi-emul.o
|
||||
obj-$(CONFIG_SND_OPL4_LIB_SEQ) += snd-seq-midi-event.o snd-seq-midi-emul.o
|
||||
obj-$(CONFIG_SND_SBAWE_SEQ) += snd-seq-midi-emul.o snd-seq-virmidi.o
|
||||
obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-seq-midi-emul.o snd-seq-virmidi.o
|
||||
10
sound/core/seq/oss/Makefile
Normal file
10
sound/core/seq/oss/Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-seq-oss-objs := seq_oss.o seq_oss_init.o seq_oss_timer.o seq_oss_ioctl.o \
|
||||
seq_oss_event.o seq_oss_rw.o seq_oss_synth.o \
|
||||
seq_oss_midi.o seq_oss_readq.o seq_oss_writeq.o
|
||||
|
||||
obj-$(CONFIG_SND_SEQUENCER) += snd-seq-oss.o
|
||||
302
sound/core/seq/oss/seq_oss.c
Normal file
302
sound/core/seq/oss/seq_oss.c
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* OSS compatible sequencer driver
|
||||
*
|
||||
* registration of device and proc
|
||||
*
|
||||
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/minors.h>
|
||||
#include <sound/initval.h>
|
||||
#include "seq_oss_device.h"
|
||||
#include "seq_oss_synth.h"
|
||||
|
||||
/*
|
||||
* module option
|
||||
*/
|
||||
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
|
||||
MODULE_DESCRIPTION("OSS-compatible sequencer module");
|
||||
MODULE_LICENSE("GPL");
|
||||
/* Takashi says this is really only for sound-service-0-, but this is OK. */
|
||||
MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_SEQUENCER);
|
||||
MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MUSIC);
|
||||
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static int register_device(void);
|
||||
static void unregister_device(void);
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static int register_proc(void);
|
||||
static void unregister_proc(void);
|
||||
#else
|
||||
static inline int register_proc(void) { return 0; }
|
||||
static inline void unregister_proc(void) {}
|
||||
#endif
|
||||
|
||||
static int odev_open(struct inode *inode, struct file *file);
|
||||
static int odev_release(struct inode *inode, struct file *file);
|
||||
static ssize_t odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset);
|
||||
static ssize_t odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset);
|
||||
static long odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
|
||||
static unsigned int odev_poll(struct file *file, poll_table * wait);
|
||||
|
||||
|
||||
/*
|
||||
* module interface
|
||||
*/
|
||||
|
||||
static int __init alsa_seq_oss_init(void)
|
||||
{
|
||||
int rc;
|
||||
static struct snd_seq_dev_ops ops = {
|
||||
snd_seq_oss_synth_register,
|
||||
snd_seq_oss_synth_unregister,
|
||||
};
|
||||
|
||||
snd_seq_autoload_lock();
|
||||
if ((rc = register_device()) < 0)
|
||||
goto error;
|
||||
if ((rc = register_proc()) < 0) {
|
||||
unregister_device();
|
||||
goto error;
|
||||
}
|
||||
if ((rc = snd_seq_oss_create_client()) < 0) {
|
||||
unregister_proc();
|
||||
unregister_device();
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((rc = snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OSS, &ops,
|
||||
sizeof(struct snd_seq_oss_reg))) < 0) {
|
||||
snd_seq_oss_delete_client();
|
||||
unregister_proc();
|
||||
unregister_device();
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* success */
|
||||
snd_seq_oss_synth_init();
|
||||
|
||||
error:
|
||||
snd_seq_autoload_unlock();
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void __exit alsa_seq_oss_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OSS);
|
||||
snd_seq_oss_delete_client();
|
||||
unregister_proc();
|
||||
unregister_device();
|
||||
}
|
||||
|
||||
module_init(alsa_seq_oss_init)
|
||||
module_exit(alsa_seq_oss_exit)
|
||||
|
||||
/*
|
||||
* ALSA minor device interface
|
||||
*/
|
||||
|
||||
static DEFINE_MUTEX(register_mutex);
|
||||
|
||||
static int
|
||||
odev_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
int level, rc;
|
||||
|
||||
if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC)
|
||||
level = SNDRV_SEQ_OSS_MODE_MUSIC;
|
||||
else
|
||||
level = SNDRV_SEQ_OSS_MODE_SYNTH;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
rc = snd_seq_oss_open(file, level);
|
||||
mutex_unlock(®ister_mutex);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
odev_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
|
||||
if ((dp = file->private_data) == NULL)
|
||||
return 0;
|
||||
|
||||
snd_seq_oss_drain_write(dp);
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
snd_seq_oss_release(dp);
|
||||
mutex_unlock(®ister_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
dp = file->private_data;
|
||||
if (snd_BUG_ON(!dp))
|
||||
return -ENXIO;
|
||||
return snd_seq_oss_read(dp, buf, count);
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
dp = file->private_data;
|
||||
if (snd_BUG_ON(!dp))
|
||||
return -ENXIO;
|
||||
return snd_seq_oss_write(dp, buf, count, file);
|
||||
}
|
||||
|
||||
static long
|
||||
odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
dp = file->private_data;
|
||||
if (snd_BUG_ON(!dp))
|
||||
return -ENXIO;
|
||||
return snd_seq_oss_ioctl(dp, cmd, arg);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
#define odev_ioctl_compat odev_ioctl
|
||||
#else
|
||||
#define odev_ioctl_compat NULL
|
||||
#endif
|
||||
|
||||
static unsigned int
|
||||
odev_poll(struct file *file, poll_table * wait)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
dp = file->private_data;
|
||||
if (snd_BUG_ON(!dp))
|
||||
return -ENXIO;
|
||||
return snd_seq_oss_poll(dp, file, wait);
|
||||
}
|
||||
|
||||
/*
|
||||
* registration of sequencer minor device
|
||||
*/
|
||||
|
||||
static const struct file_operations seq_oss_f_ops =
|
||||
{
|
||||
.owner = THIS_MODULE,
|
||||
.read = odev_read,
|
||||
.write = odev_write,
|
||||
.open = odev_open,
|
||||
.release = odev_release,
|
||||
.poll = odev_poll,
|
||||
.unlocked_ioctl = odev_ioctl,
|
||||
.compat_ioctl = odev_ioctl_compat,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
static int __init
|
||||
register_device(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER,
|
||||
NULL, 0,
|
||||
&seq_oss_f_ops, NULL)) < 0) {
|
||||
pr_err("ALSA: seq_oss: can't register device seq\n");
|
||||
mutex_unlock(®ister_mutex);
|
||||
return rc;
|
||||
}
|
||||
if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC,
|
||||
NULL, 0,
|
||||
&seq_oss_f_ops, NULL)) < 0) {
|
||||
pr_err("ALSA: seq_oss: can't register device music\n");
|
||||
snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0);
|
||||
mutex_unlock(®ister_mutex);
|
||||
return rc;
|
||||
}
|
||||
mutex_unlock(®ister_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
unregister_device(void)
|
||||
{
|
||||
mutex_lock(®ister_mutex);
|
||||
if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0)
|
||||
pr_err("ALSA: seq_oss: error unregister device music\n");
|
||||
if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0)
|
||||
pr_err("ALSA: seq_oss: error unregister device seq\n");
|
||||
mutex_unlock(®ister_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* /proc interface
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
||||
static struct snd_info_entry *info_entry;
|
||||
|
||||
static void
|
||||
info_read(struct snd_info_entry *entry, struct snd_info_buffer *buf)
|
||||
{
|
||||
mutex_lock(®ister_mutex);
|
||||
snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR);
|
||||
snd_seq_oss_system_info_read(buf);
|
||||
snd_seq_oss_synth_info_read(buf);
|
||||
snd_seq_oss_midi_info_read(buf);
|
||||
mutex_unlock(®ister_mutex);
|
||||
}
|
||||
|
||||
|
||||
static int __init
|
||||
register_proc(void)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_module_entry(THIS_MODULE, SNDRV_SEQ_OSS_PROCNAME, snd_seq_root);
|
||||
if (entry == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
entry->content = SNDRV_INFO_CONTENT_TEXT;
|
||||
entry->private_data = NULL;
|
||||
entry->c.text.read = info_read;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
return -ENOMEM;
|
||||
}
|
||||
info_entry = entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
unregister_proc(void)
|
||||
{
|
||||
snd_info_free_entry(info_entry);
|
||||
info_entry = NULL;
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
176
sound/core/seq/oss/seq_oss_device.h
Normal file
176
sound/core/seq/oss/seq_oss_device.h
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* OSS compatible sequencer driver
|
||||
*
|
||||
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef __SEQ_OSS_DEVICE_H
|
||||
#define __SEQ_OSS_DEVICE_H
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/seq_oss.h>
|
||||
#include <sound/rawmidi.h>
|
||||
#include <sound/seq_kernel.h>
|
||||
#include <sound/info.h>
|
||||
|
||||
/* max. applications */
|
||||
#define SNDRV_SEQ_OSS_MAX_CLIENTS 16
|
||||
#define SNDRV_SEQ_OSS_MAX_SYNTH_DEVS 16
|
||||
#define SNDRV_SEQ_OSS_MAX_MIDI_DEVS 32
|
||||
|
||||
/* version */
|
||||
#define SNDRV_SEQ_OSS_MAJOR_VERSION 0
|
||||
#define SNDRV_SEQ_OSS_MINOR_VERSION 1
|
||||
#define SNDRV_SEQ_OSS_TINY_VERSION 8
|
||||
#define SNDRV_SEQ_OSS_VERSION_STR "0.1.8"
|
||||
|
||||
/* device and proc interface name */
|
||||
#define SNDRV_SEQ_OSS_PROCNAME "oss"
|
||||
|
||||
|
||||
/*
|
||||
* type definitions
|
||||
*/
|
||||
|
||||
typedef unsigned int reltime_t;
|
||||
typedef unsigned int abstime_t;
|
||||
|
||||
|
||||
/*
|
||||
* synthesizer channel information
|
||||
*/
|
||||
struct seq_oss_chinfo {
|
||||
int note, vel;
|
||||
};
|
||||
|
||||
/*
|
||||
* synthesizer information
|
||||
*/
|
||||
struct seq_oss_synthinfo {
|
||||
struct snd_seq_oss_arg arg;
|
||||
struct seq_oss_chinfo *ch;
|
||||
struct seq_oss_synth_sysex *sysex;
|
||||
int nr_voices;
|
||||
int opened;
|
||||
int is_midi;
|
||||
int midi_mapped;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* sequencer client information
|
||||
*/
|
||||
|
||||
struct seq_oss_devinfo {
|
||||
|
||||
int index; /* application index */
|
||||
int cseq; /* sequencer client number */
|
||||
int port; /* sequencer port number */
|
||||
int queue; /* sequencer queue number */
|
||||
|
||||
struct snd_seq_addr addr; /* address of this device */
|
||||
|
||||
int seq_mode; /* sequencer mode */
|
||||
int file_mode; /* file access */
|
||||
|
||||
/* midi device table */
|
||||
int max_mididev;
|
||||
|
||||
/* synth device table */
|
||||
int max_synthdev;
|
||||
struct seq_oss_synthinfo synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
|
||||
int synth_opened;
|
||||
|
||||
/* output queue */
|
||||
struct seq_oss_writeq *writeq;
|
||||
|
||||
/* midi input queue */
|
||||
struct seq_oss_readq *readq;
|
||||
|
||||
/* timer */
|
||||
struct seq_oss_timer *timer;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* function prototypes
|
||||
*/
|
||||
|
||||
/* create/delete OSS sequencer client */
|
||||
int snd_seq_oss_create_client(void);
|
||||
int snd_seq_oss_delete_client(void);
|
||||
|
||||
/* device file interface */
|
||||
int snd_seq_oss_open(struct file *file, int level);
|
||||
void snd_seq_oss_release(struct seq_oss_devinfo *dp);
|
||||
int snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long arg);
|
||||
int snd_seq_oss_read(struct seq_oss_devinfo *dev, char __user *buf, int count);
|
||||
int snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt);
|
||||
unsigned int snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait);
|
||||
|
||||
void snd_seq_oss_reset(struct seq_oss_devinfo *dp);
|
||||
void snd_seq_oss_drain_write(struct seq_oss_devinfo *dp);
|
||||
|
||||
/* */
|
||||
void snd_seq_oss_process_queue(struct seq_oss_devinfo *dp, abstime_t time);
|
||||
|
||||
|
||||
/* proc interface */
|
||||
void snd_seq_oss_system_info_read(struct snd_info_buffer *buf);
|
||||
void snd_seq_oss_midi_info_read(struct snd_info_buffer *buf);
|
||||
void snd_seq_oss_synth_info_read(struct snd_info_buffer *buf);
|
||||
void snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *buf);
|
||||
|
||||
/* file mode macros */
|
||||
#define is_read_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_READ)
|
||||
#define is_write_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_WRITE)
|
||||
#define is_nonblock_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_NONBLOCK)
|
||||
|
||||
/* dispatch event */
|
||||
static inline int
|
||||
snd_seq_oss_dispatch(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int atomic, int hop)
|
||||
{
|
||||
return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop);
|
||||
}
|
||||
|
||||
/* ioctl */
|
||||
static inline int
|
||||
snd_seq_oss_control(struct seq_oss_devinfo *dp, unsigned int type, void *arg)
|
||||
{
|
||||
return snd_seq_kernel_client_ctl(dp->cseq, type, arg);
|
||||
}
|
||||
|
||||
/* fill the addresses in header */
|
||||
static inline void
|
||||
snd_seq_oss_fill_addr(struct seq_oss_devinfo *dp, struct snd_seq_event *ev,
|
||||
int dest_client, int dest_port)
|
||||
{
|
||||
ev->queue = dp->queue;
|
||||
ev->source = dp->addr;
|
||||
ev->dest.client = dest_client;
|
||||
ev->dest.port = dest_port;
|
||||
}
|
||||
|
||||
|
||||
/* misc. functions for proc interface */
|
||||
char *enabled_str(int bool);
|
||||
|
||||
#endif /* __SEQ_OSS_DEVICE_H */
|
||||
457
sound/core/seq/oss/seq_oss_event.c
Normal file
457
sound/core/seq/oss/seq_oss_event.c
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
* OSS compatible sequencer driver
|
||||
*
|
||||
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "seq_oss_device.h"
|
||||
#include "seq_oss_synth.h"
|
||||
#include "seq_oss_midi.h"
|
||||
#include "seq_oss_event.h"
|
||||
#include "seq_oss_timer.h"
|
||||
#include <sound/seq_oss_legacy.h>
|
||||
#include "seq_oss_readq.h"
|
||||
#include "seq_oss_writeq.h"
|
||||
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static int extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
|
||||
static int chn_voice_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
||||
static int chn_common_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
||||
static int timing_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
||||
static int local_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
||||
static int old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
|
||||
static int note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
|
||||
static int note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
|
||||
static int set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev);
|
||||
static int set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev);
|
||||
static int set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev);
|
||||
|
||||
|
||||
/*
|
||||
* convert an OSS event to ALSA event
|
||||
* return 0 : enqueued
|
||||
* non-zero : invalid - ignored
|
||||
*/
|
||||
|
||||
int
|
||||
snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
||||
{
|
||||
switch (q->s.code) {
|
||||
case SEQ_EXTENDED:
|
||||
return extended_event(dp, q, ev);
|
||||
|
||||
case EV_CHN_VOICE:
|
||||
return chn_voice_event(dp, q, ev);
|
||||
|
||||
case EV_CHN_COMMON:
|
||||
return chn_common_event(dp, q, ev);
|
||||
|
||||
case EV_TIMING:
|
||||
return timing_event(dp, q, ev);
|
||||
|
||||
case EV_SEQ_LOCAL:
|
||||
return local_event(dp, q, ev);
|
||||
|
||||
case EV_SYSEX:
|
||||
return snd_seq_oss_synth_sysex(dp, q->x.dev, q->x.buf, ev);
|
||||
|
||||
case SEQ_MIDIPUTC:
|
||||
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
||||
return -EINVAL;
|
||||
/* put a midi byte */
|
||||
if (! is_write_mode(dp->file_mode))
|
||||
break;
|
||||
if (snd_seq_oss_midi_open(dp, q->s.dev, SNDRV_SEQ_OSS_FILE_WRITE))
|
||||
break;
|
||||
if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SNDRV_SEQ_OSS_FILE_WRITE)
|
||||
return snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, ev);
|
||||
break;
|
||||
|
||||
case SEQ_ECHO:
|
||||
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
||||
return -EINVAL;
|
||||
return set_echo_event(dp, q, ev);
|
||||
|
||||
case SEQ_PRIVATE:
|
||||
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
||||
return -EINVAL;
|
||||
return snd_seq_oss_synth_raw_event(dp, q->c[1], q->c, ev);
|
||||
|
||||
default:
|
||||
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
||||
return -EINVAL;
|
||||
return old_event(dp, q, ev);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* old type events: mode1 only */
|
||||
static int
|
||||
old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
||||
{
|
||||
switch (q->s.code) {
|
||||
case SEQ_NOTEOFF:
|
||||
return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
|
||||
|
||||
case SEQ_NOTEON:
|
||||
return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
|
||||
|
||||
case SEQ_WAIT:
|
||||
/* skip */
|
||||
break;
|
||||
|
||||
case SEQ_PGMCHANGE:
|
||||
return set_control_event(dp, 0, SNDRV_SEQ_EVENT_PGMCHANGE,
|
||||
q->n.chn, 0, q->n.note, ev);
|
||||
|
||||
case SEQ_SYNCTIMER:
|
||||
return snd_seq_oss_timer_reset(dp->timer);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* 8bytes extended event: mode1 only */
|
||||
static int
|
||||
extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
||||
{
|
||||
int val;
|
||||
|
||||
switch (q->e.cmd) {
|
||||
case SEQ_NOTEOFF:
|
||||
return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
|
||||
|
||||
case SEQ_NOTEON:
|
||||
return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
|
||||
|
||||
case SEQ_PGMCHANGE:
|
||||
return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
|
||||
q->e.chn, 0, q->e.p1, ev);
|
||||
|
||||
case SEQ_AFTERTOUCH:
|
||||
return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CHANPRESS,
|
||||
q->e.chn, 0, q->e.p1, ev);
|
||||
|
||||
case SEQ_BALANCE:
|
||||
/* convert -128:127 to 0:127 */
|
||||
val = (char)q->e.p1;
|
||||
val = (val + 128) / 2;
|
||||
return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CONTROLLER,
|
||||
q->e.chn, CTL_PAN, val, ev);
|
||||
|
||||
case SEQ_CONTROLLER:
|
||||
val = ((short)q->e.p3 << 8) | (short)q->e.p2;
|
||||
switch (q->e.p1) {
|
||||
case CTRL_PITCH_BENDER: /* SEQ1 V2 control */
|
||||
/* -0x2000:0x1fff */
|
||||
return set_control_event(dp, q->e.dev,
|
||||
SNDRV_SEQ_EVENT_PITCHBEND,
|
||||
q->e.chn, 0, val, ev);
|
||||
case CTRL_PITCH_BENDER_RANGE:
|
||||
/* conversion: 100/semitone -> 128/semitone */
|
||||
return set_control_event(dp, q->e.dev,
|
||||
SNDRV_SEQ_EVENT_REGPARAM,
|
||||
q->e.chn, 0, val*128/100, ev);
|
||||
default:
|
||||
return set_control_event(dp, q->e.dev,
|
||||
SNDRV_SEQ_EVENT_CONTROL14,
|
||||
q->e.chn, q->e.p1, val, ev);
|
||||
}
|
||||
|
||||
case SEQ_VOLMODE:
|
||||
return snd_seq_oss_synth_raw_event(dp, q->e.dev, q->c, ev);
|
||||
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* channel voice events: mode1 and 2 */
|
||||
static int
|
||||
chn_voice_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
||||
{
|
||||
if (q->v.chn >= 32)
|
||||
return -EINVAL;
|
||||
switch (q->v.cmd) {
|
||||
case MIDI_NOTEON:
|
||||
return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
|
||||
|
||||
case MIDI_NOTEOFF:
|
||||
return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
|
||||
|
||||
case MIDI_KEY_PRESSURE:
|
||||
return set_note_event(dp, q->v.dev, SNDRV_SEQ_EVENT_KEYPRESS,
|
||||
q->v.chn, q->v.note, q->v.parm, ev);
|
||||
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* channel common events: mode1 and 2 */
|
||||
static int
|
||||
chn_common_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
||||
{
|
||||
if (q->l.chn >= 32)
|
||||
return -EINVAL;
|
||||
switch (q->l.cmd) {
|
||||
case MIDI_PGM_CHANGE:
|
||||
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
|
||||
q->l.chn, 0, q->l.p1, ev);
|
||||
|
||||
case MIDI_CTL_CHANGE:
|
||||
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CONTROLLER,
|
||||
q->l.chn, q->l.p1, q->l.val, ev);
|
||||
|
||||
case MIDI_PITCH_BEND:
|
||||
/* conversion: 0:0x3fff -> -0x2000:0x1fff */
|
||||
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PITCHBEND,
|
||||
q->l.chn, 0, q->l.val - 8192, ev);
|
||||
|
||||
case MIDI_CHN_PRESSURE:
|
||||
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CHANPRESS,
|
||||
q->l.chn, 0, q->l.val, ev);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* timer events: mode1 and mode2 */
|
||||
static int
|
||||
timing_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
||||
{
|
||||
switch (q->t.cmd) {
|
||||
case TMR_ECHO:
|
||||
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
||||
return set_echo_event(dp, q, ev);
|
||||
else {
|
||||
union evrec tmp;
|
||||
memset(&tmp, 0, sizeof(tmp));
|
||||
/* XXX: only for little-endian! */
|
||||
tmp.echo = (q->t.time << 8) | SEQ_ECHO;
|
||||
return set_echo_event(dp, &tmp, ev);
|
||||
}
|
||||
|
||||
case TMR_STOP:
|
||||
if (dp->seq_mode)
|
||||
return snd_seq_oss_timer_stop(dp->timer);
|
||||
return 0;
|
||||
|
||||
case TMR_CONTINUE:
|
||||
if (dp->seq_mode)
|
||||
return snd_seq_oss_timer_continue(dp->timer);
|
||||
return 0;
|
||||
|
||||
case TMR_TEMPO:
|
||||
if (dp->seq_mode)
|
||||
return snd_seq_oss_timer_tempo(dp->timer, q->t.time);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* local events: mode1 and 2 */
|
||||
static int
|
||||
local_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* process note-on event for OSS synth
|
||||
* three different modes are available:
|
||||
* - SNDRV_SEQ_OSS_PROCESS_EVENTS (for one-voice per channel mode)
|
||||
* Accept note 255 as volume change.
|
||||
* - SNDRV_SEQ_OSS_PASS_EVENTS
|
||||
* Pass all events to lowlevel driver anyway
|
||||
* - SNDRV_SEQ_OSS_PROCESS_KEYPRESS (mostly for Emu8000)
|
||||
* Use key-pressure if note >= 128
|
||||
*/
|
||||
static int
|
||||
note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
|
||||
{
|
||||
struct seq_oss_synthinfo *info;
|
||||
|
||||
if (!snd_seq_oss_synth_is_valid(dp, dev))
|
||||
return -ENXIO;
|
||||
|
||||
info = &dp->synths[dev];
|
||||
switch (info->arg.event_passing) {
|
||||
case SNDRV_SEQ_OSS_PROCESS_EVENTS:
|
||||
if (! info->ch || ch < 0 || ch >= info->nr_voices) {
|
||||
/* pass directly */
|
||||
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
||||
}
|
||||
|
||||
if (note == 255 && info->ch[ch].note >= 0) {
|
||||
/* volume control */
|
||||
int type;
|
||||
//if (! vel)
|
||||
/* set volume to zero -- note off */
|
||||
// type = SNDRV_SEQ_EVENT_NOTEOFF;
|
||||
//else
|
||||
if (info->ch[ch].vel)
|
||||
/* sample already started -- volume change */
|
||||
type = SNDRV_SEQ_EVENT_KEYPRESS;
|
||||
else
|
||||
/* sample not started -- start now */
|
||||
type = SNDRV_SEQ_EVENT_NOTEON;
|
||||
info->ch[ch].vel = vel;
|
||||
return set_note_event(dp, dev, type, ch, info->ch[ch].note, vel, ev);
|
||||
} else if (note >= 128)
|
||||
return -EINVAL; /* invalid */
|
||||
|
||||
if (note != info->ch[ch].note && info->ch[ch].note >= 0)
|
||||
/* note changed - note off at beginning */
|
||||
set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, ev);
|
||||
/* set current status */
|
||||
info->ch[ch].note = note;
|
||||
info->ch[ch].vel = vel;
|
||||
if (vel) /* non-zero velocity - start the note now */
|
||||
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
||||
return -EINVAL;
|
||||
|
||||
case SNDRV_SEQ_OSS_PASS_EVENTS:
|
||||
/* pass the event anyway */
|
||||
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
||||
|
||||
case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
|
||||
if (note >= 128) /* key pressure: shifted by 128 */
|
||||
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, ev);
|
||||
else /* normal note-on event */
|
||||
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* process note-off event for OSS synth
|
||||
*/
|
||||
static int
|
||||
note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
|
||||
{
|
||||
struct seq_oss_synthinfo *info;
|
||||
|
||||
if (!snd_seq_oss_synth_is_valid(dp, dev))
|
||||
return -ENXIO;
|
||||
|
||||
info = &dp->synths[dev];
|
||||
switch (info->arg.event_passing) {
|
||||
case SNDRV_SEQ_OSS_PROCESS_EVENTS:
|
||||
if (! info->ch || ch < 0 || ch >= info->nr_voices) {
|
||||
/* pass directly */
|
||||
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
||||
}
|
||||
|
||||
if (info->ch[ch].note >= 0) {
|
||||
note = info->ch[ch].note;
|
||||
info->ch[ch].vel = 0;
|
||||
info->ch[ch].note = -1;
|
||||
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
|
||||
}
|
||||
return -EINVAL; /* invalid */
|
||||
|
||||
case SNDRV_SEQ_OSS_PASS_EVENTS:
|
||||
case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
|
||||
/* pass the event anyway */
|
||||
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
|
||||
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* create a note event
|
||||
*/
|
||||
static int
|
||||
set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev)
|
||||
{
|
||||
if (! snd_seq_oss_synth_is_valid(dp, dev))
|
||||
return -ENXIO;
|
||||
|
||||
ev->type = type;
|
||||
snd_seq_oss_synth_addr(dp, dev, ev);
|
||||
ev->data.note.channel = ch;
|
||||
ev->data.note.note = note;
|
||||
ev->data.note.velocity = vel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* create a control event
|
||||
*/
|
||||
static int
|
||||
set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev)
|
||||
{
|
||||
if (! snd_seq_oss_synth_is_valid(dp, dev))
|
||||
return -ENXIO;
|
||||
|
||||
ev->type = type;
|
||||
snd_seq_oss_synth_addr(dp, dev, ev);
|
||||
ev->data.control.channel = ch;
|
||||
ev->data.control.param = param;
|
||||
ev->data.control.value = val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* create an echo event
|
||||
*/
|
||||
static int
|
||||
set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev)
|
||||
{
|
||||
ev->type = SNDRV_SEQ_EVENT_ECHO;
|
||||
/* echo back to itself */
|
||||
snd_seq_oss_fill_addr(dp, ev, dp->addr.client, dp->addr.port);
|
||||
memcpy(&ev->data, rec, LONG_EVENT_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* event input callback from ALSA sequencer:
|
||||
* the echo event is processed here.
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data,
|
||||
int atomic, int hop)
|
||||
{
|
||||
struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
|
||||
union evrec *rec;
|
||||
|
||||
if (ev->type != SNDRV_SEQ_EVENT_ECHO)
|
||||
return snd_seq_oss_midi_input(ev, direct, private_data);
|
||||
|
||||
if (ev->source.client != dp->cseq)
|
||||
return 0; /* ignored */
|
||||
|
||||
rec = (union evrec*)&ev->data;
|
||||
if (rec->s.code == SEQ_SYNCTIMER) {
|
||||
/* sync echo back */
|
||||
snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time);
|
||||
|
||||
} else {
|
||||
/* echo back event */
|
||||
if (dp->readq == NULL)
|
||||
return 0;
|
||||
snd_seq_oss_readq_put_event(dp->readq, rec);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
112
sound/core/seq/oss/seq_oss_event.h
Normal file
112
sound/core/seq/oss/seq_oss_event.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* OSS compatible sequencer driver
|
||||
*
|
||||
* seq_oss_event.h - OSS event queue record
|
||||
*
|
||||
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef __SEQ_OSS_EVENT_H
|
||||
#define __SEQ_OSS_EVENT_H
|
||||
|
||||
#include "seq_oss_device.h"
|
||||
|
||||
#define SHORT_EVENT_SIZE 4
|
||||
#define LONG_EVENT_SIZE 8
|
||||
|
||||
/* short event (4bytes) */
|
||||
struct evrec_short {
|
||||
unsigned char code;
|
||||
unsigned char parm1;
|
||||
unsigned char dev;
|
||||
unsigned char parm2;
|
||||
};
|
||||
|
||||
/* short note events (4bytes) */
|
||||
struct evrec_note {
|
||||
unsigned char code;
|
||||
unsigned char chn;
|
||||
unsigned char note;
|
||||
unsigned char vel;
|
||||
};
|
||||
|
||||
/* long timer events (8bytes) */
|
||||
struct evrec_timer {
|
||||
unsigned char code;
|
||||
unsigned char cmd;
|
||||
unsigned char dummy1, dummy2;
|
||||
unsigned int time;
|
||||
};
|
||||
|
||||
/* long extended events (8bytes) */
|
||||
struct evrec_extended {
|
||||
unsigned char code;
|
||||
unsigned char cmd;
|
||||
unsigned char dev;
|
||||
unsigned char chn;
|
||||
unsigned char p1, p2, p3, p4;
|
||||
};
|
||||
|
||||
/* long channel events (8bytes) */
|
||||
struct evrec_long {
|
||||
unsigned char code;
|
||||
unsigned char dev;
|
||||
unsigned char cmd;
|
||||
unsigned char chn;
|
||||
unsigned char p1, p2;
|
||||
unsigned short val;
|
||||
};
|
||||
|
||||
/* channel voice events (8bytes) */
|
||||
struct evrec_voice {
|
||||
unsigned char code;
|
||||
unsigned char dev;
|
||||
unsigned char cmd;
|
||||
unsigned char chn;
|
||||
unsigned char note, parm;
|
||||
unsigned short dummy;
|
||||
};
|
||||
|
||||
/* sysex events (8bytes) */
|
||||
struct evrec_sysex {
|
||||
unsigned char code;
|
||||
unsigned char dev;
|
||||
unsigned char buf[6];
|
||||
};
|
||||
|
||||
/* event record */
|
||||
union evrec {
|
||||
struct evrec_short s;
|
||||
struct evrec_note n;
|
||||
struct evrec_long l;
|
||||
struct evrec_voice v;
|
||||
struct evrec_timer t;
|
||||
struct evrec_extended e;
|
||||
struct evrec_sysex x;
|
||||
unsigned int echo;
|
||||
unsigned char c[LONG_EVENT_SIZE];
|
||||
};
|
||||
|
||||
#define ev_is_long(ev) ((ev)->s.code >= 128)
|
||||
#define ev_length(ev) ((ev)->s.code >= 128 ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE)
|
||||
|
||||
int snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
|
||||
int snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *q);
|
||||
int snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data, int atomic, int hop);
|
||||
|
||||
|
||||
#endif /* __SEQ_OSS_EVENT_H */
|
||||
539
sound/core/seq/oss/seq_oss_init.c
Normal file
539
sound/core/seq/oss/seq_oss_init.c
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
/*
|
||||
* OSS compatible sequencer driver
|
||||
*
|
||||
* open/close and reset interface
|
||||
*
|
||||
* Copyright (C) 1998-1999 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "seq_oss_device.h"
|
||||
#include "seq_oss_synth.h"
|
||||
#include "seq_oss_midi.h"
|
||||
#include "seq_oss_writeq.h"
|
||||
#include "seq_oss_readq.h"
|
||||
#include "seq_oss_timer.h"
|
||||
#include "seq_oss_event.h"
|
||||
#include <linux/init.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
/*
|
||||
* common variables
|
||||
*/
|
||||
static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN;
|
||||
module_param(maxqlen, int, 0444);
|
||||
MODULE_PARM_DESC(maxqlen, "maximum queue length");
|
||||
|
||||
static int system_client = -1; /* ALSA sequencer client number */
|
||||
static int system_port = -1;
|
||||
|
||||
static int num_clients;
|
||||
static struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS];
|
||||
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop);
|
||||
static int translate_mode(struct file *file);
|
||||
static int create_port(struct seq_oss_devinfo *dp);
|
||||
static int delete_port(struct seq_oss_devinfo *dp);
|
||||
static int alloc_seq_queue(struct seq_oss_devinfo *dp);
|
||||
static int delete_seq_queue(int queue);
|
||||
static void free_devinfo(void *private);
|
||||
|
||||
#define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec)
|
||||
|
||||
|
||||
/* call snd_seq_oss_midi_lookup_ports() asynchronously */
|
||||
static void async_call_lookup_ports(struct work_struct *work)
|
||||
{
|
||||
snd_seq_oss_midi_lookup_ports(system_client);
|
||||
}
|
||||
|
||||
static DECLARE_WORK(async_lookup_work, async_call_lookup_ports);
|
||||
|
||||
/*
|
||||
* create sequencer client for OSS sequencer
|
||||
*/
|
||||
int __init
|
||||
snd_seq_oss_create_client(void)
|
||||
{
|
||||
int rc;
|
||||
struct snd_seq_port_info *port;
|
||||
struct snd_seq_port_callback port_callback;
|
||||
|
||||
port = kmalloc(sizeof(*port), GFP_KERNEL);
|
||||
if (!port) {
|
||||
rc = -ENOMEM;
|
||||
goto __error;
|
||||
}
|
||||
|
||||
/* create ALSA client */
|
||||
rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS,
|
||||
"OSS sequencer");
|
||||
if (rc < 0)
|
||||
goto __error;
|
||||
|
||||
system_client = rc;
|
||||
|
||||
/* create annoucement receiver port */
|
||||
memset(port, 0, sizeof(*port));
|
||||
strcpy(port->name, "Receiver");
|
||||
port->addr.client = system_client;
|
||||
port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */
|
||||
port->type = 0;
|
||||
|
||||
memset(&port_callback, 0, sizeof(port_callback));
|
||||
/* don't set port_callback.owner here. otherwise the module counter
|
||||
* is incremented and we can no longer release the module..
|
||||
*/
|
||||
port_callback.event_input = receive_announce;
|
||||
port->kernel = &port_callback;
|
||||
|
||||
call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port);
|
||||
if ((system_port = port->addr.port) >= 0) {
|
||||
struct snd_seq_port_subscribe subs;
|
||||
|
||||
memset(&subs, 0, sizeof(subs));
|
||||
subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM;
|
||||
subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
|
||||
subs.dest.client = system_client;
|
||||
subs.dest.port = system_port;
|
||||
call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs);
|
||||
}
|
||||
rc = 0;
|
||||
|
||||
/* look up midi devices */
|
||||
schedule_work(&async_lookup_work);
|
||||
|
||||
__error:
|
||||
kfree(port);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* receive annoucement from system port, and check the midi device
|
||||
*/
|
||||
static int
|
||||
receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop)
|
||||
{
|
||||
struct snd_seq_port_info pinfo;
|
||||
|
||||
if (atomic)
|
||||
return 0; /* it must not happen */
|
||||
|
||||
switch (ev->type) {
|
||||
case SNDRV_SEQ_EVENT_PORT_START:
|
||||
case SNDRV_SEQ_EVENT_PORT_CHANGE:
|
||||
if (ev->data.addr.client == system_client)
|
||||
break; /* ignore myself */
|
||||
memset(&pinfo, 0, sizeof(pinfo));
|
||||
pinfo.addr = ev->data.addr;
|
||||
if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0)
|
||||
snd_seq_oss_midi_check_new_port(&pinfo);
|
||||
break;
|
||||
|
||||
case SNDRV_SEQ_EVENT_PORT_EXIT:
|
||||
if (ev->data.addr.client == system_client)
|
||||
break; /* ignore myself */
|
||||
snd_seq_oss_midi_check_exit_port(ev->data.addr.client,
|
||||
ev->data.addr.port);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* delete OSS sequencer client
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_delete_client(void)
|
||||
{
|
||||
cancel_work_sync(&async_lookup_work);
|
||||
if (system_client >= 0)
|
||||
snd_seq_delete_kernel_client(system_client);
|
||||
|
||||
snd_seq_oss_midi_clear_all();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* open sequencer device
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_open(struct file *file, int level)
|
||||
{
|
||||
int i, rc;
|
||||
struct seq_oss_devinfo *dp;
|
||||
|
||||
dp = kzalloc(sizeof(*dp), GFP_KERNEL);
|
||||
if (!dp) {
|
||||
pr_err("ALSA: seq_oss: can't malloc device info\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dp->cseq = system_client;
|
||||
dp->port = -1;
|
||||
dp->queue = -1;
|
||||
|
||||
for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) {
|
||||
if (client_table[i] == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
dp->index = i;
|
||||
if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) {
|
||||
pr_err("ALSA: seq_oss: too many applications\n");
|
||||
rc = -ENOMEM;
|
||||
goto _error;
|
||||
}
|
||||
|
||||
/* look up synth and midi devices */
|
||||
snd_seq_oss_synth_setup(dp);
|
||||
snd_seq_oss_midi_setup(dp);
|
||||
|
||||
if (dp->synth_opened == 0 && dp->max_mididev == 0) {
|
||||
/* pr_err("ALSA: seq_oss: no device found\n"); */
|
||||
rc = -ENODEV;
|
||||
goto _error;
|
||||
}
|
||||
|
||||
/* create port */
|
||||
rc = create_port(dp);
|
||||
if (rc < 0) {
|
||||
pr_err("ALSA: seq_oss: can't create port\n");
|
||||
goto _error;
|
||||
}
|
||||
|
||||
/* allocate queue */
|
||||
rc = alloc_seq_queue(dp);
|
||||
if (rc < 0)
|
||||
goto _error;
|
||||
|
||||
/* set address */
|
||||
dp->addr.client = dp->cseq;
|
||||
dp->addr.port = dp->port;
|
||||
/*dp->addr.queue = dp->queue;*/
|
||||
/*dp->addr.channel = 0;*/
|
||||
|
||||
dp->seq_mode = level;
|
||||
|
||||
/* set up file mode */
|
||||
dp->file_mode = translate_mode(file);
|
||||
|
||||
/* initialize read queue */
|
||||
if (is_read_mode(dp->file_mode)) {
|
||||
dp->readq = snd_seq_oss_readq_new(dp, maxqlen);
|
||||
if (!dp->readq) {
|
||||
rc = -ENOMEM;
|
||||
goto _error;
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize write queue */
|
||||
if (is_write_mode(dp->file_mode)) {
|
||||
dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen);
|
||||
if (!dp->writeq) {
|
||||
rc = -ENOMEM;
|
||||
goto _error;
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize timer */
|
||||
dp->timer = snd_seq_oss_timer_new(dp);
|
||||
if (!dp->timer) {
|
||||
pr_err("ALSA: seq_oss: can't alloc timer\n");
|
||||
rc = -ENOMEM;
|
||||
goto _error;
|
||||
}
|
||||
|
||||
/* set private data pointer */
|
||||
file->private_data = dp;
|
||||
|
||||
/* set up for mode2 */
|
||||
if (level == SNDRV_SEQ_OSS_MODE_MUSIC)
|
||||
snd_seq_oss_synth_setup_midi(dp);
|
||||
else if (is_read_mode(dp->file_mode))
|
||||
snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ);
|
||||
|
||||
client_table[dp->index] = dp;
|
||||
num_clients++;
|
||||
|
||||
return 0;
|
||||
|
||||
_error:
|
||||
snd_seq_oss_synth_cleanup(dp);
|
||||
snd_seq_oss_midi_cleanup(dp);
|
||||
delete_seq_queue(dp->queue);
|
||||
delete_port(dp);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* translate file flags to private mode
|
||||
*/
|
||||
static int
|
||||
translate_mode(struct file *file)
|
||||
{
|
||||
int file_mode = 0;
|
||||
if ((file->f_flags & O_ACCMODE) != O_RDONLY)
|
||||
file_mode |= SNDRV_SEQ_OSS_FILE_WRITE;
|
||||
if ((file->f_flags & O_ACCMODE) != O_WRONLY)
|
||||
file_mode |= SNDRV_SEQ_OSS_FILE_READ;
|
||||
if (file->f_flags & O_NONBLOCK)
|
||||
file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK;
|
||||
return file_mode;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* create sequencer port
|
||||
*/
|
||||
static int
|
||||
create_port(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
int rc;
|
||||
struct snd_seq_port_info port;
|
||||
struct snd_seq_port_callback callback;
|
||||
|
||||
memset(&port, 0, sizeof(port));
|
||||
port.addr.client = dp->cseq;
|
||||
sprintf(port.name, "Sequencer-%d", dp->index);
|
||||
port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */
|
||||
port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC;
|
||||
port.midi_channels = 128;
|
||||
port.synth_voices = 128;
|
||||
|
||||
memset(&callback, 0, sizeof(callback));
|
||||
callback.owner = THIS_MODULE;
|
||||
callback.private_data = dp;
|
||||
callback.event_input = snd_seq_oss_event_input;
|
||||
callback.private_free = free_devinfo;
|
||||
port.kernel = &callback;
|
||||
|
||||
rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
dp->port = port.addr.port;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* delete ALSA port
|
||||
*/
|
||||
static int
|
||||
delete_port(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
if (dp->port < 0) {
|
||||
kfree(dp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return snd_seq_event_port_detach(dp->cseq, dp->port);
|
||||
}
|
||||
|
||||
/*
|
||||
* allocate a queue
|
||||
*/
|
||||
static int
|
||||
alloc_seq_queue(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
struct snd_seq_queue_info qinfo;
|
||||
int rc;
|
||||
|
||||
memset(&qinfo, 0, sizeof(qinfo));
|
||||
qinfo.owner = system_client;
|
||||
qinfo.locked = 1;
|
||||
strcpy(qinfo.name, "OSS Sequencer Emulation");
|
||||
if ((rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo)) < 0)
|
||||
return rc;
|
||||
dp->queue = qinfo.queue;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* release queue
|
||||
*/
|
||||
static int
|
||||
delete_seq_queue(int queue)
|
||||
{
|
||||
struct snd_seq_queue_info qinfo;
|
||||
int rc;
|
||||
|
||||
if (queue < 0)
|
||||
return 0;
|
||||
memset(&qinfo, 0, sizeof(qinfo));
|
||||
qinfo.queue = queue;
|
||||
rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo);
|
||||
if (rc < 0)
|
||||
pr_err("ALSA: seq_oss: unable to delete queue %d (%d)\n", queue, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* free device informations - private_free callback of port
|
||||
*/
|
||||
static void
|
||||
free_devinfo(void *private)
|
||||
{
|
||||
struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private;
|
||||
|
||||
if (dp->timer)
|
||||
snd_seq_oss_timer_delete(dp->timer);
|
||||
|
||||
if (dp->writeq)
|
||||
snd_seq_oss_writeq_delete(dp->writeq);
|
||||
|
||||
if (dp->readq)
|
||||
snd_seq_oss_readq_delete(dp->readq);
|
||||
|
||||
kfree(dp);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* close sequencer device
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_release(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
int queue;
|
||||
|
||||
client_table[dp->index] = NULL;
|
||||
num_clients--;
|
||||
|
||||
snd_seq_oss_reset(dp);
|
||||
|
||||
snd_seq_oss_synth_cleanup(dp);
|
||||
snd_seq_oss_midi_cleanup(dp);
|
||||
|
||||
/* clear slot */
|
||||
queue = dp->queue;
|
||||
if (dp->port >= 0)
|
||||
delete_port(dp);
|
||||
delete_seq_queue(queue);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Wait until the queue is empty (if we don't have nonblock)
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_drain_write(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
if (! dp->timer->running)
|
||||
return;
|
||||
if (is_write_mode(dp->file_mode) && !is_nonblock_mode(dp->file_mode) &&
|
||||
dp->writeq) {
|
||||
while (snd_seq_oss_writeq_sync(dp->writeq))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* reset sequencer devices
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_reset(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* reset all synth devices */
|
||||
for (i = 0; i < dp->max_synthdev; i++)
|
||||
snd_seq_oss_synth_reset(dp, i);
|
||||
|
||||
/* reset all midi devices */
|
||||
if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) {
|
||||
for (i = 0; i < dp->max_mididev; i++)
|
||||
snd_seq_oss_midi_reset(dp, i);
|
||||
}
|
||||
|
||||
/* remove queues */
|
||||
if (dp->readq)
|
||||
snd_seq_oss_readq_clear(dp->readq);
|
||||
if (dp->writeq)
|
||||
snd_seq_oss_writeq_clear(dp->writeq);
|
||||
|
||||
/* reset timer */
|
||||
snd_seq_oss_timer_stop(dp->timer);
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/*
|
||||
* misc. functions for proc interface
|
||||
*/
|
||||
char *
|
||||
enabled_str(int bool)
|
||||
{
|
||||
return bool ? "enabled" : "disabled";
|
||||
}
|
||||
|
||||
static char *
|
||||
filemode_str(int val)
|
||||
{
|
||||
static char *str[] = {
|
||||
"none", "read", "write", "read/write",
|
||||
};
|
||||
return str[val & SNDRV_SEQ_OSS_FILE_ACMODE];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* proc interface
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_system_info_read(struct snd_info_buffer *buf)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_devinfo *dp;
|
||||
|
||||
snd_iprintf(buf, "ALSA client number %d\n", system_client);
|
||||
snd_iprintf(buf, "ALSA receiver port %d\n", system_port);
|
||||
|
||||
snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients);
|
||||
for (i = 0; i < num_clients; i++) {
|
||||
snd_iprintf(buf, "\nApplication %d: ", i);
|
||||
if ((dp = client_table[i]) == NULL) {
|
||||
snd_iprintf(buf, "*empty*\n");
|
||||
continue;
|
||||
}
|
||||
snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue);
|
||||
snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n",
|
||||
(dp->seq_mode ? "music" : "synth"),
|
||||
filemode_str(dp->file_mode));
|
||||
if (dp->seq_mode)
|
||||
snd_iprintf(buf, " timer tempo = %d, timebase = %d\n",
|
||||
dp->timer->oss_tempo, dp->timer->oss_timebase);
|
||||
snd_iprintf(buf, " max queue length %d\n", maxqlen);
|
||||
if (is_read_mode(dp->file_mode) && dp->readq)
|
||||
snd_seq_oss_readq_info_read(dp->readq, buf);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
191
sound/core/seq/oss/seq_oss_ioctl.c
Normal file
191
sound/core/seq/oss/seq_oss_ioctl.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* OSS compatible sequencer driver
|
||||
*
|
||||
* OSS compatible i/o control
|
||||
*
|
||||
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "seq_oss_device.h"
|
||||
#include "seq_oss_readq.h"
|
||||
#include "seq_oss_writeq.h"
|
||||
#include "seq_oss_timer.h"
|
||||
#include "seq_oss_synth.h"
|
||||
#include "seq_oss_midi.h"
|
||||
#include "seq_oss_event.h"
|
||||
|
||||
static int snd_seq_oss_synth_info_user(struct seq_oss_devinfo *dp, void __user *arg)
|
||||
{
|
||||
struct synth_info info;
|
||||
|
||||
if (copy_from_user(&info, arg, sizeof(info)))
|
||||
return -EFAULT;
|
||||
if (snd_seq_oss_synth_make_info(dp, info.device, &info) < 0)
|
||||
return -EINVAL;
|
||||
if (copy_to_user(arg, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_seq_oss_midi_info_user(struct seq_oss_devinfo *dp, void __user *arg)
|
||||
{
|
||||
struct midi_info info;
|
||||
|
||||
if (copy_from_user(&info, arg, sizeof(info)))
|
||||
return -EFAULT;
|
||||
if (snd_seq_oss_midi_make_info(dp, info.device, &info) < 0)
|
||||
return -EINVAL;
|
||||
if (copy_to_user(arg, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_seq_oss_oob_user(struct seq_oss_devinfo *dp, void __user *arg)
|
||||
{
|
||||
unsigned char ev[8];
|
||||
struct snd_seq_event tmpev;
|
||||
|
||||
if (copy_from_user(ev, arg, 8))
|
||||
return -EFAULT;
|
||||
memset(&tmpev, 0, sizeof(tmpev));
|
||||
snd_seq_oss_fill_addr(dp, &tmpev, dp->addr.port, dp->addr.client);
|
||||
tmpev.time.tick = 0;
|
||||
if (! snd_seq_oss_process_event(dp, (union evrec *)ev, &tmpev)) {
|
||||
snd_seq_oss_dispatch(dp, &tmpev, 0, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long carg)
|
||||
{
|
||||
int dev, val;
|
||||
void __user *arg = (void __user *)carg;
|
||||
int __user *p = arg;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDCTL_TMR_TIMEBASE:
|
||||
case SNDCTL_TMR_TEMPO:
|
||||
case SNDCTL_TMR_START:
|
||||
case SNDCTL_TMR_STOP:
|
||||
case SNDCTL_TMR_CONTINUE:
|
||||
case SNDCTL_TMR_METRONOME:
|
||||
case SNDCTL_TMR_SOURCE:
|
||||
case SNDCTL_TMR_SELECT:
|
||||
case SNDCTL_SEQ_CTRLRATE:
|
||||
return snd_seq_oss_timer_ioctl(dp->timer, cmd, arg);
|
||||
|
||||
case SNDCTL_SEQ_PANIC:
|
||||
snd_seq_oss_reset(dp);
|
||||
return -EINVAL;
|
||||
|
||||
case SNDCTL_SEQ_SYNC:
|
||||
if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
|
||||
return 0;
|
||||
while (snd_seq_oss_writeq_sync(dp->writeq))
|
||||
;
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
return 0;
|
||||
|
||||
case SNDCTL_SEQ_RESET:
|
||||
snd_seq_oss_reset(dp);
|
||||
return 0;
|
||||
|
||||
case SNDCTL_SEQ_TESTMIDI:
|
||||
if (get_user(dev, p))
|
||||
return -EFAULT;
|
||||
return snd_seq_oss_midi_open(dp, dev, dp->file_mode);
|
||||
|
||||
case SNDCTL_SEQ_GETINCOUNT:
|
||||
if (dp->readq == NULL || ! is_read_mode(dp->file_mode))
|
||||
return 0;
|
||||
return put_user(dp->readq->qlen, p) ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_SEQ_GETOUTCOUNT:
|
||||
if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
|
||||
return 0;
|
||||
return put_user(snd_seq_oss_writeq_get_free_size(dp->writeq), p) ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_SEQ_GETTIME:
|
||||
return put_user(snd_seq_oss_timer_cur_tick(dp->timer), p) ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_SEQ_RESETSAMPLES:
|
||||
if (get_user(dev, p))
|
||||
return -EFAULT;
|
||||
return snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
|
||||
|
||||
case SNDCTL_SEQ_NRSYNTHS:
|
||||
return put_user(dp->max_synthdev, p) ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_SEQ_NRMIDIS:
|
||||
return put_user(dp->max_mididev, p) ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_SYNTH_MEMAVL:
|
||||
if (get_user(dev, p))
|
||||
return -EFAULT;
|
||||
val = snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
|
||||
return put_user(val, p) ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_FM_4OP_ENABLE:
|
||||
if (get_user(dev, p))
|
||||
return -EFAULT;
|
||||
snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
|
||||
return 0;
|
||||
|
||||
case SNDCTL_SYNTH_INFO:
|
||||
case SNDCTL_SYNTH_ID:
|
||||
return snd_seq_oss_synth_info_user(dp, arg);
|
||||
|
||||
case SNDCTL_SEQ_OUTOFBAND:
|
||||
return snd_seq_oss_oob_user(dp, arg);
|
||||
|
||||
case SNDCTL_MIDI_INFO:
|
||||
return snd_seq_oss_midi_info_user(dp, arg);
|
||||
|
||||
case SNDCTL_SEQ_THRESHOLD:
|
||||
if (! is_write_mode(dp->file_mode))
|
||||
return 0;
|
||||
if (get_user(val, p))
|
||||
return -EFAULT;
|
||||
if (val < 1)
|
||||
val = 1;
|
||||
if (val >= dp->writeq->maxlen)
|
||||
val = dp->writeq->maxlen - 1;
|
||||
snd_seq_oss_writeq_set_output(dp->writeq, val);
|
||||
return 0;
|
||||
|
||||
case SNDCTL_MIDI_PRETIME:
|
||||
if (dp->readq == NULL || !is_read_mode(dp->file_mode))
|
||||
return 0;
|
||||
if (get_user(val, p))
|
||||
return -EFAULT;
|
||||
if (val <= 0)
|
||||
val = -1;
|
||||
else
|
||||
val = (HZ * val) / 10;
|
||||
dp->readq->pre_event_timeout = val;
|
||||
return put_user(val, p) ? -EFAULT : 0;
|
||||
|
||||
default:
|
||||
if (! is_write_mode(dp->file_mode))
|
||||
return -EIO;
|
||||
return snd_seq_oss_synth_ioctl(dp, 0, cmd, carg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
711
sound/core/seq/oss/seq_oss_midi.c
Normal file
711
sound/core/seq/oss/seq_oss_midi.c
Normal file
|
|
@ -0,0 +1,711 @@
|
|||
/*
|
||||
* OSS compatible sequencer driver
|
||||
*
|
||||
* MIDI device handlers
|
||||
*
|
||||
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <sound/asoundef.h>
|
||||
#include "seq_oss_midi.h"
|
||||
#include "seq_oss_readq.h"
|
||||
#include "seq_oss_timer.h"
|
||||
#include "seq_oss_event.h"
|
||||
#include <sound/seq_midi_event.h>
|
||||
#include "../seq_lock.h"
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
|
||||
/*
|
||||
* constants
|
||||
*/
|
||||
#define SNDRV_SEQ_OSS_MAX_MIDI_NAME 30
|
||||
|
||||
/*
|
||||
* definition of midi device record
|
||||
*/
|
||||
struct seq_oss_midi {
|
||||
int seq_device; /* device number */
|
||||
int client; /* sequencer client number */
|
||||
int port; /* sequencer port number */
|
||||
unsigned int flags; /* port capability */
|
||||
int opened; /* flag for opening */
|
||||
unsigned char name[SNDRV_SEQ_OSS_MAX_MIDI_NAME];
|
||||
struct snd_midi_event *coder; /* MIDI event coder */
|
||||
struct seq_oss_devinfo *devinfo; /* assigned OSSseq device */
|
||||
snd_use_lock_t use_lock;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* midi device table
|
||||
*/
|
||||
static int max_midi_devs;
|
||||
static struct seq_oss_midi *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS];
|
||||
|
||||
static DEFINE_SPINLOCK(register_lock);
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static struct seq_oss_midi *get_mdev(int dev);
|
||||
static struct seq_oss_midi *get_mididev(struct seq_oss_devinfo *dp, int dev);
|
||||
static int send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev);
|
||||
static int send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev);
|
||||
|
||||
/*
|
||||
* look up the existing ports
|
||||
* this looks a very exhausting job.
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_lookup_ports(int client)
|
||||
{
|
||||
struct snd_seq_client_info *clinfo;
|
||||
struct snd_seq_port_info *pinfo;
|
||||
|
||||
clinfo = kzalloc(sizeof(*clinfo), GFP_KERNEL);
|
||||
pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL);
|
||||
if (! clinfo || ! pinfo) {
|
||||
kfree(clinfo);
|
||||
kfree(pinfo);
|
||||
return -ENOMEM;
|
||||
}
|
||||
clinfo->client = -1;
|
||||
while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, clinfo) == 0) {
|
||||
if (clinfo->client == client)
|
||||
continue; /* ignore myself */
|
||||
pinfo->addr.client = clinfo->client;
|
||||
pinfo->addr.port = -1;
|
||||
while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, pinfo) == 0)
|
||||
snd_seq_oss_midi_check_new_port(pinfo);
|
||||
}
|
||||
kfree(clinfo);
|
||||
kfree(pinfo);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*/
|
||||
static struct seq_oss_midi *
|
||||
get_mdev(int dev)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
mdev = midi_devs[dev];
|
||||
if (mdev)
|
||||
snd_use_lock_use(&mdev->use_lock);
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
return mdev;
|
||||
}
|
||||
|
||||
/*
|
||||
* look for the identical slot
|
||||
*/
|
||||
static struct seq_oss_midi *
|
||||
find_slot(int client, int port)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
for (i = 0; i < max_midi_devs; i++) {
|
||||
mdev = midi_devs[i];
|
||||
if (mdev && mdev->client == client && mdev->port == port) {
|
||||
/* found! */
|
||||
snd_use_lock_use(&mdev->use_lock);
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
return mdev;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)
|
||||
#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
|
||||
/*
|
||||
* register a new port if it doesn't exist yet
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *pinfo)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
|
||||
/* the port must include generic midi */
|
||||
if (! (pinfo->type & SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC))
|
||||
return 0;
|
||||
/* either read or write subscribable */
|
||||
if ((pinfo->capability & PERM_WRITE) != PERM_WRITE &&
|
||||
(pinfo->capability & PERM_READ) != PERM_READ)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* look for the identical slot
|
||||
*/
|
||||
if ((mdev = find_slot(pinfo->addr.client, pinfo->addr.port)) != NULL) {
|
||||
/* already exists */
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* allocate midi info record
|
||||
*/
|
||||
if ((mdev = kzalloc(sizeof(*mdev), GFP_KERNEL)) == NULL) {
|
||||
pr_err("ALSA: seq_oss: can't malloc midi info\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* copy the port information */
|
||||
mdev->client = pinfo->addr.client;
|
||||
mdev->port = pinfo->addr.port;
|
||||
mdev->flags = pinfo->capability;
|
||||
mdev->opened = 0;
|
||||
snd_use_lock_init(&mdev->use_lock);
|
||||
|
||||
/* copy and truncate the name of synth device */
|
||||
strlcpy(mdev->name, pinfo->name, sizeof(mdev->name));
|
||||
|
||||
/* create MIDI coder */
|
||||
if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &mdev->coder) < 0) {
|
||||
pr_err("ALSA: seq_oss: can't malloc midi coder\n");
|
||||
kfree(mdev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
/* OSS sequencer adds running status to all sequences */
|
||||
snd_midi_event_no_status(mdev->coder, 1);
|
||||
|
||||
/*
|
||||
* look for en empty slot
|
||||
*/
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
for (i = 0; i < max_midi_devs; i++) {
|
||||
if (midi_devs[i] == NULL)
|
||||
break;
|
||||
}
|
||||
if (i >= max_midi_devs) {
|
||||
if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) {
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
snd_midi_event_free(mdev->coder);
|
||||
kfree(mdev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
max_midi_devs++;
|
||||
}
|
||||
mdev->seq_device = i;
|
||||
midi_devs[mdev->seq_device] = mdev;
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* release the midi device if it was registered
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_check_exit_port(int client, int port)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
int index;
|
||||
|
||||
if ((mdev = find_slot(client, port)) != NULL) {
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
midi_devs[mdev->seq_device] = NULL;
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
snd_use_lock_sync(&mdev->use_lock);
|
||||
if (mdev->coder)
|
||||
snd_midi_event_free(mdev->coder);
|
||||
kfree(mdev);
|
||||
}
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
for (index = max_midi_devs - 1; index >= 0; index--) {
|
||||
if (midi_devs[index])
|
||||
break;
|
||||
}
|
||||
max_midi_devs = index + 1;
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* release the midi device if it was registered
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_midi_clear_all(void)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
for (i = 0; i < max_midi_devs; i++) {
|
||||
if ((mdev = midi_devs[i]) != NULL) {
|
||||
if (mdev->coder)
|
||||
snd_midi_event_free(mdev->coder);
|
||||
kfree(mdev);
|
||||
midi_devs[i] = NULL;
|
||||
}
|
||||
}
|
||||
max_midi_devs = 0;
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* set up midi tables
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
dp->max_mididev = max_midi_devs;
|
||||
}
|
||||
|
||||
/*
|
||||
* clean up midi tables
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dp->max_mididev; i++)
|
||||
snd_seq_oss_midi_close(dp, i);
|
||||
dp->max_mididev = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* open all midi devices. ignore errors.
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dp->max_mididev; i++)
|
||||
snd_seq_oss_midi_open(dp, i, file_mode);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get the midi device information
|
||||
*/
|
||||
static struct seq_oss_midi *
|
||||
get_mididev(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
if (dev < 0 || dev >= dp->max_mididev)
|
||||
return NULL;
|
||||
return get_mdev(dev);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* open the midi device if not opened yet
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int fmode)
|
||||
{
|
||||
int perm;
|
||||
struct seq_oss_midi *mdev;
|
||||
struct snd_seq_port_subscribe subs;
|
||||
|
||||
if ((mdev = get_mididev(dp, dev)) == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
/* already used? */
|
||||
if (mdev->opened && mdev->devinfo != dp) {
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
perm = 0;
|
||||
if (is_write_mode(fmode))
|
||||
perm |= PERM_WRITE;
|
||||
if (is_read_mode(fmode))
|
||||
perm |= PERM_READ;
|
||||
perm &= mdev->flags;
|
||||
if (perm == 0) {
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* already opened? */
|
||||
if ((mdev->opened & perm) == perm) {
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
perm &= ~mdev->opened;
|
||||
|
||||
memset(&subs, 0, sizeof(subs));
|
||||
|
||||
if (perm & PERM_WRITE) {
|
||||
subs.sender = dp->addr;
|
||||
subs.dest.client = mdev->client;
|
||||
subs.dest.port = mdev->port;
|
||||
if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
|
||||
mdev->opened |= PERM_WRITE;
|
||||
}
|
||||
if (perm & PERM_READ) {
|
||||
subs.sender.client = mdev->client;
|
||||
subs.sender.port = mdev->port;
|
||||
subs.dest = dp->addr;
|
||||
subs.flags = SNDRV_SEQ_PORT_SUBS_TIMESTAMP;
|
||||
subs.queue = dp->queue; /* queue for timestamps */
|
||||
if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
|
||||
mdev->opened |= PERM_READ;
|
||||
}
|
||||
|
||||
if (! mdev->opened) {
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
mdev->devinfo = dp;
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* close the midi device if already opened
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
struct snd_seq_port_subscribe subs;
|
||||
|
||||
if ((mdev = get_mididev(dp, dev)) == NULL)
|
||||
return -ENODEV;
|
||||
if (! mdev->opened || mdev->devinfo != dp) {
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(&subs, 0, sizeof(subs));
|
||||
if (mdev->opened & PERM_WRITE) {
|
||||
subs.sender = dp->addr;
|
||||
subs.dest.client = mdev->client;
|
||||
subs.dest.port = mdev->port;
|
||||
snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
|
||||
}
|
||||
if (mdev->opened & PERM_READ) {
|
||||
subs.sender.client = mdev->client;
|
||||
subs.sender.port = mdev->port;
|
||||
subs.dest = dp->addr;
|
||||
snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
|
||||
}
|
||||
|
||||
mdev->opened = 0;
|
||||
mdev->devinfo = NULL;
|
||||
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* change seq capability flags to file mode flags
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
int mode;
|
||||
|
||||
if ((mdev = get_mididev(dp, dev)) == NULL)
|
||||
return 0;
|
||||
|
||||
mode = 0;
|
||||
if (mdev->opened & PERM_WRITE)
|
||||
mode |= SNDRV_SEQ_OSS_FILE_WRITE;
|
||||
if (mdev->opened & PERM_READ)
|
||||
mode |= SNDRV_SEQ_OSS_FILE_READ;
|
||||
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return mode;
|
||||
}
|
||||
|
||||
/*
|
||||
* reset the midi device and close it:
|
||||
* so far, only close the device.
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
|
||||
if ((mdev = get_mididev(dp, dev)) == NULL)
|
||||
return;
|
||||
if (! mdev->opened) {
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mdev->opened & PERM_WRITE) {
|
||||
struct snd_seq_event ev;
|
||||
int c;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.dest.client = mdev->client;
|
||||
ev.dest.port = mdev->port;
|
||||
ev.queue = dp->queue;
|
||||
ev.source.port = dp->port;
|
||||
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) {
|
||||
ev.type = SNDRV_SEQ_EVENT_SENSING;
|
||||
snd_seq_oss_dispatch(dp, &ev, 0, 0);
|
||||
}
|
||||
for (c = 0; c < 16; c++) {
|
||||
ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
|
||||
ev.data.control.channel = c;
|
||||
ev.data.control.param = MIDI_CTL_ALL_NOTES_OFF;
|
||||
snd_seq_oss_dispatch(dp, &ev, 0, 0);
|
||||
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
|
||||
ev.data.control.param =
|
||||
MIDI_CTL_RESET_CONTROLLERS;
|
||||
snd_seq_oss_dispatch(dp, &ev, 0, 0);
|
||||
ev.type = SNDRV_SEQ_EVENT_PITCHBEND;
|
||||
ev.data.control.value = 0;
|
||||
snd_seq_oss_dispatch(dp, &ev, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
// snd_seq_oss_midi_close(dp, dev);
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get client/port of the specified MIDI device
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
|
||||
if ((mdev = get_mididev(dp, dev)) == NULL)
|
||||
return;
|
||||
addr->client = mdev->client;
|
||||
addr->port = mdev->port;
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* input callback - this can be atomic
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private_data)
|
||||
{
|
||||
struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
|
||||
struct seq_oss_midi *mdev;
|
||||
int rc;
|
||||
|
||||
if (dp->readq == NULL)
|
||||
return 0;
|
||||
if ((mdev = find_slot(ev->source.client, ev->source.port)) == NULL)
|
||||
return 0;
|
||||
if (! (mdev->opened & PERM_READ)) {
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
||||
rc = send_synth_event(dp, ev, mdev->seq_device);
|
||||
else
|
||||
rc = send_midi_event(dp, ev, mdev);
|
||||
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* convert ALSA sequencer event to OSS synth event
|
||||
*/
|
||||
static int
|
||||
send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev)
|
||||
{
|
||||
union evrec ossev;
|
||||
|
||||
memset(&ossev, 0, sizeof(ossev));
|
||||
|
||||
switch (ev->type) {
|
||||
case SNDRV_SEQ_EVENT_NOTEON:
|
||||
ossev.v.cmd = MIDI_NOTEON; break;
|
||||
case SNDRV_SEQ_EVENT_NOTEOFF:
|
||||
ossev.v.cmd = MIDI_NOTEOFF; break;
|
||||
case SNDRV_SEQ_EVENT_KEYPRESS:
|
||||
ossev.v.cmd = MIDI_KEY_PRESSURE; break;
|
||||
case SNDRV_SEQ_EVENT_CONTROLLER:
|
||||
ossev.l.cmd = MIDI_CTL_CHANGE; break;
|
||||
case SNDRV_SEQ_EVENT_PGMCHANGE:
|
||||
ossev.l.cmd = MIDI_PGM_CHANGE; break;
|
||||
case SNDRV_SEQ_EVENT_CHANPRESS:
|
||||
ossev.l.cmd = MIDI_CHN_PRESSURE; break;
|
||||
case SNDRV_SEQ_EVENT_PITCHBEND:
|
||||
ossev.l.cmd = MIDI_PITCH_BEND; break;
|
||||
default:
|
||||
return 0; /* not supported */
|
||||
}
|
||||
|
||||
ossev.v.dev = dev;
|
||||
|
||||
switch (ev->type) {
|
||||
case SNDRV_SEQ_EVENT_NOTEON:
|
||||
case SNDRV_SEQ_EVENT_NOTEOFF:
|
||||
case SNDRV_SEQ_EVENT_KEYPRESS:
|
||||
ossev.v.code = EV_CHN_VOICE;
|
||||
ossev.v.note = ev->data.note.note;
|
||||
ossev.v.parm = ev->data.note.velocity;
|
||||
ossev.v.chn = ev->data.note.channel;
|
||||
break;
|
||||
case SNDRV_SEQ_EVENT_CONTROLLER:
|
||||
case SNDRV_SEQ_EVENT_PGMCHANGE:
|
||||
case SNDRV_SEQ_EVENT_CHANPRESS:
|
||||
ossev.l.code = EV_CHN_COMMON;
|
||||
ossev.l.p1 = ev->data.control.param;
|
||||
ossev.l.val = ev->data.control.value;
|
||||
ossev.l.chn = ev->data.control.channel;
|
||||
break;
|
||||
case SNDRV_SEQ_EVENT_PITCHBEND:
|
||||
ossev.l.code = EV_CHN_COMMON;
|
||||
ossev.l.val = ev->data.control.value + 8192;
|
||||
ossev.l.chn = ev->data.control.channel;
|
||||
break;
|
||||
}
|
||||
|
||||
snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
|
||||
snd_seq_oss_readq_put_event(dp->readq, &ossev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* decode event and send MIDI bytes to read queue
|
||||
*/
|
||||
static int
|
||||
send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev)
|
||||
{
|
||||
char msg[32];
|
||||
int len;
|
||||
|
||||
snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
|
||||
if (!dp->timer->running)
|
||||
len = snd_seq_oss_timer_start(dp->timer);
|
||||
if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
|
||||
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
|
||||
snd_seq_oss_readq_puts(dp->readq, mdev->seq_device,
|
||||
ev->data.ext.ptr, ev->data.ext.len);
|
||||
} else {
|
||||
len = snd_midi_event_decode(mdev->coder, msg, sizeof(msg), ev);
|
||||
if (len > 0)
|
||||
snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, msg, len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* dump midi data
|
||||
* return 0 : enqueued
|
||||
* non-zero : invalid - ignored
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c, struct snd_seq_event *ev)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
|
||||
if ((mdev = get_mididev(dp, dev)) == NULL)
|
||||
return -ENODEV;
|
||||
if (snd_midi_event_encode_byte(mdev->coder, c, ev) > 0) {
|
||||
snd_seq_oss_fill_addr(dp, ev, mdev->client, mdev->port);
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return 0;
|
||||
}
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* create OSS compatible midi_info record
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
|
||||
if ((mdev = get_mididev(dp, dev)) == NULL)
|
||||
return -ENXIO;
|
||||
inf->device = dev;
|
||||
inf->dev_type = 0; /* FIXME: ?? */
|
||||
inf->capabilities = 0; /* FIXME: ?? */
|
||||
strlcpy(inf->name, mdev->name, sizeof(inf->name));
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/*
|
||||
* proc interface
|
||||
*/
|
||||
static char *
|
||||
capmode_str(int val)
|
||||
{
|
||||
val &= PERM_READ|PERM_WRITE;
|
||||
if (val == (PERM_READ|PERM_WRITE))
|
||||
return "read/write";
|
||||
else if (val == PERM_READ)
|
||||
return "read";
|
||||
else if (val == PERM_WRITE)
|
||||
return "write";
|
||||
else
|
||||
return "none";
|
||||
}
|
||||
|
||||
void
|
||||
snd_seq_oss_midi_info_read(struct snd_info_buffer *buf)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_midi *mdev;
|
||||
|
||||
snd_iprintf(buf, "\nNumber of MIDI devices: %d\n", max_midi_devs);
|
||||
for (i = 0; i < max_midi_devs; i++) {
|
||||
snd_iprintf(buf, "\nmidi %d: ", i);
|
||||
mdev = get_mdev(i);
|
||||
if (mdev == NULL) {
|
||||
snd_iprintf(buf, "*empty*\n");
|
||||
continue;
|
||||
}
|
||||
snd_iprintf(buf, "[%s] ALSA port %d:%d\n", mdev->name,
|
||||
mdev->client, mdev->port);
|
||||
snd_iprintf(buf, " capability %s / opened %s\n",
|
||||
capmode_str(mdev->flags),
|
||||
capmode_str(mdev->opened));
|
||||
snd_use_lock_free(&mdev->use_lock);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue