mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 17:18:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
119
sound/soc/omap/Kconfig
Normal file
119
sound/soc/omap/Kconfig
Normal file
|
@ -0,0 +1,119 @@
|
|||
config SND_OMAP_SOC
|
||||
tristate "SoC Audio for the Texas Instruments OMAP chips"
|
||||
depends on (ARCH_OMAP && DMA_OMAP) || (ARM && COMPILE_TEST)
|
||||
select SND_DMAENGINE_PCM
|
||||
|
||||
config SND_OMAP_SOC_DMIC
|
||||
tristate
|
||||
|
||||
config SND_OMAP_SOC_MCBSP
|
||||
tristate
|
||||
|
||||
config SND_OMAP_SOC_MCPDM
|
||||
tristate
|
||||
|
||||
config SND_OMAP_SOC_HDMI
|
||||
tristate
|
||||
|
||||
config SND_OMAP_SOC_N810
|
||||
tristate "SoC Audio support for Nokia N810"
|
||||
depends on SND_OMAP_SOC && MACH_NOKIA_N810 && I2C
|
||||
depends on OMAP_MUX
|
||||
select SND_OMAP_SOC_MCBSP
|
||||
select SND_SOC_TLV320AIC3X
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on Nokia N810.
|
||||
|
||||
config SND_OMAP_SOC_RX51
|
||||
tristate "SoC Audio support for Nokia RX-51"
|
||||
depends on SND_OMAP_SOC && ARM && (MACH_NOKIA_RX51 || COMPILE_TEST) && I2C
|
||||
select SND_OMAP_SOC_MCBSP
|
||||
select SND_SOC_TLV320AIC3X
|
||||
select SND_SOC_TPA6130A2
|
||||
depends on GPIOLIB
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on Nokia RX-51
|
||||
hardware. This is also known as Nokia N900 product.
|
||||
|
||||
config SND_OMAP_SOC_AMS_DELTA
|
||||
tristate "SoC Audio support for Amstrad E3 (Delta) videophone"
|
||||
depends on SND_OMAP_SOC && MACH_AMS_DELTA && TTY
|
||||
select SND_OMAP_SOC_MCBSP
|
||||
select SND_SOC_CX20442
|
||||
help
|
||||
Say Y if you want to add support for SoC audio device connected to
|
||||
a handset and a speakerphone found on Amstrad E3 (Delta) videophone.
|
||||
|
||||
Note that in order to get those devices fully supported, you have to
|
||||
build the kernel with standard serial port driver included and
|
||||
configured for at least 4 ports. Then, from userspace, you must load
|
||||
a line discipline #19 on the modem (ttyS3) serial line. The simplest
|
||||
way to achieve this is to install util-linux-ng and use the included
|
||||
ldattach utility. This can be started automatically from udev,
|
||||
a simple rule like this one should do the trick (it does for me):
|
||||
ACTION=="add", KERNEL=="controlC0", \
|
||||
RUN+="/usr/sbin/ldattach 19 /dev/ttyS3"
|
||||
|
||||
config SND_OMAP_SOC_OSK5912
|
||||
tristate "SoC Audio support for omap osk5912"
|
||||
depends on SND_OMAP_SOC && MACH_OMAP_OSK && I2C
|
||||
select SND_OMAP_SOC_MCBSP
|
||||
select SND_SOC_TLV320AIC23_I2C
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on osk5912.
|
||||
|
||||
config SND_OMAP_SOC_AM3517EVM
|
||||
tristate "SoC Audio support for OMAP3517 / AM3517 EVM"
|
||||
depends on SND_OMAP_SOC && MACH_OMAP3517EVM && I2C
|
||||
select SND_OMAP_SOC_MCBSP
|
||||
select SND_SOC_TLV320AIC23_I2C
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the OMAP3517 / AM3517
|
||||
EVM.
|
||||
|
||||
config SND_OMAP_SOC_OMAP_TWL4030
|
||||
tristate "SoC Audio support for TI SoC based boards with twl4030 codec"
|
||||
depends on TWL4030_CORE && SND_OMAP_SOC
|
||||
select SND_OMAP_SOC_MCBSP
|
||||
select SND_SOC_TWL4030
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on TI SoC based boards
|
||||
using twl4030 as c codec. This driver currently supports:
|
||||
- Beagleboard or Devkit8000
|
||||
- Gumstix Overo or CompuLab CM-T35/CM-T3730
|
||||
- IGEP v2
|
||||
- OMAP3EVM
|
||||
- SDP3430
|
||||
- Zoom2
|
||||
|
||||
config SND_OMAP_SOC_OMAP_ABE_TWL6040
|
||||
tristate "SoC Audio support for OMAP boards using ABE and twl6040 codec"
|
||||
depends on TWL6040_CORE && SND_OMAP_SOC && (ARCH_OMAP4 || COMPILE_TEST)
|
||||
select SND_OMAP_SOC_DMIC
|
||||
select SND_OMAP_SOC_MCPDM
|
||||
select SND_SOC_TWL6040
|
||||
select SND_SOC_DMIC
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on OMAP boards using
|
||||
ABE and twl6040 codec. This driver currently supports:
|
||||
- SDP4430/Blaze boards
|
||||
- PandaBoard (4430)
|
||||
- PandaBoardES (4460)
|
||||
|
||||
config SND_OMAP_SOC_OMAP_HDMI
|
||||
tristate "SoC Audio support for Texas Instruments OMAP HDMI"
|
||||
depends on SND_OMAP_SOC && OMAP4_DSS_HDMI && OMAP2_DSS
|
||||
select SND_OMAP_SOC_HDMI
|
||||
select SND_SOC_HDMI_CODEC
|
||||
select OMAP4_DSS_HDMI_AUDIO
|
||||
help
|
||||
Say Y if you want to add support for SoC HDMI audio on Texas Instruments
|
||||
OMAP4 chips
|
||||
|
||||
config SND_OMAP_SOC_OMAP3_PANDORA
|
||||
tristate "SoC Audio support for OMAP3 Pandora"
|
||||
depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3_PANDORA
|
||||
select SND_OMAP_SOC_MCBSP
|
||||
select SND_SOC_TWL4030
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on the OMAP3 Pandora.
|
33
sound/soc/omap/Makefile
Normal file
33
sound/soc/omap/Makefile
Normal file
|
@ -0,0 +1,33 @@
|
|||
# OMAP Platform Support
|
||||
snd-soc-omap-objs := omap-pcm.o
|
||||
snd-soc-omap-dmic-objs := omap-dmic.o
|
||||
snd-soc-omap-mcbsp-objs := omap-mcbsp.o mcbsp.o
|
||||
snd-soc-omap-mcpdm-objs := omap-mcpdm.o
|
||||
snd-soc-omap-hdmi-objs := omap-hdmi.o
|
||||
|
||||
obj-$(CONFIG_SND_OMAP_SOC) += snd-soc-omap.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_DMIC) += snd-soc-omap-dmic.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_HDMI) += snd-soc-omap-hdmi.o
|
||||
|
||||
# OMAP Machine Support
|
||||
snd-soc-n810-objs := n810.o
|
||||
snd-soc-rx51-objs := rx51.o
|
||||
snd-soc-ams-delta-objs := ams-delta.o
|
||||
snd-soc-osk5912-objs := osk5912.o
|
||||
snd-soc-am3517evm-objs := am3517evm.o
|
||||
snd-soc-omap-abe-twl6040-objs := omap-abe-twl6040.o
|
||||
snd-soc-omap-twl4030-objs := omap-twl4030.o
|
||||
snd-soc-omap3pandora-objs := omap3pandora.o
|
||||
snd-soc-omap-hdmi-card-objs := omap-hdmi-card.o
|
||||
|
||||
obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_AM3517EVM) += snd-soc-am3517evm.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040) += snd-soc-omap-abe-twl6040.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OMAP_TWL4030) += snd-soc-omap-twl4030.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o
|
||||
obj-$(CONFIG_SND_OMAP_SOC_OMAP_HDMI) += snd-soc-omap-hdmi-card.o
|
141
sound/soc/omap/am3517evm.c
Normal file
141
sound/soc/omap/am3517evm.c
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* am3517evm.c -- ALSA SoC support for OMAP3517 / AM3517 EVM
|
||||
*
|
||||
* Author: Anuj Aggarwal <anuj.aggarwal@ti.com>
|
||||
*
|
||||
* Based on sound/soc/omap/beagle.c by Steve Sakoman
|
||||
*
|
||||
* Copyright (C) 2009 Texas Instruments Incorporated
|
||||
*
|
||||
* 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 version 2.
|
||||
*
|
||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
|
||||
* whether express or implied; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
#include <linux/platform_data/asoc-ti-mcbsp.h>
|
||||
|
||||
#include "omap-mcbsp.h"
|
||||
|
||||
#include "../codecs/tlv320aic23.h"
|
||||
|
||||
#define CODEC_CLOCK 12000000
|
||||
|
||||
static int am3517evm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
/* Set the codec system clock for DAC and ADC */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
|
||||
CODEC_CLOCK, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
printk(KERN_ERR "can't set codec system clock\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops am3517evm_ops = {
|
||||
.hw_params = am3517evm_hw_params,
|
||||
};
|
||||
|
||||
/* am3517evm machine dapm widgets */
|
||||
static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Line Out", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
SND_SOC_DAPM_MIC("Mic In", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
/* Line Out connected to LLOUT, RLOUT */
|
||||
{"Line Out", NULL, "LOUT"},
|
||||
{"Line Out", NULL, "ROUT"},
|
||||
|
||||
{"LLINEIN", NULL, "Line In"},
|
||||
{"RLINEIN", NULL, "Line In"},
|
||||
|
||||
{"MICIN", NULL, "Mic In"},
|
||||
};
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link am3517evm_dai = {
|
||||
.name = "TLV320AIC23",
|
||||
.stream_name = "AIC23",
|
||||
.cpu_dai_name = "omap-mcbsp.1",
|
||||
.codec_dai_name = "tlv320aic23-hifi",
|
||||
.platform_name = "omap-mcbsp.1",
|
||||
.codec_name = "tlv320aic23-codec.2-001a",
|
||||
.dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM,
|
||||
.ops = &am3517evm_ops,
|
||||
};
|
||||
|
||||
/* Audio machine driver */
|
||||
static struct snd_soc_card snd_soc_am3517evm = {
|
||||
.name = "am3517evm",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &am3517evm_dai,
|
||||
.num_links = 1,
|
||||
|
||||
.dapm_widgets = tlv320aic23_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets),
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
};
|
||||
|
||||
static struct platform_device *am3517evm_snd_device;
|
||||
|
||||
static int __init am3517evm_soc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!machine_is_omap3517evm())
|
||||
return -ENODEV;
|
||||
pr_info("OMAP3517 / AM3517 EVM SoC init\n");
|
||||
|
||||
am3517evm_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!am3517evm_snd_device) {
|
||||
printk(KERN_ERR "Platform device allocation failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(am3517evm_snd_device, &snd_soc_am3517evm);
|
||||
|
||||
ret = platform_device_add(am3517evm_snd_device);
|
||||
if (ret)
|
||||
goto err1;
|
||||
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
printk(KERN_ERR "Unable to add platform device\n");
|
||||
platform_device_put(am3517evm_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit am3517evm_soc_exit(void)
|
||||
{
|
||||
platform_device_unregister(am3517evm_snd_device);
|
||||
}
|
||||
|
||||
module_init(am3517evm_soc_init);
|
||||
module_exit(am3517evm_soc_exit);
|
||||
|
||||
MODULE_AUTHOR("Anuj Aggarwal <anuj.aggarwal@ti.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC OMAP3517 / AM3517 EVM");
|
||||
MODULE_LICENSE("GPL v2");
|
613
sound/soc/omap/ams-delta.c
Normal file
613
sound/soc/omap/ams-delta.c
Normal file
|
@ -0,0 +1,613 @@
|
|||
/*
|
||||
* ams-delta.c -- SoC audio for Amstrad E3 (Delta) videophone
|
||||
*
|
||||
* Copyright (C) 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
|
||||
*
|
||||
* Initially based on sound/soc/omap/osk5912.x
|
||||
* Copyright (C) 2008 Mistral Solutions
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include <mach/board-ams-delta.h>
|
||||
#include <linux/platform_data/asoc-ti-mcbsp.h>
|
||||
|
||||
#include "omap-mcbsp.h"
|
||||
#include "../codecs/cx20442.h"
|
||||
|
||||
/* Board specific DAPM widgets */
|
||||
static const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = {
|
||||
/* Handset */
|
||||
SND_SOC_DAPM_MIC("Mouthpiece", NULL),
|
||||
SND_SOC_DAPM_HP("Earpiece", NULL),
|
||||
/* Handsfree/Speakerphone */
|
||||
SND_SOC_DAPM_MIC("Microphone", NULL),
|
||||
SND_SOC_DAPM_SPK("Speaker", NULL),
|
||||
};
|
||||
|
||||
/* How they are connected to codec pins */
|
||||
static const struct snd_soc_dapm_route ams_delta_audio_map[] = {
|
||||
{"TELIN", NULL, "Mouthpiece"},
|
||||
{"Earpiece", NULL, "TELOUT"},
|
||||
|
||||
{"MIC", NULL, "Microphone"},
|
||||
{"Speaker", NULL, "SPKOUT"},
|
||||
};
|
||||
|
||||
/*
|
||||
* Controls, functional after the modem line discipline is activated.
|
||||
*/
|
||||
|
||||
/* Virtual switch: audio input/output constellations */
|
||||
static const char *ams_delta_audio_mode[] =
|
||||
{"Mixed", "Handset", "Handsfree", "Speakerphone"};
|
||||
|
||||
/* Selection <-> pin translation */
|
||||
#define AMS_DELTA_MOUTHPIECE 0
|
||||
#define AMS_DELTA_EARPIECE 1
|
||||
#define AMS_DELTA_MICROPHONE 2
|
||||
#define AMS_DELTA_SPEAKER 3
|
||||
#define AMS_DELTA_AGC 4
|
||||
|
||||
#define AMS_DELTA_MIXED ((1 << AMS_DELTA_EARPIECE) | \
|
||||
(1 << AMS_DELTA_MICROPHONE))
|
||||
#define AMS_DELTA_HANDSET ((1 << AMS_DELTA_MOUTHPIECE) | \
|
||||
(1 << AMS_DELTA_EARPIECE))
|
||||
#define AMS_DELTA_HANDSFREE ((1 << AMS_DELTA_MICROPHONE) | \
|
||||
(1 << AMS_DELTA_SPEAKER))
|
||||
#define AMS_DELTA_SPEAKERPHONE (AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC))
|
||||
|
||||
static const unsigned short ams_delta_audio_mode_pins[] = {
|
||||
AMS_DELTA_MIXED,
|
||||
AMS_DELTA_HANDSET,
|
||||
AMS_DELTA_HANDSFREE,
|
||||
AMS_DELTA_SPEAKERPHONE,
|
||||
};
|
||||
|
||||
static unsigned short ams_delta_audio_agc;
|
||||
|
||||
/*
|
||||
* Used for passing a codec structure pointer
|
||||
* from the board initialization code to the tty line discipline.
|
||||
*/
|
||||
static struct snd_soc_codec *cx20442_codec;
|
||||
|
||||
static int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
struct snd_soc_dapm_context *dapm = &card->dapm;
|
||||
struct soc_enum *control = (struct soc_enum *)kcontrol->private_value;
|
||||
unsigned short pins;
|
||||
int pin, changed = 0;
|
||||
|
||||
/* Refuse any mode changes if we are not able to control the codec. */
|
||||
if (!cx20442_codec->hw_write)
|
||||
return -EUNATCH;
|
||||
|
||||
if (ucontrol->value.enumerated.item[0] >= control->items)
|
||||
return -EINVAL;
|
||||
|
||||
snd_soc_dapm_mutex_lock(dapm);
|
||||
|
||||
/* Translate selection to bitmap */
|
||||
pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]];
|
||||
|
||||
/* Setup pins after corresponding bits if changed */
|
||||
pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE));
|
||||
|
||||
if (pin != snd_soc_dapm_get_pin_status(dapm, "Mouthpiece")) {
|
||||
changed = 1;
|
||||
if (pin)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Mouthpiece");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece");
|
||||
}
|
||||
pin = !!(pins & (1 << AMS_DELTA_EARPIECE));
|
||||
if (pin != snd_soc_dapm_get_pin_status(dapm, "Earpiece")) {
|
||||
changed = 1;
|
||||
if (pin)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Earpiece");
|
||||
}
|
||||
pin = !!(pins & (1 << AMS_DELTA_MICROPHONE));
|
||||
if (pin != snd_soc_dapm_get_pin_status(dapm, "Microphone")) {
|
||||
changed = 1;
|
||||
if (pin)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Microphone");
|
||||
}
|
||||
pin = !!(pins & (1 << AMS_DELTA_SPEAKER));
|
||||
if (pin != snd_soc_dapm_get_pin_status(dapm, "Speaker")) {
|
||||
changed = 1;
|
||||
if (pin)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker");
|
||||
}
|
||||
pin = !!(pins & (1 << AMS_DELTA_AGC));
|
||||
if (pin != ams_delta_audio_agc) {
|
||||
ams_delta_audio_agc = pin;
|
||||
changed = 1;
|
||||
if (pin)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "AGCIN");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN");
|
||||
}
|
||||
|
||||
if (changed)
|
||||
snd_soc_dapm_sync_unlocked(dapm);
|
||||
|
||||
snd_soc_dapm_mutex_unlock(dapm);
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
struct snd_soc_dapm_context *dapm = &card->dapm;
|
||||
unsigned short pins, mode;
|
||||
|
||||
pins = ((snd_soc_dapm_get_pin_status(dapm, "Mouthpiece") <<
|
||||
AMS_DELTA_MOUTHPIECE) |
|
||||
(snd_soc_dapm_get_pin_status(dapm, "Earpiece") <<
|
||||
AMS_DELTA_EARPIECE));
|
||||
if (pins)
|
||||
pins |= (snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
|
||||
AMS_DELTA_MICROPHONE);
|
||||
else
|
||||
pins = ((snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
|
||||
AMS_DELTA_MICROPHONE) |
|
||||
(snd_soc_dapm_get_pin_status(dapm, "Speaker") <<
|
||||
AMS_DELTA_SPEAKER) |
|
||||
(ams_delta_audio_agc << AMS_DELTA_AGC));
|
||||
|
||||
for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++)
|
||||
if (pins == ams_delta_audio_mode_pins[mode])
|
||||
break;
|
||||
|
||||
if (mode >= ARRAY_SIZE(ams_delta_audio_mode))
|
||||
return -EINVAL;
|
||||
|
||||
ucontrol->value.enumerated.item[0] = mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum,
|
||||
ams_delta_audio_mode);
|
||||
|
||||
static const struct snd_kcontrol_new ams_delta_audio_controls[] = {
|
||||
SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum,
|
||||
ams_delta_get_audio_mode, ams_delta_set_audio_mode),
|
||||
};
|
||||
|
||||
/* Hook switch */
|
||||
static struct snd_soc_jack ams_delta_hook_switch;
|
||||
static struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = {
|
||||
{
|
||||
.gpio = 4,
|
||||
.name = "hook_switch",
|
||||
.report = SND_JACK_HEADSET,
|
||||
.invert = 1,
|
||||
.debounce_time = 150,
|
||||
}
|
||||
};
|
||||
|
||||
/* After we are able to control the codec over the modem,
|
||||
* the hook switch can be used for dynamic DAPM reconfiguration. */
|
||||
static struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = {
|
||||
/* Handset */
|
||||
{
|
||||
.pin = "Mouthpiece",
|
||||
.mask = SND_JACK_MICROPHONE,
|
||||
},
|
||||
{
|
||||
.pin = "Earpiece",
|
||||
.mask = SND_JACK_HEADPHONE,
|
||||
},
|
||||
/* Handsfree */
|
||||
{
|
||||
.pin = "Microphone",
|
||||
.mask = SND_JACK_MICROPHONE,
|
||||
.invert = 1,
|
||||
},
|
||||
{
|
||||
.pin = "Speaker",
|
||||
.mask = SND_JACK_HEADPHONE,
|
||||
.invert = 1,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Modem line discipline, required for making above controls functional.
|
||||
* Activated from userspace with ldattach, possibly invoked from udev rule.
|
||||
*/
|
||||
|
||||
/* To actually apply any modem controlled configuration changes to the codec,
|
||||
* we must connect codec DAI pins to the modem for a moment. Be careful not
|
||||
* to interfere with our digital mute function that shares the same hardware. */
|
||||
static struct timer_list cx81801_timer;
|
||||
static bool cx81801_cmd_pending;
|
||||
static bool ams_delta_muted;
|
||||
static DEFINE_SPINLOCK(ams_delta_lock);
|
||||
|
||||
static void cx81801_timeout(unsigned long data)
|
||||
{
|
||||
int muted;
|
||||
|
||||
spin_lock(&ams_delta_lock);
|
||||
cx81801_cmd_pending = 0;
|
||||
muted = ams_delta_muted;
|
||||
spin_unlock(&ams_delta_lock);
|
||||
|
||||
/* Reconnect the codec DAI back from the modem to the CPU DAI
|
||||
* only if digital mute still off */
|
||||
if (!muted)
|
||||
ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC, 0);
|
||||
}
|
||||
|
||||
/* Line discipline .open() */
|
||||
static int cx81801_open(struct tty_struct *tty)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!cx20442_codec)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Pass the codec structure pointer for use by other ldisc callbacks,
|
||||
* both the card and the codec specific parts.
|
||||
*/
|
||||
tty->disc_data = cx20442_codec;
|
||||
|
||||
ret = v253_ops.open(tty);
|
||||
|
||||
if (ret < 0)
|
||||
tty->disc_data = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Line discipline .close() */
|
||||
static void cx81801_close(struct tty_struct *tty)
|
||||
{
|
||||
struct snd_soc_codec *codec = tty->disc_data;
|
||||
struct snd_soc_dapm_context *dapm = &codec->component.card->dapm;
|
||||
|
||||
del_timer_sync(&cx81801_timer);
|
||||
|
||||
/* Prevent the hook switch from further changing the DAPM pins */
|
||||
INIT_LIST_HEAD(&ams_delta_hook_switch.pins);
|
||||
|
||||
if (!codec)
|
||||
return;
|
||||
|
||||
v253_ops.close(tty);
|
||||
|
||||
/* Revert back to default audio input/output constellation */
|
||||
snd_soc_dapm_mutex_lock(dapm);
|
||||
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece");
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece");
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone");
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker");
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN");
|
||||
|
||||
snd_soc_dapm_sync_unlocked(dapm);
|
||||
|
||||
snd_soc_dapm_mutex_unlock(dapm);
|
||||
}
|
||||
|
||||
/* Line discipline .hangup() */
|
||||
static int cx81801_hangup(struct tty_struct *tty)
|
||||
{
|
||||
cx81801_close(tty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Line discipline .receive_buf() */
|
||||
static void cx81801_receive(struct tty_struct *tty,
|
||||
const unsigned char *cp, char *fp, int count)
|
||||
{
|
||||
struct snd_soc_codec *codec = tty->disc_data;
|
||||
const unsigned char *c;
|
||||
int apply, ret;
|
||||
|
||||
if (!codec)
|
||||
return;
|
||||
|
||||
if (!codec->hw_write) {
|
||||
/* First modem response, complete setup procedure */
|
||||
|
||||
/* Initialize timer used for config pulse generation */
|
||||
setup_timer(&cx81801_timer, cx81801_timeout, 0);
|
||||
|
||||
v253_ops.receive_buf(tty, cp, fp, count);
|
||||
|
||||
/* Link hook switch to DAPM pins */
|
||||
ret = snd_soc_jack_add_pins(&ams_delta_hook_switch,
|
||||
ARRAY_SIZE(ams_delta_hook_switch_pins),
|
||||
ams_delta_hook_switch_pins);
|
||||
if (ret)
|
||||
dev_warn(codec->dev,
|
||||
"Failed to link hook switch to DAPM pins, "
|
||||
"will continue with hook switch unlinked.\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
v253_ops.receive_buf(tty, cp, fp, count);
|
||||
|
||||
for (c = &cp[count - 1]; c >= cp; c--) {
|
||||
if (*c != '\r')
|
||||
continue;
|
||||
/* Complete modem response received, apply config to codec */
|
||||
|
||||
spin_lock_bh(&ams_delta_lock);
|
||||
mod_timer(&cx81801_timer, jiffies + msecs_to_jiffies(150));
|
||||
apply = !ams_delta_muted && !cx81801_cmd_pending;
|
||||
cx81801_cmd_pending = 1;
|
||||
spin_unlock_bh(&ams_delta_lock);
|
||||
|
||||
/* Apply config pulse by connecting the codec to the modem
|
||||
* if not already done */
|
||||
if (apply)
|
||||
ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC,
|
||||
AMS_DELTA_LATCH2_MODEM_CODEC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Line discipline .write_wakeup() */
|
||||
static void cx81801_wakeup(struct tty_struct *tty)
|
||||
{
|
||||
v253_ops.write_wakeup(tty);
|
||||
}
|
||||
|
||||
static struct tty_ldisc_ops cx81801_ops = {
|
||||
.magic = TTY_LDISC_MAGIC,
|
||||
.name = "cx81801",
|
||||
.owner = THIS_MODULE,
|
||||
.open = cx81801_open,
|
||||
.close = cx81801_close,
|
||||
.hangup = cx81801_hangup,
|
||||
.receive_buf = cx81801_receive,
|
||||
.write_wakeup = cx81801_wakeup,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Even if not very useful, the sound card can still work without any of the
|
||||
* above functonality activated. You can still control its audio input/output
|
||||
* constellation and speakerphone gain from userspace by issuing AT commands
|
||||
* over the modem port.
|
||||
*/
|
||||
|
||||
static int ams_delta_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
|
||||
/* Set cpu DAI configuration */
|
||||
return snd_soc_dai_set_fmt(rtd->cpu_dai,
|
||||
SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
}
|
||||
|
||||
static struct snd_soc_ops ams_delta_ops = {
|
||||
.hw_params = ams_delta_hw_params,
|
||||
};
|
||||
|
||||
|
||||
/* Digital mute implemented using modem/CPU multiplexer.
|
||||
* Shares hardware with codec config pulse generation */
|
||||
static bool ams_delta_muted = 1;
|
||||
|
||||
static int ams_delta_digital_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
int apply;
|
||||
|
||||
if (ams_delta_muted == mute)
|
||||
return 0;
|
||||
|
||||
spin_lock_bh(&ams_delta_lock);
|
||||
ams_delta_muted = mute;
|
||||
apply = !cx81801_cmd_pending;
|
||||
spin_unlock_bh(&ams_delta_lock);
|
||||
|
||||
if (apply)
|
||||
ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC,
|
||||
mute ? AMS_DELTA_LATCH2_MODEM_CODEC : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Our codec DAI probably doesn't have its own .ops structure */
|
||||
static const struct snd_soc_dai_ops ams_delta_dai_ops = {
|
||||
.digital_mute = ams_delta_digital_mute,
|
||||
};
|
||||
|
||||
/* Will be used if the codec ever has its own digital_mute function */
|
||||
static int ams_delta_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return ams_delta_digital_mute(NULL, 0);
|
||||
}
|
||||
|
||||
static void ams_delta_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
ams_delta_digital_mute(NULL, 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Card initialization
|
||||
*/
|
||||
|
||||
static int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct snd_soc_dapm_context *dapm = &card->dapm;
|
||||
int ret;
|
||||
/* Codec is ready, now add/activate board specific controls */
|
||||
|
||||
/* Store a pointer to the codec structure for tty ldisc use */
|
||||
cx20442_codec = rtd->codec;
|
||||
|
||||
/* Set up digital mute if not provided by the codec */
|
||||
if (!codec_dai->driver->ops) {
|
||||
codec_dai->driver->ops = &ams_delta_dai_ops;
|
||||
} else {
|
||||
ams_delta_ops.startup = ams_delta_startup;
|
||||
ams_delta_ops.shutdown = ams_delta_shutdown;
|
||||
}
|
||||
|
||||
/* Add hook switch - can be used to control the codec from userspace
|
||||
* even if line discipline fails */
|
||||
ret = snd_soc_jack_new(rtd->codec, "hook_switch",
|
||||
SND_JACK_HEADSET, &ams_delta_hook_switch);
|
||||
if (ret)
|
||||
dev_warn(card->dev,
|
||||
"Failed to allocate resources for hook switch, "
|
||||
"will continue without one.\n");
|
||||
else {
|
||||
ret = snd_soc_jack_add_gpios(&ams_delta_hook_switch,
|
||||
ARRAY_SIZE(ams_delta_hook_switch_gpios),
|
||||
ams_delta_hook_switch_gpios);
|
||||
if (ret)
|
||||
dev_warn(card->dev,
|
||||
"Failed to set up hook switch GPIO line, "
|
||||
"will continue with hook switch inactive.\n");
|
||||
}
|
||||
|
||||
/* Register optional line discipline for over the modem control */
|
||||
ret = tty_register_ldisc(N_V253, &cx81801_ops);
|
||||
if (ret) {
|
||||
dev_warn(card->dev,
|
||||
"Failed to register line discipline, "
|
||||
"will continue without any controls.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set up initial pin constellation */
|
||||
snd_soc_dapm_disable_pin(dapm, "Mouthpiece");
|
||||
snd_soc_dapm_disable_pin(dapm, "Speaker");
|
||||
snd_soc_dapm_disable_pin(dapm, "AGCIN");
|
||||
snd_soc_dapm_disable_pin(dapm, "AGCOUT");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ams_delta_card_remove(struct snd_soc_card *card)
|
||||
{
|
||||
snd_soc_jack_free_gpios(&ams_delta_hook_switch,
|
||||
ARRAY_SIZE(ams_delta_hook_switch_gpios),
|
||||
ams_delta_hook_switch_gpios);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* DAI glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link ams_delta_dai_link = {
|
||||
.name = "CX20442",
|
||||
.stream_name = "CX20442",
|
||||
.cpu_dai_name = "omap-mcbsp.1",
|
||||
.codec_dai_name = "cx20442-voice",
|
||||
.init = ams_delta_cx20442_init,
|
||||
.platform_name = "omap-mcbsp.1",
|
||||
.codec_name = "cx20442-codec",
|
||||
.ops = &ams_delta_ops,
|
||||
};
|
||||
|
||||
/* Audio card driver */
|
||||
static struct snd_soc_card ams_delta_audio_card = {
|
||||
.name = "AMS_DELTA",
|
||||
.owner = THIS_MODULE,
|
||||
.remove = ams_delta_card_remove,
|
||||
.dai_link = &ams_delta_dai_link,
|
||||
.num_links = 1,
|
||||
|
||||
.controls = ams_delta_audio_controls,
|
||||
.num_controls = ARRAY_SIZE(ams_delta_audio_controls),
|
||||
.dapm_widgets = ams_delta_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(ams_delta_dapm_widgets),
|
||||
.dapm_routes = ams_delta_audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(ams_delta_audio_map),
|
||||
};
|
||||
|
||||
/* Module init/exit */
|
||||
static int ams_delta_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &ams_delta_audio_card;
|
||||
int ret;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
card->dev = NULL;
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ams_delta_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
if (tty_unregister_ldisc(N_V253) != 0)
|
||||
dev_warn(&pdev->dev,
|
||||
"failed to unregister V253 line discipline\n");
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
card->dev = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DRV_NAME "ams-delta-audio"
|
||||
|
||||
static struct platform_driver ams_delta_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ams_delta_probe,
|
||||
.remove = ams_delta_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(ams_delta_driver);
|
||||
|
||||
MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>");
|
||||
MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
1101
sound/soc/omap/mcbsp.c
Normal file
1101
sound/soc/omap/mcbsp.c
Normal file
File diff suppressed because it is too large
Load diff
354
sound/soc/omap/mcbsp.h
Normal file
354
sound/soc/omap/mcbsp.h
Normal file
|
@ -0,0 +1,354 @@
|
|||
/*
|
||||
* sound/soc/omap/mcbsp.h
|
||||
*
|
||||
* OMAP Multi-Channel Buffered Serial Port
|
||||
*
|
||||
* Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
|
||||
* Peter Ujfalusi <peter.ujfalusi@ti.com>
|
||||
*
|
||||
* 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 __ASOC_MCBSP_H
|
||||
#define __ASOC_MCBSP_H
|
||||
|
||||
#ifdef CONFIG_ARCH_OMAP1
|
||||
#define mcbsp_omap1() 1
|
||||
#else
|
||||
#define mcbsp_omap1() 0
|
||||
#endif
|
||||
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
/* McBSP register numbers. Register address offset = num * reg_step */
|
||||
enum {
|
||||
/* Common registers */
|
||||
OMAP_MCBSP_REG_SPCR2 = 4,
|
||||
OMAP_MCBSP_REG_SPCR1,
|
||||
OMAP_MCBSP_REG_RCR2,
|
||||
OMAP_MCBSP_REG_RCR1,
|
||||
OMAP_MCBSP_REG_XCR2,
|
||||
OMAP_MCBSP_REG_XCR1,
|
||||
OMAP_MCBSP_REG_SRGR2,
|
||||
OMAP_MCBSP_REG_SRGR1,
|
||||
OMAP_MCBSP_REG_MCR2,
|
||||
OMAP_MCBSP_REG_MCR1,
|
||||
OMAP_MCBSP_REG_RCERA,
|
||||
OMAP_MCBSP_REG_RCERB,
|
||||
OMAP_MCBSP_REG_XCERA,
|
||||
OMAP_MCBSP_REG_XCERB,
|
||||
OMAP_MCBSP_REG_PCR0,
|
||||
OMAP_MCBSP_REG_RCERC,
|
||||
OMAP_MCBSP_REG_RCERD,
|
||||
OMAP_MCBSP_REG_XCERC,
|
||||
OMAP_MCBSP_REG_XCERD,
|
||||
OMAP_MCBSP_REG_RCERE,
|
||||
OMAP_MCBSP_REG_RCERF,
|
||||
OMAP_MCBSP_REG_XCERE,
|
||||
OMAP_MCBSP_REG_XCERF,
|
||||
OMAP_MCBSP_REG_RCERG,
|
||||
OMAP_MCBSP_REG_RCERH,
|
||||
OMAP_MCBSP_REG_XCERG,
|
||||
OMAP_MCBSP_REG_XCERH,
|
||||
|
||||
/* OMAP1-OMAP2420 registers */
|
||||
OMAP_MCBSP_REG_DRR2 = 0,
|
||||
OMAP_MCBSP_REG_DRR1,
|
||||
OMAP_MCBSP_REG_DXR2,
|
||||
OMAP_MCBSP_REG_DXR1,
|
||||
|
||||
/* OMAP2430 and onwards */
|
||||
OMAP_MCBSP_REG_DRR = 0,
|
||||
OMAP_MCBSP_REG_DXR = 2,
|
||||
OMAP_MCBSP_REG_SYSCON = 35,
|
||||
OMAP_MCBSP_REG_THRSH2,
|
||||
OMAP_MCBSP_REG_THRSH1,
|
||||
OMAP_MCBSP_REG_IRQST = 40,
|
||||
OMAP_MCBSP_REG_IRQEN,
|
||||
OMAP_MCBSP_REG_WAKEUPEN,
|
||||
OMAP_MCBSP_REG_XCCR,
|
||||
OMAP_MCBSP_REG_RCCR,
|
||||
OMAP_MCBSP_REG_XBUFFSTAT,
|
||||
OMAP_MCBSP_REG_RBUFFSTAT,
|
||||
OMAP_MCBSP_REG_SSELCR,
|
||||
};
|
||||
|
||||
/* OMAP3 sidetone control registers */
|
||||
#define OMAP_ST_REG_REV 0x00
|
||||
#define OMAP_ST_REG_SYSCONFIG 0x10
|
||||
#define OMAP_ST_REG_IRQSTATUS 0x18
|
||||
#define OMAP_ST_REG_IRQENABLE 0x1C
|
||||
#define OMAP_ST_REG_SGAINCR 0x24
|
||||
#define OMAP_ST_REG_SFIRCR 0x28
|
||||
#define OMAP_ST_REG_SSELCR 0x2C
|
||||
|
||||
/************************** McBSP SPCR1 bit definitions ***********************/
|
||||
#define RRST BIT(0)
|
||||
#define RRDY BIT(1)
|
||||
#define RFULL BIT(2)
|
||||
#define RSYNC_ERR BIT(3)
|
||||
#define RINTM(value) (((value) & 0x3) << 4) /* bits 4:5 */
|
||||
#define ABIS BIT(6)
|
||||
#define DXENA BIT(7)
|
||||
#define CLKSTP(value) (((value) & 0x3) << 11) /* bits 11:12 */
|
||||
#define RJUST(value) (((value) & 0x3) << 13) /* bits 13:14 */
|
||||
#define ALB BIT(15)
|
||||
#define DLB BIT(15)
|
||||
|
||||
/************************** McBSP SPCR2 bit definitions ***********************/
|
||||
#define XRST BIT(0)
|
||||
#define XRDY BIT(1)
|
||||
#define XEMPTY BIT(2)
|
||||
#define XSYNC_ERR BIT(3)
|
||||
#define XINTM(value) (((value) & 0x3) << 4) /* bits 4:5 */
|
||||
#define GRST BIT(6)
|
||||
#define FRST BIT(7)
|
||||
#define SOFT BIT(8)
|
||||
#define FREE BIT(9)
|
||||
|
||||
/************************** McBSP PCR bit definitions *************************/
|
||||
#define CLKRP BIT(0)
|
||||
#define CLKXP BIT(1)
|
||||
#define FSRP BIT(2)
|
||||
#define FSXP BIT(3)
|
||||
#define DR_STAT BIT(4)
|
||||
#define DX_STAT BIT(5)
|
||||
#define CLKS_STAT BIT(6)
|
||||
#define SCLKME BIT(7)
|
||||
#define CLKRM BIT(8)
|
||||
#define CLKXM BIT(9)
|
||||
#define FSRM BIT(10)
|
||||
#define FSXM BIT(11)
|
||||
#define RIOEN BIT(12)
|
||||
#define XIOEN BIT(13)
|
||||
#define IDLE_EN BIT(14)
|
||||
|
||||
/************************** McBSP RCR1 bit definitions ************************/
|
||||
#define RWDLEN1(value) (((value) & 0x7) << 5) /* Bits 5:7 */
|
||||
#define RFRLEN1(value) (((value) & 0x7f) << 8) /* Bits 8:14 */
|
||||
|
||||
/************************** McBSP XCR1 bit definitions ************************/
|
||||
#define XWDLEN1(value) (((value) & 0x7) << 5) /* Bits 5:7 */
|
||||
#define XFRLEN1(value) (((value) & 0x7f) << 8) /* Bits 8:14 */
|
||||
|
||||
/*************************** McBSP RCR2 bit definitions ***********************/
|
||||
#define RDATDLY(value) ((value) & 0x3) /* Bits 0:1 */
|
||||
#define RFIG BIT(2)
|
||||
#define RCOMPAND(value) (((value) & 0x3) << 3) /* Bits 3:4 */
|
||||
#define RWDLEN2(value) (((value) & 0x7) << 5) /* Bits 5:7 */
|
||||
#define RFRLEN2(value) (((value) & 0x7f) << 8) /* Bits 8:14 */
|
||||
#define RPHASE BIT(15)
|
||||
|
||||
/*************************** McBSP XCR2 bit definitions ***********************/
|
||||
#define XDATDLY(value) ((value) & 0x3) /* Bits 0:1 */
|
||||
#define XFIG BIT(2)
|
||||
#define XCOMPAND(value) (((value) & 0x3) << 3) /* Bits 3:4 */
|
||||
#define XWDLEN2(value) (((value) & 0x7) << 5) /* Bits 5:7 */
|
||||
#define XFRLEN2(value) (((value) & 0x7f) << 8) /* Bits 8:14 */
|
||||
#define XPHASE BIT(15)
|
||||
|
||||
/************************* McBSP SRGR1 bit definitions ************************/
|
||||
#define CLKGDV(value) ((value) & 0x7f) /* Bits 0:7 */
|
||||
#define FWID(value) (((value) & 0xff) << 8) /* Bits 8:15 */
|
||||
|
||||
/************************* McBSP SRGR2 bit definitions ************************/
|
||||
#define FPER(value) ((value) & 0x0fff) /* Bits 0:11 */
|
||||
#define FSGM BIT(12)
|
||||
#define CLKSM BIT(13)
|
||||
#define CLKSP BIT(14)
|
||||
#define GSYNC BIT(15)
|
||||
|
||||
/************************* McBSP MCR1 bit definitions *************************/
|
||||
#define RMCM BIT(0)
|
||||
#define RCBLK(value) (((value) & 0x7) << 2) /* Bits 2:4 */
|
||||
#define RPABLK(value) (((value) & 0x3) << 5) /* Bits 5:6 */
|
||||
#define RPBBLK(value) (((value) & 0x3) << 7) /* Bits 7:8 */
|
||||
|
||||
/************************* McBSP MCR2 bit definitions *************************/
|
||||
#define XMCM(value) ((value) & 0x3) /* Bits 0:1 */
|
||||
#define XCBLK(value) (((value) & 0x7) << 2) /* Bits 2:4 */
|
||||
#define XPABLK(value) (((value) & 0x3) << 5) /* Bits 5:6 */
|
||||
#define XPBBLK(value) (((value) & 0x3) << 7) /* Bits 7:8 */
|
||||
|
||||
/*********************** McBSP XCCR bit definitions *************************/
|
||||
#define XDISABLE BIT(0)
|
||||
#define XDMAEN BIT(3)
|
||||
#define DILB BIT(5)
|
||||
#define XFULL_CYCLE BIT(11)
|
||||
#define DXENDLY(value) (((value) & 0x3) << 12) /* Bits 12:13 */
|
||||
#define PPCONNECT BIT(14)
|
||||
#define EXTCLKGATE BIT(15)
|
||||
|
||||
/********************** McBSP RCCR bit definitions *************************/
|
||||
#define RDISABLE BIT(0)
|
||||
#define RDMAEN BIT(3)
|
||||
#define RFULL_CYCLE BIT(11)
|
||||
|
||||
/********************** McBSP SYSCONFIG bit definitions ********************/
|
||||
#define SOFTRST BIT(1)
|
||||
#define ENAWAKEUP BIT(2)
|
||||
#define SIDLEMODE(value) (((value) & 0x3) << 3)
|
||||
#define CLOCKACTIVITY(value) (((value) & 0x3) << 8)
|
||||
|
||||
/********************** McBSP SSELCR bit definitions ***********************/
|
||||
#define SIDETONEEN BIT(10)
|
||||
|
||||
/********************** McBSP Sidetone SYSCONFIG bit definitions ***********/
|
||||
#define ST_AUTOIDLE BIT(0)
|
||||
|
||||
/********************** McBSP Sidetone SGAINCR bit definitions *************/
|
||||
#define ST_CH0GAIN(value) ((value) & 0xffff) /* Bits 0:15 */
|
||||
#define ST_CH1GAIN(value) (((value) & 0xffff) << 16) /* Bits 16:31 */
|
||||
|
||||
/********************** McBSP Sidetone SFIRCR bit definitions **************/
|
||||
#define ST_FIRCOEFF(value) ((value) & 0xffff) /* Bits 0:15 */
|
||||
|
||||
/********************** McBSP Sidetone SSELCR bit definitions **************/
|
||||
#define ST_SIDETONEEN BIT(0)
|
||||
#define ST_COEFFWREN BIT(1)
|
||||
#define ST_COEFFWRDONE BIT(2)
|
||||
|
||||
/********************** McBSP DMA operating modes **************************/
|
||||
#define MCBSP_DMA_MODE_ELEMENT 0
|
||||
#define MCBSP_DMA_MODE_THRESHOLD 1
|
||||
|
||||
/********************** McBSP WAKEUPEN/IRQST/IRQEN bit definitions *********/
|
||||
#define RSYNCERREN BIT(0)
|
||||
#define RFSREN BIT(1)
|
||||
#define REOFEN BIT(2)
|
||||
#define RRDYEN BIT(3)
|
||||
#define RUNDFLEN BIT(4)
|
||||
#define ROVFLEN BIT(5)
|
||||
#define XSYNCERREN BIT(7)
|
||||
#define XFSXEN BIT(8)
|
||||
#define XEOFEN BIT(9)
|
||||
#define XRDYEN BIT(10)
|
||||
#define XUNDFLEN BIT(11)
|
||||
#define XOVFLEN BIT(12)
|
||||
#define XEMPTYEOFEN BIT(14)
|
||||
|
||||
/* Clock signal muxing options */
|
||||
#define CLKR_SRC_CLKR 0 /* CLKR signal is from the CLKR pin */
|
||||
#define CLKR_SRC_CLKX 1 /* CLKR signal is from the CLKX pin */
|
||||
#define FSR_SRC_FSR 2 /* FSR signal is from the FSR pin */
|
||||
#define FSR_SRC_FSX 3 /* FSR signal is from the FSX pin */
|
||||
|
||||
/* McBSP functional clock sources */
|
||||
#define MCBSP_CLKS_PRCM_SRC 0
|
||||
#define MCBSP_CLKS_PAD_SRC 1
|
||||
|
||||
/* we don't do multichannel for now */
|
||||
struct omap_mcbsp_reg_cfg {
|
||||
u16 spcr2;
|
||||
u16 spcr1;
|
||||
u16 rcr2;
|
||||
u16 rcr1;
|
||||
u16 xcr2;
|
||||
u16 xcr1;
|
||||
u16 srgr2;
|
||||
u16 srgr1;
|
||||
u16 mcr2;
|
||||
u16 mcr1;
|
||||
u16 pcr0;
|
||||
u16 rcerc;
|
||||
u16 rcerd;
|
||||
u16 xcerc;
|
||||
u16 xcerd;
|
||||
u16 rcere;
|
||||
u16 rcerf;
|
||||
u16 xcere;
|
||||
u16 xcerf;
|
||||
u16 rcerg;
|
||||
u16 rcerh;
|
||||
u16 xcerg;
|
||||
u16 xcerh;
|
||||
u16 xccr;
|
||||
u16 rccr;
|
||||
};
|
||||
|
||||
struct omap_mcbsp_st_data {
|
||||
void __iomem *io_base_st;
|
||||
bool running;
|
||||
bool enabled;
|
||||
s16 taps[128]; /* Sidetone filter coefficients */
|
||||
int nr_taps; /* Number of filter coefficients in use */
|
||||
s16 ch0gain;
|
||||
s16 ch1gain;
|
||||
};
|
||||
|
||||
struct omap_mcbsp {
|
||||
struct device *dev;
|
||||
struct clk *fclk;
|
||||
spinlock_t lock;
|
||||
unsigned long phys_base;
|
||||
unsigned long phys_dma_base;
|
||||
void __iomem *io_base;
|
||||
u8 id;
|
||||
/*
|
||||
* Flags indicating is the bus already activated and configured by
|
||||
* another substream
|
||||
*/
|
||||
int active;
|
||||
int configured;
|
||||
u8 free;
|
||||
|
||||
int irq;
|
||||
int rx_irq;
|
||||
int tx_irq;
|
||||
|
||||
/* Protect the field .free, while checking if the mcbsp is in use */
|
||||
struct omap_mcbsp_platform_data *pdata;
|
||||
struct omap_mcbsp_st_data *st_data;
|
||||
struct omap_mcbsp_reg_cfg cfg_regs;
|
||||
struct snd_dmaengine_dai_dma_data dma_data[2];
|
||||
unsigned int dma_req[2];
|
||||
int dma_op_mode;
|
||||
u16 max_tx_thres;
|
||||
u16 max_rx_thres;
|
||||
void *reg_cache;
|
||||
int reg_cache_size;
|
||||
|
||||
unsigned int fmt;
|
||||
unsigned int in_freq;
|
||||
int clk_div;
|
||||
int wlen;
|
||||
};
|
||||
|
||||
void omap_mcbsp_config(struct omap_mcbsp *mcbsp,
|
||||
const struct omap_mcbsp_reg_cfg *config);
|
||||
void omap_mcbsp_set_tx_threshold(struct omap_mcbsp *mcbsp, u16 threshold);
|
||||
void omap_mcbsp_set_rx_threshold(struct omap_mcbsp *mcbsp, u16 threshold);
|
||||
u16 omap_mcbsp_get_tx_delay(struct omap_mcbsp *mcbsp);
|
||||
u16 omap_mcbsp_get_rx_delay(struct omap_mcbsp *mcbsp);
|
||||
int omap_mcbsp_get_dma_op_mode(struct omap_mcbsp *mcbsp);
|
||||
int omap_mcbsp_request(struct omap_mcbsp *mcbsp);
|
||||
void omap_mcbsp_free(struct omap_mcbsp *mcbsp);
|
||||
void omap_mcbsp_start(struct omap_mcbsp *mcbsp, int tx, int rx);
|
||||
void omap_mcbsp_stop(struct omap_mcbsp *mcbsp, int tx, int rx);
|
||||
|
||||
/* McBSP functional clock source changing function */
|
||||
int omap2_mcbsp_set_clks_src(struct omap_mcbsp *mcbsp, u8 fck_src_id);
|
||||
|
||||
/* Sidetone specific API */
|
||||
int omap_st_set_chgain(struct omap_mcbsp *mcbsp, int channel, s16 chgain);
|
||||
int omap_st_get_chgain(struct omap_mcbsp *mcbsp, int channel, s16 *chgain);
|
||||
int omap_st_enable(struct omap_mcbsp *mcbsp);
|
||||
int omap_st_disable(struct omap_mcbsp *mcbsp);
|
||||
int omap_st_is_enabled(struct omap_mcbsp *mcbsp);
|
||||
|
||||
int omap_mcbsp_init(struct platform_device *pdev);
|
||||
void omap_mcbsp_sysfs_remove(struct omap_mcbsp *mcbsp);
|
||||
|
||||
#endif /* __ASOC_MCBSP_H */
|
391
sound/soc/omap/n810.c
Normal file
391
sound/soc/omap/n810.c
Normal file
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
* n810.c -- SoC audio for Nokia N810
|
||||
*
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/asoc-ti-mcbsp.h>
|
||||
|
||||
#include "omap-mcbsp.h"
|
||||
|
||||
#define N810_HEADSET_AMP_GPIO 10
|
||||
#define N810_SPEAKER_AMP_GPIO 101
|
||||
|
||||
enum {
|
||||
N810_JACK_DISABLED,
|
||||
N810_JACK_HP,
|
||||
N810_JACK_HS,
|
||||
N810_JACK_MIC,
|
||||
};
|
||||
|
||||
static struct clk *sys_clkout2;
|
||||
static struct clk *sys_clkout2_src;
|
||||
static struct clk *func96m_clk;
|
||||
|
||||
static int n810_spk_func;
|
||||
static int n810_jack_func;
|
||||
static int n810_dmic_func;
|
||||
|
||||
static void n810_ext_control(struct snd_soc_dapm_context *dapm)
|
||||
{
|
||||
int hp = 0, line1l = 0;
|
||||
|
||||
switch (n810_jack_func) {
|
||||
case N810_JACK_HS:
|
||||
line1l = 1;
|
||||
case N810_JACK_HP:
|
||||
hp = 1;
|
||||
break;
|
||||
case N810_JACK_MIC:
|
||||
line1l = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
snd_soc_dapm_mutex_lock(dapm);
|
||||
|
||||
if (n810_spk_func)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk");
|
||||
|
||||
if (hp)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
|
||||
if (line1l)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "LINE1L");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "LINE1L");
|
||||
|
||||
if (n810_dmic_func)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "DMic");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "DMic");
|
||||
|
||||
snd_soc_dapm_sync_unlocked(dapm);
|
||||
|
||||
snd_soc_dapm_mutex_unlock(dapm);
|
||||
}
|
||||
|
||||
static int n810_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
|
||||
snd_pcm_hw_constraint_minmax(runtime,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
|
||||
|
||||
n810_ext_control(&codec->dapm);
|
||||
return clk_prepare_enable(sys_clkout2);
|
||||
}
|
||||
|
||||
static void n810_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
clk_disable_unprepare(sys_clkout2);
|
||||
}
|
||||
|
||||
static int n810_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int err;
|
||||
|
||||
/* Set the codec system clock for DAC and ADC */
|
||||
err = snd_soc_dai_set_sysclk(codec_dai, 0, 12000000,
|
||||
SND_SOC_CLOCK_IN);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops n810_ops = {
|
||||
.startup = n810_startup,
|
||||
.hw_params = n810_hw_params,
|
||||
.shutdown = n810_shutdown,
|
||||
};
|
||||
|
||||
static int n810_get_spk(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.integer.value[0] = n810_spk_func;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int n810_set_spk(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (n810_spk_func == ucontrol->value.integer.value[0])
|
||||
return 0;
|
||||
|
||||
n810_spk_func = ucontrol->value.integer.value[0];
|
||||
n810_ext_control(&card->dapm);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int n810_get_jack(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.integer.value[0] = n810_jack_func;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int n810_set_jack(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (n810_jack_func == ucontrol->value.integer.value[0])
|
||||
return 0;
|
||||
|
||||
n810_jack_func = ucontrol->value.integer.value[0];
|
||||
n810_ext_control(&card->dapm);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int n810_get_input(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.integer.value[0] = n810_dmic_func;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int n810_set_input(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (n810_dmic_func == ucontrol->value.integer.value[0])
|
||||
return 0;
|
||||
|
||||
n810_dmic_func = ucontrol->value.integer.value[0];
|
||||
n810_ext_control(&card->dapm);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int n810_spk_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
if (SND_SOC_DAPM_EVENT_ON(event))
|
||||
gpio_set_value(N810_SPEAKER_AMP_GPIO, 1);
|
||||
else
|
||||
gpio_set_value(N810_SPEAKER_AMP_GPIO, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int n810_jack_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
if (SND_SOC_DAPM_EVENT_ON(event))
|
||||
gpio_set_value(N810_HEADSET_AMP_GPIO, 1);
|
||||
else
|
||||
gpio_set_value(N810_HEADSET_AMP_GPIO, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_SPK("Ext Spk", n810_spk_event),
|
||||
SND_SOC_DAPM_HP("Headphone Jack", n810_jack_event),
|
||||
SND_SOC_DAPM_MIC("DMic", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{"Headphone Jack", NULL, "HPLOUT"},
|
||||
{"Headphone Jack", NULL, "HPROUT"},
|
||||
|
||||
{"Ext Spk", NULL, "LLOUT"},
|
||||
{"Ext Spk", NULL, "RLOUT"},
|
||||
|
||||
{"DMic Rate 64", NULL, "Mic Bias"},
|
||||
{"Mic Bias", NULL, "DMic"},
|
||||
};
|
||||
|
||||
static const char *spk_function[] = {"Off", "On"};
|
||||
static const char *jack_function[] = {"Off", "Headphone", "Headset", "Mic"};
|
||||
static const char *input_function[] = {"ADC", "Digital Mic"};
|
||||
static const struct soc_enum n810_enum[] = {
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function),
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new aic33_n810_controls[] = {
|
||||
SOC_ENUM_EXT("Speaker Function", n810_enum[0],
|
||||
n810_get_spk, n810_set_spk),
|
||||
SOC_ENUM_EXT("Jack Function", n810_enum[1],
|
||||
n810_get_jack, n810_set_jack),
|
||||
SOC_ENUM_EXT("Input Select", n810_enum[2],
|
||||
n810_get_input, n810_set_input),
|
||||
};
|
||||
|
||||
static int n810_aic33_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
|
||||
/* Not connected */
|
||||
snd_soc_dapm_nc_pin(dapm, "MONO_LOUT");
|
||||
snd_soc_dapm_nc_pin(dapm, "HPLCOM");
|
||||
snd_soc_dapm_nc_pin(dapm, "HPRCOM");
|
||||
snd_soc_dapm_nc_pin(dapm, "MIC3L");
|
||||
snd_soc_dapm_nc_pin(dapm, "MIC3R");
|
||||
snd_soc_dapm_nc_pin(dapm, "LINE1R");
|
||||
snd_soc_dapm_nc_pin(dapm, "LINE2L");
|
||||
snd_soc_dapm_nc_pin(dapm, "LINE2R");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link n810_dai = {
|
||||
.name = "TLV320AIC33",
|
||||
.stream_name = "AIC33",
|
||||
.cpu_dai_name = "omap-mcbsp.2",
|
||||
.platform_name = "omap-mcbsp.2",
|
||||
.codec_name = "tlv320aic3x-codec.2-0018",
|
||||
.codec_dai_name = "tlv320aic3x-hifi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM,
|
||||
.init = n810_aic33_init,
|
||||
.ops = &n810_ops,
|
||||
};
|
||||
|
||||
/* Audio machine driver */
|
||||
static struct snd_soc_card snd_soc_n810 = {
|
||||
.name = "N810",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &n810_dai,
|
||||
.num_links = 1,
|
||||
|
||||
.controls = aic33_n810_controls,
|
||||
.num_controls = ARRAY_SIZE(aic33_n810_controls),
|
||||
.dapm_widgets = aic33_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(aic33_dapm_widgets),
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
};
|
||||
|
||||
static struct platform_device *n810_snd_device;
|
||||
|
||||
static int __init n810_soc_init(void)
|
||||
{
|
||||
int err;
|
||||
struct device *dev;
|
||||
|
||||
if (!of_have_populated_dt() ||
|
||||
(!of_machine_is_compatible("nokia,n810") &&
|
||||
!of_machine_is_compatible("nokia,n810-wimax")))
|
||||
return -ENODEV;
|
||||
|
||||
n810_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!n810_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(n810_snd_device, &snd_soc_n810);
|
||||
err = platform_device_add(n810_snd_device);
|
||||
if (err)
|
||||
goto err1;
|
||||
|
||||
dev = &n810_snd_device->dev;
|
||||
|
||||
sys_clkout2_src = clk_get(dev, "sys_clkout2_src");
|
||||
if (IS_ERR(sys_clkout2_src)) {
|
||||
dev_err(dev, "Could not get sys_clkout2_src clock\n");
|
||||
err = PTR_ERR(sys_clkout2_src);
|
||||
goto err2;
|
||||
}
|
||||
sys_clkout2 = clk_get(dev, "sys_clkout2");
|
||||
if (IS_ERR(sys_clkout2)) {
|
||||
dev_err(dev, "Could not get sys_clkout2\n");
|
||||
err = PTR_ERR(sys_clkout2);
|
||||
goto err3;
|
||||
}
|
||||
/*
|
||||
* Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use
|
||||
* 96 MHz as its parent in order to get 12 MHz
|
||||
*/
|
||||
func96m_clk = clk_get(dev, "func_96m_ck");
|
||||
if (IS_ERR(func96m_clk)) {
|
||||
dev_err(dev, "Could not get func 96M clock\n");
|
||||
err = PTR_ERR(func96m_clk);
|
||||
goto err4;
|
||||
}
|
||||
clk_set_parent(sys_clkout2_src, func96m_clk);
|
||||
clk_set_rate(sys_clkout2, 12000000);
|
||||
|
||||
if (WARN_ON((gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) ||
|
||||
(gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0))) {
|
||||
err = -EINVAL;
|
||||
goto err4;
|
||||
}
|
||||
|
||||
gpio_direction_output(N810_HEADSET_AMP_GPIO, 0);
|
||||
gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0);
|
||||
|
||||
return 0;
|
||||
err4:
|
||||
clk_put(sys_clkout2);
|
||||
err3:
|
||||
clk_put(sys_clkout2_src);
|
||||
err2:
|
||||
platform_device_del(n810_snd_device);
|
||||
err1:
|
||||
platform_device_put(n810_snd_device);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit n810_soc_exit(void)
|
||||
{
|
||||
gpio_free(N810_SPEAKER_AMP_GPIO);
|
||||
gpio_free(N810_HEADSET_AMP_GPIO);
|
||||
clk_put(sys_clkout2_src);
|
||||
clk_put(sys_clkout2);
|
||||
clk_put(func96m_clk);
|
||||
|
||||
platform_device_unregister(n810_snd_device);
|
||||
}
|
||||
|
||||
module_init(n810_soc_init);
|
||||
module_exit(n810_soc_exit);
|
||||
|
||||
MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@bitmer.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC Nokia N810");
|
||||
MODULE_LICENSE("GPL");
|
370
sound/soc/omap/omap-abe-twl6040.c
Normal file
370
sound/soc/omap/omap-abe-twl6040.c
Normal file
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* omap-abe-twl6040.c -- SoC audio for TI OMAP based boards with ABE and
|
||||
* twl6040 codec
|
||||
*
|
||||
* Author: Misael Lopez Cruz <misael.lopez@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/twl6040.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
#include "omap-dmic.h"
|
||||
#include "omap-mcpdm.h"
|
||||
#include "../codecs/twl6040.h"
|
||||
|
||||
struct abe_twl6040 {
|
||||
int jack_detection; /* board can detect jack events */
|
||||
int mclk_freq; /* MCLK frequency speed for twl6040 */
|
||||
|
||||
struct platform_device *dmic_codec_dev;
|
||||
};
|
||||
|
||||
static int omap_abe_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card);
|
||||
int clk_id, freq;
|
||||
int ret;
|
||||
|
||||
clk_id = twl6040_get_clk_id(rtd->codec);
|
||||
if (clk_id == TWL6040_SYSCLK_SEL_HPPLL)
|
||||
freq = priv->mclk_freq;
|
||||
else if (clk_id == TWL6040_SYSCLK_SEL_LPPLL)
|
||||
freq = 32768;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
/* set the codec mclk */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, freq,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "can't set codec system clock\n");
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops omap_abe_ops = {
|
||||
.hw_params = omap_abe_hw_params,
|
||||
};
|
||||
|
||||
static int omap_abe_dmic_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
int ret = 0;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_DMIC_SYSCLK_PAD_CLKS,
|
||||
19200000, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "can't set DMIC cpu system clock\n");
|
||||
return ret;
|
||||
}
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_DMIC_ABE_DMIC_CLK, 2400000,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "can't set DMIC output clock\n");
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops omap_abe_dmic_ops = {
|
||||
.hw_params = omap_abe_dmic_hw_params,
|
||||
};
|
||||
|
||||
/* Headset jack */
|
||||
static struct snd_soc_jack hs_jack;
|
||||
|
||||
/*Headset jack detection DAPM pins */
|
||||
static struct snd_soc_jack_pin hs_jack_pins[] = {
|
||||
{
|
||||
.pin = "Headset Mic",
|
||||
.mask = SND_JACK_MICROPHONE,
|
||||
},
|
||||
{
|
||||
.pin = "Headset Stereophone",
|
||||
.mask = SND_JACK_HEADPHONE,
|
||||
},
|
||||
};
|
||||
|
||||
/* SDP4430 machine DAPM */
|
||||
static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
|
||||
/* Outputs */
|
||||
SND_SOC_DAPM_HP("Headset Stereophone", NULL),
|
||||
SND_SOC_DAPM_SPK("Earphone Spk", NULL),
|
||||
SND_SOC_DAPM_SPK("Ext Spk", NULL),
|
||||
SND_SOC_DAPM_LINE("Line Out", NULL),
|
||||
SND_SOC_DAPM_SPK("Vibrator", NULL),
|
||||
|
||||
/* Inputs */
|
||||
SND_SOC_DAPM_MIC("Headset Mic", NULL),
|
||||
SND_SOC_DAPM_MIC("Main Handset Mic", NULL),
|
||||
SND_SOC_DAPM_MIC("Sub Handset Mic", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
|
||||
/* Digital microphones */
|
||||
SND_SOC_DAPM_MIC("Digital Mic", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
/* Routings for outputs */
|
||||
{"Headset Stereophone", NULL, "HSOL"},
|
||||
{"Headset Stereophone", NULL, "HSOR"},
|
||||
|
||||
{"Earphone Spk", NULL, "EP"},
|
||||
|
||||
{"Ext Spk", NULL, "HFL"},
|
||||
{"Ext Spk", NULL, "HFR"},
|
||||
|
||||
{"Line Out", NULL, "AUXL"},
|
||||
{"Line Out", NULL, "AUXR"},
|
||||
|
||||
{"Vibrator", NULL, "VIBRAL"},
|
||||
{"Vibrator", NULL, "VIBRAR"},
|
||||
|
||||
/* Routings for inputs */
|
||||
{"HSMIC", NULL, "Headset Mic"},
|
||||
{"Headset Mic", NULL, "Headset Mic Bias"},
|
||||
|
||||
{"MAINMIC", NULL, "Main Handset Mic"},
|
||||
{"Main Handset Mic", NULL, "Main Mic Bias"},
|
||||
|
||||
{"SUBMIC", NULL, "Sub Handset Mic"},
|
||||
{"Sub Handset Mic", NULL, "Main Mic Bias"},
|
||||
|
||||
{"AFML", NULL, "Line In"},
|
||||
{"AFMR", NULL, "Line In"},
|
||||
};
|
||||
|
||||
static int omap_abe_twl6040_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card);
|
||||
int hs_trim;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Configure McPDM offset cancellation based on the HSOTRIM value from
|
||||
* twl6040.
|
||||
*/
|
||||
hs_trim = twl6040_get_trim_value(codec, TWL6040_TRIM_HSOTRIM);
|
||||
omap_mcpdm_configure_dn_offsets(rtd, TWL6040_HSF_TRIM_LEFT(hs_trim),
|
||||
TWL6040_HSF_TRIM_RIGHT(hs_trim));
|
||||
|
||||
/* Headset jack detection only if it is supported */
|
||||
if (priv->jack_detection) {
|
||||
ret = snd_soc_jack_new(codec, "Headset Jack",
|
||||
SND_JACK_HEADSET, &hs_jack);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
|
||||
hs_jack_pins);
|
||||
twl6040_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADSET);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_route dmic_audio_map[] = {
|
||||
{"DMic", NULL, "Digital Mic"},
|
||||
{"Digital Mic", NULL, "Digital Mic1 Bias"},
|
||||
};
|
||||
|
||||
static int omap_abe_dmic_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dapm_context *dapm = &rtd->card->dapm;
|
||||
|
||||
return snd_soc_dapm_add_routes(dapm, dmic_audio_map,
|
||||
ARRAY_SIZE(dmic_audio_map));
|
||||
}
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link abe_twl6040_dai_links[] = {
|
||||
{
|
||||
.name = "TWL6040",
|
||||
.stream_name = "TWL6040",
|
||||
.codec_dai_name = "twl6040-legacy",
|
||||
.codec_name = "twl6040-codec",
|
||||
.init = omap_abe_twl6040_init,
|
||||
.ops = &omap_abe_ops,
|
||||
},
|
||||
{
|
||||
.name = "DMIC",
|
||||
.stream_name = "DMIC Capture",
|
||||
.codec_dai_name = "dmic-hifi",
|
||||
.codec_name = "dmic-codec",
|
||||
.init = omap_abe_dmic_init,
|
||||
.ops = &omap_abe_dmic_ops,
|
||||
},
|
||||
};
|
||||
|
||||
/* Audio machine driver */
|
||||
static struct snd_soc_card omap_abe_card = {
|
||||
.owner = THIS_MODULE,
|
||||
|
||||
.dapm_widgets = twl6040_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(twl6040_dapm_widgets),
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
};
|
||||
|
||||
static int omap_abe_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct snd_soc_card *card = &omap_abe_card;
|
||||
struct device_node *dai_node;
|
||||
struct abe_twl6040 *priv;
|
||||
int num_links = 0;
|
||||
int ret = 0;
|
||||
|
||||
if (!node) {
|
||||
dev_err(&pdev->dev, "of node is missing.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(struct abe_twl6040), GFP_KERNEL);
|
||||
if (priv == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->dmic_codec_dev = ERR_PTR(-EINVAL);
|
||||
|
||||
if (snd_soc_of_parse_card_name(card, "ti,model")) {
|
||||
dev_err(&pdev->dev, "Card name is not provided\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Error while parsing DAPM routing\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
dai_node = of_parse_phandle(node, "ti,mcpdm", 0);
|
||||
if (!dai_node) {
|
||||
dev_err(&pdev->dev, "McPDM node is not provided\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
abe_twl6040_dai_links[0].cpu_of_node = dai_node;
|
||||
abe_twl6040_dai_links[0].platform_of_node = dai_node;
|
||||
|
||||
dai_node = of_parse_phandle(node, "ti,dmic", 0);
|
||||
if (dai_node) {
|
||||
num_links = 2;
|
||||
abe_twl6040_dai_links[1].cpu_of_node = dai_node;
|
||||
abe_twl6040_dai_links[1].platform_of_node = dai_node;
|
||||
|
||||
priv->dmic_codec_dev = platform_device_register_simple(
|
||||
"dmic-codec", -1, NULL, 0);
|
||||
if (IS_ERR(priv->dmic_codec_dev)) {
|
||||
dev_err(&pdev->dev, "Can't instantiate dmic-codec\n");
|
||||
return PTR_ERR(priv->dmic_codec_dev);
|
||||
}
|
||||
} else {
|
||||
num_links = 1;
|
||||
}
|
||||
|
||||
priv->jack_detection = of_property_read_bool(node, "ti,jack-detection");
|
||||
of_property_read_u32(node, "ti,mclk-freq", &priv->mclk_freq);
|
||||
if (!priv->mclk_freq) {
|
||||
dev_err(&pdev->dev, "MCLK frequency not provided\n");
|
||||
ret = -EINVAL;
|
||||
goto err_unregister;
|
||||
}
|
||||
|
||||
card->fully_routed = 1;
|
||||
|
||||
if (!priv->mclk_freq) {
|
||||
dev_err(&pdev->dev, "MCLK frequency missing\n");
|
||||
ret = -ENODEV;
|
||||
goto err_unregister;
|
||||
}
|
||||
|
||||
card->dai_link = abe_twl6040_dai_links;
|
||||
card->num_links = num_links;
|
||||
|
||||
snd_soc_card_set_drvdata(card, priv);
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
|
||||
ret);
|
||||
goto err_unregister;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
if (!IS_ERR(priv->dmic_codec_dev))
|
||||
platform_device_unregister(priv->dmic_codec_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap_abe_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
|
||||
if (!IS_ERR(priv->dmic_codec_dev))
|
||||
platform_device_unregister(priv->dmic_codec_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id omap_abe_of_match[] = {
|
||||
{.compatible = "ti,abe-twl6040", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_abe_of_match);
|
||||
|
||||
static struct platform_driver omap_abe_driver = {
|
||||
.driver = {
|
||||
.name = "omap-abe-twl6040",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = omap_abe_of_match,
|
||||
},
|
||||
.probe = omap_abe_probe,
|
||||
.remove = omap_abe_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(omap_abe_driver);
|
||||
|
||||
MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC for OMAP boards with ABE and twl6040 codec");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:omap-abe-twl6040");
|
523
sound/soc/omap/omap-dmic.c
Normal file
523
sound/soc/omap/omap-dmic.c
Normal file
|
@ -0,0 +1,523 @@
|
|||
/*
|
||||
* omap-dmic.c -- OMAP ASoC DMIC DAI driver
|
||||
*
|
||||
* Copyright (C) 2010 - 2011 Texas Instruments
|
||||
*
|
||||
* Author: David Lambert <dlambert@ti.com>
|
||||
* Misael Lopez Cruz <misael.lopez@ti.com>
|
||||
* Liam Girdwood <lrg@ti.com>
|
||||
* Peter Ujfalusi <peter.ujfalusi@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/omap-pcm.h>
|
||||
|
||||
#include "omap-dmic.h"
|
||||
|
||||
struct omap_dmic {
|
||||
struct device *dev;
|
||||
void __iomem *io_base;
|
||||
struct clk *fclk;
|
||||
int fclk_freq;
|
||||
int out_freq;
|
||||
int clk_div;
|
||||
int sysclk;
|
||||
int threshold;
|
||||
u32 ch_enabled;
|
||||
bool active;
|
||||
struct mutex mutex;
|
||||
|
||||
struct snd_dmaengine_dai_dma_data dma_data;
|
||||
};
|
||||
|
||||
static inline void omap_dmic_write(struct omap_dmic *dmic, u16 reg, u32 val)
|
||||
{
|
||||
writel_relaxed(val, dmic->io_base + reg);
|
||||
}
|
||||
|
||||
static inline int omap_dmic_read(struct omap_dmic *dmic, u16 reg)
|
||||
{
|
||||
return readl_relaxed(dmic->io_base + reg);
|
||||
}
|
||||
|
||||
static inline void omap_dmic_start(struct omap_dmic *dmic)
|
||||
{
|
||||
u32 ctrl = omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG);
|
||||
|
||||
/* Configure DMA controller */
|
||||
omap_dmic_write(dmic, OMAP_DMIC_DMAENABLE_SET_REG,
|
||||
OMAP_DMIC_DMA_ENABLE);
|
||||
|
||||
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, ctrl | dmic->ch_enabled);
|
||||
}
|
||||
|
||||
static inline void omap_dmic_stop(struct omap_dmic *dmic)
|
||||
{
|
||||
u32 ctrl = omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG);
|
||||
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG,
|
||||
ctrl & ~OMAP_DMIC_UP_ENABLE_MASK);
|
||||
|
||||
/* Disable DMA request generation */
|
||||
omap_dmic_write(dmic, OMAP_DMIC_DMAENABLE_CLR_REG,
|
||||
OMAP_DMIC_DMA_ENABLE);
|
||||
|
||||
}
|
||||
|
||||
static inline int dmic_is_enabled(struct omap_dmic *dmic)
|
||||
{
|
||||
return omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG) &
|
||||
OMAP_DMIC_UP_ENABLE_MASK;
|
||||
}
|
||||
|
||||
static int omap_dmic_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&dmic->mutex);
|
||||
|
||||
if (!dai->active)
|
||||
dmic->active = 1;
|
||||
else
|
||||
ret = -EBUSY;
|
||||
|
||||
mutex_unlock(&dmic->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void omap_dmic_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
mutex_lock(&dmic->mutex);
|
||||
|
||||
if (!dai->active)
|
||||
dmic->active = 0;
|
||||
|
||||
mutex_unlock(&dmic->mutex);
|
||||
}
|
||||
|
||||
static int omap_dmic_select_divider(struct omap_dmic *dmic, int sample_rate)
|
||||
{
|
||||
int divider = -EINVAL;
|
||||
|
||||
/*
|
||||
* 192KHz rate is only supported with 19.2MHz/3.84MHz clock
|
||||
* configuration.
|
||||
*/
|
||||
if (sample_rate == 192000) {
|
||||
if (dmic->fclk_freq == 19200000 && dmic->out_freq == 3840000)
|
||||
divider = 0x6; /* Divider: 5 (192KHz sampling rate) */
|
||||
else
|
||||
dev_err(dmic->dev,
|
||||
"invalid clock configuration for 192KHz\n");
|
||||
|
||||
return divider;
|
||||
}
|
||||
|
||||
switch (dmic->out_freq) {
|
||||
case 1536000:
|
||||
if (dmic->fclk_freq != 24576000)
|
||||
goto div_err;
|
||||
divider = 0x4; /* Divider: 16 */
|
||||
break;
|
||||
case 2400000:
|
||||
switch (dmic->fclk_freq) {
|
||||
case 12000000:
|
||||
divider = 0x5; /* Divider: 5 */
|
||||
break;
|
||||
case 19200000:
|
||||
divider = 0x0; /* Divider: 8 */
|
||||
break;
|
||||
case 24000000:
|
||||
divider = 0x2; /* Divider: 10 */
|
||||
break;
|
||||
default:
|
||||
goto div_err;
|
||||
}
|
||||
break;
|
||||
case 3072000:
|
||||
if (dmic->fclk_freq != 24576000)
|
||||
goto div_err;
|
||||
divider = 0x3; /* Divider: 8 */
|
||||
break;
|
||||
case 3840000:
|
||||
if (dmic->fclk_freq != 19200000)
|
||||
goto div_err;
|
||||
divider = 0x1; /* Divider: 5 (96KHz sampling rate) */
|
||||
break;
|
||||
default:
|
||||
dev_err(dmic->dev, "invalid out frequency: %dHz\n",
|
||||
dmic->out_freq);
|
||||
break;
|
||||
}
|
||||
|
||||
return divider;
|
||||
|
||||
div_err:
|
||||
dev_err(dmic->dev, "invalid out frequency %dHz for %dHz input\n",
|
||||
dmic->out_freq, dmic->fclk_freq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int omap_dmic_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
||||
struct snd_dmaengine_dai_dma_data *dma_data;
|
||||
int channels;
|
||||
|
||||
dmic->clk_div = omap_dmic_select_divider(dmic, params_rate(params));
|
||||
if (dmic->clk_div < 0) {
|
||||
dev_err(dmic->dev, "no valid divider for %dHz from %dHz\n",
|
||||
dmic->out_freq, dmic->fclk_freq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dmic->ch_enabled = 0;
|
||||
channels = params_channels(params);
|
||||
switch (channels) {
|
||||
case 6:
|
||||
dmic->ch_enabled |= OMAP_DMIC_UP3_ENABLE;
|
||||
case 4:
|
||||
dmic->ch_enabled |= OMAP_DMIC_UP2_ENABLE;
|
||||
case 2:
|
||||
dmic->ch_enabled |= OMAP_DMIC_UP1_ENABLE;
|
||||
break;
|
||||
default:
|
||||
dev_err(dmic->dev, "invalid number of legacy channels\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* packet size is threshold * channels */
|
||||
dma_data = snd_soc_dai_get_dma_data(dai, substream);
|
||||
dma_data->maxburst = dmic->threshold * channels;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_dmic_dai_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
||||
u32 ctrl;
|
||||
|
||||
/* Configure uplink threshold */
|
||||
omap_dmic_write(dmic, OMAP_DMIC_FIFO_CTRL_REG, dmic->threshold);
|
||||
|
||||
ctrl = omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG);
|
||||
|
||||
/* Set dmic out format */
|
||||
ctrl &= ~(OMAP_DMIC_FORMAT | OMAP_DMIC_POLAR_MASK);
|
||||
ctrl |= (OMAP_DMICOUTFORMAT_LJUST | OMAP_DMIC_POLAR1 |
|
||||
OMAP_DMIC_POLAR2 | OMAP_DMIC_POLAR3);
|
||||
|
||||
/* Configure dmic clock divider */
|
||||
ctrl &= ~OMAP_DMIC_CLK_DIV_MASK;
|
||||
ctrl |= OMAP_DMIC_CLK_DIV(dmic->clk_div);
|
||||
|
||||
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, ctrl);
|
||||
|
||||
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG,
|
||||
ctrl | OMAP_DMICOUTFORMAT_LJUST | OMAP_DMIC_POLAR1 |
|
||||
OMAP_DMIC_POLAR2 | OMAP_DMIC_POLAR3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_dmic_dai_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
omap_dmic_start(dmic);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
omap_dmic_stop(dmic);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_dmic_select_fclk(struct omap_dmic *dmic, int clk_id,
|
||||
unsigned int freq)
|
||||
{
|
||||
struct clk *parent_clk;
|
||||
char *parent_clk_name;
|
||||
int ret = 0;
|
||||
|
||||
switch (freq) {
|
||||
case 12000000:
|
||||
case 19200000:
|
||||
case 24000000:
|
||||
case 24576000:
|
||||
break;
|
||||
default:
|
||||
dev_err(dmic->dev, "invalid input frequency: %dHz\n", freq);
|
||||
dmic->fclk_freq = 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (dmic->sysclk == clk_id) {
|
||||
dmic->fclk_freq = freq;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* re-parent not allowed if a stream is ongoing */
|
||||
if (dmic->active && dmic_is_enabled(dmic)) {
|
||||
dev_err(dmic->dev, "can't re-parent when DMIC active\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
switch (clk_id) {
|
||||
case OMAP_DMIC_SYSCLK_PAD_CLKS:
|
||||
parent_clk_name = "pad_clks_ck";
|
||||
break;
|
||||
case OMAP_DMIC_SYSCLK_SLIMBLUS_CLKS:
|
||||
parent_clk_name = "slimbus_clk";
|
||||
break;
|
||||
case OMAP_DMIC_SYSCLK_SYNC_MUX_CLKS:
|
||||
parent_clk_name = "dmic_sync_mux_ck";
|
||||
break;
|
||||
default:
|
||||
dev_err(dmic->dev, "fclk clk_id (%d) not supported\n", clk_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
parent_clk = clk_get(dmic->dev, parent_clk_name);
|
||||
if (IS_ERR(parent_clk)) {
|
||||
dev_err(dmic->dev, "can't get %s\n", parent_clk_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mutex_lock(&dmic->mutex);
|
||||
if (dmic->active) {
|
||||
/* disable clock while reparenting */
|
||||
pm_runtime_put_sync(dmic->dev);
|
||||
ret = clk_set_parent(dmic->fclk, parent_clk);
|
||||
pm_runtime_get_sync(dmic->dev);
|
||||
} else {
|
||||
ret = clk_set_parent(dmic->fclk, parent_clk);
|
||||
}
|
||||
mutex_unlock(&dmic->mutex);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(dmic->dev, "re-parent failed\n");
|
||||
goto err_busy;
|
||||
}
|
||||
|
||||
dmic->sysclk = clk_id;
|
||||
dmic->fclk_freq = freq;
|
||||
|
||||
err_busy:
|
||||
clk_put(parent_clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap_dmic_select_outclk(struct omap_dmic *dmic, int clk_id,
|
||||
unsigned int freq)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (clk_id != OMAP_DMIC_ABE_DMIC_CLK) {
|
||||
dev_err(dmic->dev, "output clk_id (%d) not supported\n",
|
||||
clk_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (freq) {
|
||||
case 1536000:
|
||||
case 2400000:
|
||||
case 3072000:
|
||||
case 3840000:
|
||||
dmic->out_freq = freq;
|
||||
break;
|
||||
default:
|
||||
dev_err(dmic->dev, "invalid out frequency: %dHz\n", freq);
|
||||
dmic->out_freq = 0;
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap_dmic_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
|
||||
unsigned int freq, int dir)
|
||||
{
|
||||
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
if (dir == SND_SOC_CLOCK_IN)
|
||||
return omap_dmic_select_fclk(dmic, clk_id, freq);
|
||||
else if (dir == SND_SOC_CLOCK_OUT)
|
||||
return omap_dmic_select_outclk(dmic, clk_id, freq);
|
||||
|
||||
dev_err(dmic->dev, "invalid clock direction (%d)\n", dir);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops omap_dmic_dai_ops = {
|
||||
.startup = omap_dmic_dai_startup,
|
||||
.shutdown = omap_dmic_dai_shutdown,
|
||||
.hw_params = omap_dmic_dai_hw_params,
|
||||
.prepare = omap_dmic_dai_prepare,
|
||||
.trigger = omap_dmic_dai_trigger,
|
||||
.set_sysclk = omap_dmic_set_dai_sysclk,
|
||||
};
|
||||
|
||||
static int omap_dmic_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
pm_runtime_enable(dmic->dev);
|
||||
|
||||
/* Disable lines while request is ongoing */
|
||||
pm_runtime_get_sync(dmic->dev);
|
||||
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, 0x00);
|
||||
pm_runtime_put_sync(dmic->dev);
|
||||
|
||||
/* Configure DMIC threshold value */
|
||||
dmic->threshold = OMAP_DMIC_THRES_MAX - 3;
|
||||
|
||||
snd_soc_dai_init_dma_data(dai, NULL, &dmic->dma_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_dmic_remove(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
pm_runtime_disable(dmic->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_driver omap_dmic_dai = {
|
||||
.name = "omap-dmic",
|
||||
.probe = omap_dmic_probe,
|
||||
.remove = omap_dmic_remove,
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 6,
|
||||
.rates = SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE,
|
||||
.sig_bits = 24,
|
||||
},
|
||||
.ops = &omap_dmic_dai_ops,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver omap_dmic_component = {
|
||||
.name = "omap-dmic",
|
||||
};
|
||||
|
||||
static int asoc_dmic_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_dmic *dmic;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
dmic = devm_kzalloc(&pdev->dev, sizeof(struct omap_dmic), GFP_KERNEL);
|
||||
if (!dmic)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, dmic);
|
||||
dmic->dev = &pdev->dev;
|
||||
dmic->sysclk = OMAP_DMIC_SYSCLK_SYNC_MUX_CLKS;
|
||||
|
||||
mutex_init(&dmic->mutex);
|
||||
|
||||
dmic->fclk = devm_clk_get(dmic->dev, "fck");
|
||||
if (IS_ERR(dmic->fclk)) {
|
||||
dev_err(dmic->dev, "cant get fck\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma");
|
||||
if (!res) {
|
||||
dev_err(dmic->dev, "invalid dma memory resource\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
dmic->dma_data.addr = res->start + OMAP_DMIC_DATA_REG;
|
||||
|
||||
dmic->dma_data.filter_data = "up_link";
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu");
|
||||
dmic->io_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(dmic->io_base))
|
||||
return PTR_ERR(dmic->io_base);
|
||||
|
||||
|
||||
ret = devm_snd_soc_register_component(&pdev->dev,
|
||||
&omap_dmic_component,
|
||||
&omap_dmic_dai, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = omap_pcm_platform_register(&pdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id omap_dmic_of_match[] = {
|
||||
{ .compatible = "ti,omap4-dmic", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_dmic_of_match);
|
||||
|
||||
static struct platform_driver asoc_dmic_driver = {
|
||||
.driver = {
|
||||
.name = "omap-dmic",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = omap_dmic_of_match,
|
||||
},
|
||||
.probe = asoc_dmic_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(asoc_dmic_driver);
|
||||
|
||||
MODULE_ALIAS("platform:omap-dmic");
|
||||
MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
|
||||
MODULE_DESCRIPTION("OMAP DMIC ASoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
69
sound/soc/omap/omap-dmic.h
Normal file
69
sound/soc/omap/omap-dmic.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* omap-dmic.h -- OMAP Digital Microphone Controller
|
||||
*
|
||||
* 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 _OMAP_DMIC_H
|
||||
#define _OMAP_DMIC_H
|
||||
|
||||
#define OMAP_DMIC_REVISION_REG 0x00
|
||||
#define OMAP_DMIC_SYSCONFIG_REG 0x10
|
||||
#define OMAP_DMIC_IRQSTATUS_RAW_REG 0x24
|
||||
#define OMAP_DMIC_IRQSTATUS_REG 0x28
|
||||
#define OMAP_DMIC_IRQENABLE_SET_REG 0x2C
|
||||
#define OMAP_DMIC_IRQENABLE_CLR_REG 0x30
|
||||
#define OMAP_DMIC_IRQWAKE_EN_REG 0x34
|
||||
#define OMAP_DMIC_DMAENABLE_SET_REG 0x38
|
||||
#define OMAP_DMIC_DMAENABLE_CLR_REG 0x3C
|
||||
#define OMAP_DMIC_DMAWAKEEN_REG 0x40
|
||||
#define OMAP_DMIC_CTRL_REG 0x44
|
||||
#define OMAP_DMIC_DATA_REG 0x48
|
||||
#define OMAP_DMIC_FIFO_CTRL_REG 0x4C
|
||||
#define OMAP_DMIC_FIFO_DMIC1R_DATA_REG 0x50
|
||||
#define OMAP_DMIC_FIFO_DMIC1L_DATA_REG 0x54
|
||||
#define OMAP_DMIC_FIFO_DMIC2R_DATA_REG 0x58
|
||||
#define OMAP_DMIC_FIFO_DMIC2L_DATA_REG 0x5C
|
||||
#define OMAP_DMIC_FIFO_DMIC3R_DATA_REG 0x60
|
||||
#define OMAP_DMIC_FIFO_DMIC3L_DATA_REG 0x64
|
||||
|
||||
/* IRQSTATUS_RAW, IRQSTATUS, IRQENABLE_SET, IRQENABLE_CLR bit fields */
|
||||
#define OMAP_DMIC_IRQ (1 << 0)
|
||||
#define OMAP_DMIC_IRQ_FULL (1 << 1)
|
||||
#define OMAP_DMIC_IRQ_ALMST_EMPTY (1 << 2)
|
||||
#define OMAP_DMIC_IRQ_EMPTY (1 << 3)
|
||||
#define OMAP_DMIC_IRQ_MASK 0x07
|
||||
|
||||
/* DMIC_DMAENABLE bit fields */
|
||||
#define OMAP_DMIC_DMA_ENABLE 0x1
|
||||
|
||||
/* DMIC_CTRL bit fields */
|
||||
#define OMAP_DMIC_UP1_ENABLE (1 << 0)
|
||||
#define OMAP_DMIC_UP2_ENABLE (1 << 1)
|
||||
#define OMAP_DMIC_UP3_ENABLE (1 << 2)
|
||||
#define OMAP_DMIC_UP_ENABLE_MASK 0x7
|
||||
#define OMAP_DMIC_FORMAT (1 << 3)
|
||||
#define OMAP_DMIC_POLAR1 (1 << 4)
|
||||
#define OMAP_DMIC_POLAR2 (1 << 5)
|
||||
#define OMAP_DMIC_POLAR3 (1 << 6)
|
||||
#define OMAP_DMIC_POLAR_MASK (0x7 << 4)
|
||||
#define OMAP_DMIC_CLK_DIV(x) (((x) & 0x7) << 7)
|
||||
#define OMAP_DMIC_CLK_DIV_MASK (0x7 << 7)
|
||||
#define OMAP_DMIC_RESET (1 << 10)
|
||||
|
||||
#define OMAP_DMICOUTFORMAT_LJUST (0 << 3)
|
||||
#define OMAP_DMICOUTFORMAT_RJUST (1 << 3)
|
||||
|
||||
/* DMIC_FIFO_CTRL bit fields */
|
||||
#define OMAP_DMIC_THRES_MAX 0xF
|
||||
|
||||
enum omap_dmic_clk {
|
||||
OMAP_DMIC_SYSCLK_PAD_CLKS, /* PAD_CLKS */
|
||||
OMAP_DMIC_SYSCLK_SLIMBLUS_CLKS, /* SLIMBUS_CLK */
|
||||
OMAP_DMIC_SYSCLK_SYNC_MUX_CLKS, /* DMIC_SYNC_MUX_CLK */
|
||||
OMAP_DMIC_ABE_DMIC_CLK, /* abe_dmic_clk */
|
||||
};
|
||||
|
||||
#endif
|
87
sound/soc/omap/omap-hdmi-card.c
Normal file
87
sound/soc/omap/omap-hdmi-card.c
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* omap-hdmi-card.c
|
||||
*
|
||||
* OMAP ALSA SoC machine driver for TI OMAP HDMI
|
||||
* Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
|
||||
* Author: Ricardo Neri <ricardo.neri@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <asm/mach-types.h>
|
||||
#include <video/omapdss.h>
|
||||
|
||||
#define DRV_NAME "omap-hdmi-audio"
|
||||
|
||||
static struct snd_soc_dai_link omap_hdmi_dai = {
|
||||
.name = "HDMI",
|
||||
.stream_name = "HDMI",
|
||||
.cpu_dai_name = "omap-hdmi-audio-dai",
|
||||
.platform_name = "omap-hdmi-audio-dai",
|
||||
.codec_name = "hdmi-audio-codec",
|
||||
.codec_dai_name = "hdmi-hifi",
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_omap_hdmi = {
|
||||
.name = "OMAPHDMI",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &omap_hdmi_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static int omap_hdmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &snd_soc_omap_hdmi;
|
||||
int ret;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
card->dev = NULL;
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_hdmi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
card->dev = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver omap_hdmi_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = omap_hdmi_probe,
|
||||
.remove = omap_hdmi_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(omap_hdmi_driver);
|
||||
|
||||
MODULE_AUTHOR("Ricardo Neri <ricardo.neri@ti.com>");
|
||||
MODULE_DESCRIPTION("OMAP HDMI machine ASoC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
364
sound/soc/omap/omap-hdmi.c
Normal file
364
sound/soc/omap/omap-hdmi.c
Normal file
|
@ -0,0 +1,364 @@
|
|||
/*
|
||||
* omap-hdmi.c
|
||||
*
|
||||
* OMAP ALSA SoC DAI driver for HDMI audio on OMAP4 processors.
|
||||
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/
|
||||
* Authors: Jorge Candelaria <jorge.candelaria@ti.com>
|
||||
* Ricardo Neri <ricardo.neri@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/asound.h>
|
||||
#include <sound/asoundef.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <video/omapdss.h>
|
||||
#include <sound/omap-pcm.h>
|
||||
|
||||
#include "omap-hdmi.h"
|
||||
|
||||
#define DRV_NAME "omap-hdmi-audio-dai"
|
||||
|
||||
struct hdmi_priv {
|
||||
struct snd_dmaengine_dai_dma_data dma_data;
|
||||
unsigned int dma_req;
|
||||
struct omap_dss_audio dss_audio;
|
||||
struct snd_aes_iec958 iec;
|
||||
struct snd_cea_861_aud_if cea;
|
||||
struct omap_dss_device *dssdev;
|
||||
};
|
||||
|
||||
static int omap_hdmi_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct hdmi_priv *priv = snd_soc_dai_get_drvdata(dai);
|
||||
int err;
|
||||
/*
|
||||
* Make sure that the period bytes are multiple of the DMA packet size.
|
||||
* Largest packet size we use is 32 32-bit words = 128 bytes
|
||||
*/
|
||||
err = snd_pcm_hw_constraint_step(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
|
||||
if (err < 0) {
|
||||
dev_err(dai->dev, "could not apply constraint\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!priv->dssdev->driver->audio_supported(priv->dssdev)) {
|
||||
dev_err(dai->dev, "audio not supported\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
snd_soc_dai_set_dma_data(dai, substream, &priv->dma_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_hdmi_dai_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct hdmi_priv *priv = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
return priv->dssdev->driver->audio_enable(priv->dssdev);
|
||||
}
|
||||
|
||||
static int omap_hdmi_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct hdmi_priv *priv = snd_soc_dai_get_drvdata(dai);
|
||||
struct snd_aes_iec958 *iec = &priv->iec;
|
||||
struct snd_cea_861_aud_if *cea = &priv->cea;
|
||||
int err = 0;
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
priv->dma_data.maxburst = 16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
priv->dma_data.maxburst = 32;
|
||||
break;
|
||||
default:
|
||||
dev_err(dai->dev, "format not supported!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* fill the IEC-60958 channel status word
|
||||
*/
|
||||
/* initialize the word bytes */
|
||||
memset(iec->status, 0, sizeof(iec->status));
|
||||
|
||||
/* specify IEC-60958-3 (commercial use) */
|
||||
iec->status[0] &= ~IEC958_AES0_PROFESSIONAL;
|
||||
|
||||
/* specify that the audio is LPCM*/
|
||||
iec->status[0] &= ~IEC958_AES0_NONAUDIO;
|
||||
|
||||
iec->status[0] |= IEC958_AES0_CON_NOT_COPYRIGHT;
|
||||
|
||||
iec->status[0] |= IEC958_AES0_CON_EMPHASIS_NONE;
|
||||
|
||||
iec->status[0] |= IEC958_AES1_PRO_MODE_NOTID;
|
||||
|
||||
iec->status[1] = IEC958_AES1_CON_GENERAL;
|
||||
|
||||
iec->status[2] |= IEC958_AES2_CON_SOURCE_UNSPEC;
|
||||
|
||||
iec->status[2] |= IEC958_AES2_CON_CHANNEL_UNSPEC;
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 32000:
|
||||
iec->status[3] |= IEC958_AES3_CON_FS_32000;
|
||||
break;
|
||||
case 44100:
|
||||
iec->status[3] |= IEC958_AES3_CON_FS_44100;
|
||||
break;
|
||||
case 48000:
|
||||
iec->status[3] |= IEC958_AES3_CON_FS_48000;
|
||||
break;
|
||||
case 88200:
|
||||
iec->status[3] |= IEC958_AES3_CON_FS_88200;
|
||||
break;
|
||||
case 96000:
|
||||
iec->status[3] |= IEC958_AES3_CON_FS_96000;
|
||||
break;
|
||||
case 176400:
|
||||
iec->status[3] |= IEC958_AES3_CON_FS_176400;
|
||||
break;
|
||||
case 192000:
|
||||
iec->status[3] |= IEC958_AES3_CON_FS_192000;
|
||||
break;
|
||||
default:
|
||||
dev_err(dai->dev, "rate not supported!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* specify the clock accuracy */
|
||||
iec->status[3] |= IEC958_AES3_CON_CLOCK_1000PPM;
|
||||
|
||||
/*
|
||||
* specify the word length. The same word length value can mean
|
||||
* two different lengths. Hence, we need to specify the maximum
|
||||
* word length as well.
|
||||
*/
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
iec->status[4] |= IEC958_AES4_CON_WORDLEN_20_16;
|
||||
iec->status[4] &= ~IEC958_AES4_CON_MAX_WORDLEN_24;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
iec->status[4] |= IEC958_AES4_CON_WORDLEN_24_20;
|
||||
iec->status[4] |= IEC958_AES4_CON_MAX_WORDLEN_24;
|
||||
break;
|
||||
default:
|
||||
dev_err(dai->dev, "format not supported!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill the CEA-861 audio infoframe (see spec for details)
|
||||
*/
|
||||
|
||||
cea->db1_ct_cc = (params_channels(params) - 1)
|
||||
& CEA861_AUDIO_INFOFRAME_DB1CC;
|
||||
cea->db1_ct_cc |= CEA861_AUDIO_INFOFRAME_DB1CT_FROM_STREAM;
|
||||
|
||||
cea->db2_sf_ss = CEA861_AUDIO_INFOFRAME_DB2SF_FROM_STREAM;
|
||||
cea->db2_sf_ss |= CEA861_AUDIO_INFOFRAME_DB2SS_FROM_STREAM;
|
||||
|
||||
cea->db3 = 0; /* not used, all zeros */
|
||||
|
||||
/*
|
||||
* The OMAP HDMI IP requires to use the 8-channel channel code when
|
||||
* transmitting more than two channels.
|
||||
*/
|
||||
if (params_channels(params) == 2)
|
||||
cea->db4_ca = 0x0;
|
||||
else
|
||||
cea->db4_ca = 0x13;
|
||||
|
||||
cea->db5_dminh_lsv = CEA861_AUDIO_INFOFRAME_DB5_DM_INH_PROHIBITED;
|
||||
/* the expression is trivial but makes clear what we are doing */
|
||||
cea->db5_dminh_lsv |= (0 & CEA861_AUDIO_INFOFRAME_DB5_LSV);
|
||||
|
||||
priv->dss_audio.iec = iec;
|
||||
priv->dss_audio.cea = cea;
|
||||
|
||||
err = priv->dssdev->driver->audio_config(priv->dssdev,
|
||||
&priv->dss_audio);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int omap_hdmi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct hdmi_priv *priv = snd_soc_dai_get_drvdata(dai);
|
||||
int err = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
err = priv->dssdev->driver->audio_start(priv->dssdev);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
priv->dssdev->driver->audio_stop(priv->dssdev);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static void omap_hdmi_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct hdmi_priv *priv = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
priv->dssdev->driver->audio_disable(priv->dssdev);
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops omap_hdmi_dai_ops = {
|
||||
.startup = omap_hdmi_dai_startup,
|
||||
.hw_params = omap_hdmi_dai_hw_params,
|
||||
.prepare = omap_hdmi_dai_prepare,
|
||||
.trigger = omap_hdmi_dai_trigger,
|
||||
.shutdown = omap_hdmi_dai_shutdown,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver omap_hdmi_dai = {
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.rates = OMAP_HDMI_RATES,
|
||||
.formats = OMAP_HDMI_FORMATS,
|
||||
},
|
||||
.ops = &omap_hdmi_dai_ops,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver omap_hdmi_component = {
|
||||
.name = DRV_NAME,
|
||||
};
|
||||
|
||||
static int omap_hdmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct resource *hdmi_rsrc;
|
||||
struct hdmi_priv *hdmi_data;
|
||||
bool hdmi_dev_found = false;
|
||||
|
||||
hdmi_data = devm_kzalloc(&pdev->dev, sizeof(*hdmi_data), GFP_KERNEL);
|
||||
if (hdmi_data == NULL) {
|
||||
dev_err(&pdev->dev, "Cannot allocate memory for HDMI data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hdmi_rsrc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!hdmi_rsrc) {
|
||||
dev_err(&pdev->dev, "Cannot obtain IORESOURCE_MEM HDMI\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hdmi_data->dma_data.addr = hdmi_rsrc->start + OMAP_HDMI_AUDIO_DMA_PORT;
|
||||
|
||||
hdmi_rsrc = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
if (!hdmi_rsrc) {
|
||||
dev_err(&pdev->dev, "Cannot obtain IORESOURCE_DMA HDMI\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hdmi_data->dma_req = hdmi_rsrc->start;
|
||||
hdmi_data->dma_data.filter_data = &hdmi_data->dma_req;
|
||||
hdmi_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
|
||||
/*
|
||||
* TODO: We assume that there is only one DSS HDMI device. Future
|
||||
* OMAP implementations may support more than one HDMI devices and
|
||||
* we should provided separate audio support for all of them.
|
||||
*/
|
||||
/* Find an HDMI device. */
|
||||
for_each_dss_dev(hdmi_data->dssdev) {
|
||||
omap_dss_get_device(hdmi_data->dssdev);
|
||||
|
||||
if (!hdmi_data->dssdev->driver) {
|
||||
omap_dss_put_device(hdmi_data->dssdev);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hdmi_data->dssdev->type == OMAP_DISPLAY_TYPE_HDMI) {
|
||||
hdmi_dev_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hdmi_dev_found) {
|
||||
dev_err(&pdev->dev, "no driver for HDMI display found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&pdev->dev, hdmi_data);
|
||||
ret = snd_soc_register_component(&pdev->dev, &omap_hdmi_component,
|
||||
&omap_hdmi_dai, 1);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return omap_pcm_platform_register(&pdev->dev);
|
||||
}
|
||||
|
||||
static int omap_hdmi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hdmi_priv *hdmi_data = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
|
||||
if (hdmi_data == NULL) {
|
||||
dev_err(&pdev->dev, "cannot obtain HDMi data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
omap_dss_put_device(hdmi_data->dssdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver hdmi_dai_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = omap_hdmi_probe,
|
||||
.remove = omap_hdmi_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(hdmi_dai_driver);
|
||||
|
||||
MODULE_AUTHOR("Jorge Candelaria <jorge.candelaria@ti.com>");
|
||||
MODULE_AUTHOR("Ricardo Neri <ricardo.neri@ti.com>");
|
||||
MODULE_DESCRIPTION("OMAP HDMI SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
38
sound/soc/omap/omap-hdmi.h
Normal file
38
sound/soc/omap/omap-hdmi.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* omap-hdmi.h
|
||||
*
|
||||
* Definitions for OMAP ALSA SoC DAI driver for HDMI audio on OMAP4 processors.
|
||||
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/
|
||||
* Authors: Jorge Candelaria <jorge.candelaria@ti.com>
|
||||
* Ricardo Neri <ricardo.neri@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __OMAP_HDMI_H__
|
||||
#define __OMAP_HDMI_H__
|
||||
|
||||
#define OMAP_HDMI_AUDIO_DMA_PORT 0x8c
|
||||
|
||||
#define OMAP_HDMI_RATES (SNDRV_PCM_RATE_32000 | \
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
|
||||
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \
|
||||
SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
|
||||
|
||||
#define OMAP_HDMI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
#endif
|
847
sound/soc/omap/omap-mcbsp.c
Normal file
847
sound/soc/omap/omap-mcbsp.c
Normal file
|
@ -0,0 +1,847 @@
|
|||
/*
|
||||
* omap-mcbsp.c -- OMAP ALSA SoC DAI driver using McBSP port
|
||||
*
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
|
||||
* Peter Ujfalusi <peter.ujfalusi@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/omap-pcm.h>
|
||||
|
||||
#include <linux/platform_data/asoc-ti-mcbsp.h>
|
||||
#include "mcbsp.h"
|
||||
#include "omap-mcbsp.h"
|
||||
|
||||
#define OMAP_MCBSP_RATES (SNDRV_PCM_RATE_8000_96000)
|
||||
|
||||
#define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax, \
|
||||
xhandler_get, xhandler_put) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
.info = omap_mcbsp_st_info_volsw, \
|
||||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = (unsigned long) &(struct soc_mixer_control) \
|
||||
{.min = xmin, .max = xmax} }
|
||||
|
||||
enum {
|
||||
OMAP_MCBSP_WORD_8 = 0,
|
||||
OMAP_MCBSP_WORD_12,
|
||||
OMAP_MCBSP_WORD_16,
|
||||
OMAP_MCBSP_WORD_20,
|
||||
OMAP_MCBSP_WORD_24,
|
||||
OMAP_MCBSP_WORD_32,
|
||||
};
|
||||
|
||||
/*
|
||||
* Stream DMA parameters. DMA request line and port address are set runtime
|
||||
* since they are different between OMAP1 and later OMAPs
|
||||
*/
|
||||
static void omap_mcbsp_set_threshold(struct snd_pcm_substream *substream,
|
||||
unsigned int packet_size)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
int words;
|
||||
|
||||
/*
|
||||
* Configure McBSP threshold based on either:
|
||||
* packet_size, when the sDMA is in packet mode, or based on the
|
||||
* period size in THRESHOLD mode, otherwise use McBSP threshold = 1
|
||||
* for mono streams.
|
||||
*/
|
||||
if (packet_size)
|
||||
words = packet_size;
|
||||
else
|
||||
words = 1;
|
||||
|
||||
/* Configure McBSP internal buffer usage */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
omap_mcbsp_set_tx_threshold(mcbsp, words);
|
||||
else
|
||||
omap_mcbsp_set_rx_threshold(mcbsp, words);
|
||||
}
|
||||
|
||||
static int omap_mcbsp_hwrule_min_buffersize(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_interval *buffer_size = hw_param_interval(params,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_SIZE);
|
||||
struct snd_interval *channels = hw_param_interval(params,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
struct omap_mcbsp *mcbsp = rule->private;
|
||||
struct snd_interval frames;
|
||||
int size;
|
||||
|
||||
snd_interval_any(&frames);
|
||||
size = mcbsp->pdata->buffer_size;
|
||||
|
||||
frames.min = size / channels->min;
|
||||
frames.integer = 1;
|
||||
return snd_interval_refine(buffer_size, &frames);
|
||||
}
|
||||
|
||||
static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
int err = 0;
|
||||
|
||||
if (!cpu_dai->active)
|
||||
err = omap_mcbsp_request(mcbsp);
|
||||
|
||||
/*
|
||||
* OMAP3 McBSP FIFO is word structured.
|
||||
* McBSP2 has 1024 + 256 = 1280 word long buffer,
|
||||
* McBSP1,3,4,5 has 128 word long buffer
|
||||
* This means that the size of the FIFO depends on the sample format.
|
||||
* For example on McBSP3:
|
||||
* 16bit samples: size is 128 * 2 = 256 bytes
|
||||
* 32bit samples: size is 128 * 4 = 512 bytes
|
||||
* It is simpler to place constraint for buffer and period based on
|
||||
* channels.
|
||||
* McBSP3 as example again (16 or 32 bit samples):
|
||||
* 1 channel (mono): size is 128 frames (128 words)
|
||||
* 2 channels (stereo): size is 128 / 2 = 64 frames (2 * 64 words)
|
||||
* 4 channels: size is 128 / 4 = 32 frames (4 * 32 words)
|
||||
*/
|
||||
if (mcbsp->pdata->buffer_size) {
|
||||
/*
|
||||
* Rule for the buffer size. We should not allow
|
||||
* smaller buffer than the FIFO size to avoid underruns.
|
||||
* This applies only for the playback stream.
|
||||
*/
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
snd_pcm_hw_rule_add(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
||||
omap_mcbsp_hwrule_min_buffersize,
|
||||
mcbsp,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
|
||||
|
||||
/* Make sure, that the period size is always even */
|
||||
snd_pcm_hw_constraint_step(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void omap_mcbsp_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
|
||||
if (!cpu_dai->active) {
|
||||
omap_mcbsp_free(mcbsp);
|
||||
mcbsp->configured = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
int err = 0, play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
mcbsp->active++;
|
||||
omap_mcbsp_start(mcbsp, play, !play);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
omap_mcbsp_stop(mcbsp, play, !play);
|
||||
mcbsp->active--;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static snd_pcm_sframes_t omap_mcbsp_dai_delay(
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
u16 fifo_use;
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
fifo_use = omap_mcbsp_get_tx_delay(mcbsp);
|
||||
else
|
||||
fifo_use = omap_mcbsp_get_rx_delay(mcbsp);
|
||||
|
||||
/*
|
||||
* Divide the used locations with the channel count to get the
|
||||
* FIFO usage in samples (don't care about partial samples in the
|
||||
* buffer).
|
||||
*/
|
||||
delay = fifo_use / substream->runtime->channels;
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
struct omap_mcbsp_reg_cfg *regs = &mcbsp->cfg_regs;
|
||||
struct snd_dmaengine_dai_dma_data *dma_data;
|
||||
int wlen, channels, wpf;
|
||||
int pkt_size = 0;
|
||||
unsigned int format, div, framesize, master;
|
||||
|
||||
dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream);
|
||||
channels = params_channels(params);
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
wlen = 16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
wlen = 32;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
if (mcbsp->pdata->buffer_size) {
|
||||
if (mcbsp->dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) {
|
||||
int period_words, max_thrsh;
|
||||
int divider = 0;
|
||||
|
||||
period_words = params_period_bytes(params) / (wlen / 8);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
max_thrsh = mcbsp->max_tx_thres;
|
||||
else
|
||||
max_thrsh = mcbsp->max_rx_thres;
|
||||
/*
|
||||
* Use sDMA packet mode if McBSP is in threshold mode:
|
||||
* If period words less than the FIFO size the packet
|
||||
* size is set to the number of period words, otherwise
|
||||
* Look for the biggest threshold value which divides
|
||||
* the period size evenly.
|
||||
*/
|
||||
divider = period_words / max_thrsh;
|
||||
if (period_words % max_thrsh)
|
||||
divider++;
|
||||
while (period_words % divider &&
|
||||
divider < period_words)
|
||||
divider++;
|
||||
if (divider == period_words)
|
||||
return -EINVAL;
|
||||
|
||||
pkt_size = period_words / divider;
|
||||
} else if (channels > 1) {
|
||||
/* Use packet mode for non mono streams */
|
||||
pkt_size = channels;
|
||||
}
|
||||
omap_mcbsp_set_threshold(substream, pkt_size);
|
||||
}
|
||||
|
||||
dma_data->maxburst = pkt_size;
|
||||
|
||||
if (mcbsp->configured) {
|
||||
/* McBSP already configured by another stream */
|
||||
return 0;
|
||||
}
|
||||
|
||||
regs->rcr2 &= ~(RPHASE | RFRLEN2(0x7f) | RWDLEN2(7));
|
||||
regs->xcr2 &= ~(RPHASE | XFRLEN2(0x7f) | XWDLEN2(7));
|
||||
regs->rcr1 &= ~(RFRLEN1(0x7f) | RWDLEN1(7));
|
||||
regs->xcr1 &= ~(XFRLEN1(0x7f) | XWDLEN1(7));
|
||||
format = mcbsp->fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
||||
wpf = channels;
|
||||
if (channels == 2 && (format == SND_SOC_DAIFMT_I2S ||
|
||||
format == SND_SOC_DAIFMT_LEFT_J)) {
|
||||
/* Use dual-phase frames */
|
||||
regs->rcr2 |= RPHASE;
|
||||
regs->xcr2 |= XPHASE;
|
||||
/* Set 1 word per (McBSP) frame for phase1 and phase2 */
|
||||
wpf--;
|
||||
regs->rcr2 |= RFRLEN2(wpf - 1);
|
||||
regs->xcr2 |= XFRLEN2(wpf - 1);
|
||||
}
|
||||
|
||||
regs->rcr1 |= RFRLEN1(wpf - 1);
|
||||
regs->xcr1 |= XFRLEN1(wpf - 1);
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
/* Set word lengths */
|
||||
regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_16);
|
||||
regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_16);
|
||||
regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_16);
|
||||
regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_16);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
/* Set word lengths */
|
||||
regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_32);
|
||||
regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_32);
|
||||
regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_32);
|
||||
regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_32);
|
||||
break;
|
||||
default:
|
||||
/* Unsupported PCM format */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* In McBSP master modes, FRAME (i.e. sample rate) is generated
|
||||
* by _counting_ BCLKs. Calculate frame size in BCLKs */
|
||||
master = mcbsp->fmt & SND_SOC_DAIFMT_MASTER_MASK;
|
||||
if (master == SND_SOC_DAIFMT_CBS_CFS) {
|
||||
div = mcbsp->clk_div ? mcbsp->clk_div : 1;
|
||||
framesize = (mcbsp->in_freq / div) / params_rate(params);
|
||||
|
||||
if (framesize < wlen * channels) {
|
||||
printk(KERN_ERR "%s: not enough bandwidth for desired rate and "
|
||||
"channels\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else
|
||||
framesize = wlen * channels;
|
||||
|
||||
/* Set FS period and length in terms of bit clock periods */
|
||||
regs->srgr2 &= ~FPER(0xfff);
|
||||
regs->srgr1 &= ~FWID(0xff);
|
||||
switch (format) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
regs->srgr2 |= FPER(framesize - 1);
|
||||
regs->srgr1 |= FWID((framesize >> 1) - 1);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
regs->srgr2 |= FPER(framesize - 1);
|
||||
regs->srgr1 |= FWID(0);
|
||||
break;
|
||||
}
|
||||
|
||||
omap_mcbsp_config(mcbsp, &mcbsp->cfg_regs);
|
||||
mcbsp->wlen = wlen;
|
||||
mcbsp->configured = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This must be called before _set_clkdiv and _set_sysclk since McBSP register
|
||||
* cache is initialized here
|
||||
*/
|
||||
static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
struct omap_mcbsp_reg_cfg *regs = &mcbsp->cfg_regs;
|
||||
bool inv_fs = false;
|
||||
|
||||
if (mcbsp->configured)
|
||||
return 0;
|
||||
|
||||
mcbsp->fmt = fmt;
|
||||
memset(regs, 0, sizeof(*regs));
|
||||
/* Generic McBSP register settings */
|
||||
regs->spcr2 |= XINTM(3) | FREE;
|
||||
regs->spcr1 |= RINTM(3);
|
||||
/* RFIG and XFIG are not defined in 2430 and on OMAP3+ */
|
||||
if (!mcbsp->pdata->has_ccr) {
|
||||
regs->rcr2 |= RFIG;
|
||||
regs->xcr2 |= XFIG;
|
||||
}
|
||||
|
||||
/* Configure XCCR/RCCR only for revisions which have ccr registers */
|
||||
if (mcbsp->pdata->has_ccr) {
|
||||
regs->xccr = DXENDLY(1) | XDMAEN | XDISABLE;
|
||||
regs->rccr = RFULL_CYCLE | RDMAEN | RDISABLE;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
/* 1-bit data delay */
|
||||
regs->rcr2 |= RDATDLY(1);
|
||||
regs->xcr2 |= XDATDLY(1);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
/* 0-bit data delay */
|
||||
regs->rcr2 |= RDATDLY(0);
|
||||
regs->xcr2 |= XDATDLY(0);
|
||||
regs->spcr1 |= RJUST(2);
|
||||
/* Invert FS polarity configuration */
|
||||
inv_fs = true;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
/* 1-bit data delay */
|
||||
regs->rcr2 |= RDATDLY(1);
|
||||
regs->xcr2 |= XDATDLY(1);
|
||||
/* Invert FS polarity configuration */
|
||||
inv_fs = true;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
/* 0-bit data delay */
|
||||
regs->rcr2 |= RDATDLY(0);
|
||||
regs->xcr2 |= XDATDLY(0);
|
||||
/* Invert FS polarity configuration */
|
||||
inv_fs = true;
|
||||
break;
|
||||
default:
|
||||
/* Unsupported data format */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
/* McBSP master. Set FS and bit clocks as outputs */
|
||||
regs->pcr0 |= FSXM | FSRM |
|
||||
CLKXM | CLKRM;
|
||||
/* Sample rate generator drives the FS */
|
||||
regs->srgr2 |= FSGM;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFS:
|
||||
/* McBSP slave. FS clock as output */
|
||||
regs->srgr2 |= FSGM;
|
||||
regs->pcr0 |= FSXM | FSRM;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
/* McBSP slave */
|
||||
break;
|
||||
default:
|
||||
/* Unsupported master/slave configuration */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Set bit clock (CLKX/CLKR) and FS polarities */
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
/*
|
||||
* Normal BCLK + FS.
|
||||
* FS active low. TX data driven on falling edge of bit clock
|
||||
* and RX data sampled on rising edge of bit clock.
|
||||
*/
|
||||
regs->pcr0 |= FSXP | FSRP |
|
||||
CLKXP | CLKRP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
regs->pcr0 |= CLKXP | CLKRP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
regs->pcr0 |= FSXP | FSRP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
if (inv_fs == true)
|
||||
regs->pcr0 ^= FSXP | FSRP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_mcbsp_dai_set_clkdiv(struct snd_soc_dai *cpu_dai,
|
||||
int div_id, int div)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
struct omap_mcbsp_reg_cfg *regs = &mcbsp->cfg_regs;
|
||||
|
||||
if (div_id != OMAP_MCBSP_CLKGDV)
|
||||
return -ENODEV;
|
||||
|
||||
mcbsp->clk_div = div;
|
||||
regs->srgr1 &= ~CLKGDV(0xff);
|
||||
regs->srgr1 |= CLKGDV(div - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
int clk_id, unsigned int freq,
|
||||
int dir)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
struct omap_mcbsp_reg_cfg *regs = &mcbsp->cfg_regs;
|
||||
int err = 0;
|
||||
|
||||
if (mcbsp->active) {
|
||||
if (freq == mcbsp->in_freq)
|
||||
return 0;
|
||||
else
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
mcbsp->in_freq = freq;
|
||||
regs->srgr2 &= ~CLKSM;
|
||||
regs->pcr0 &= ~SCLKME;
|
||||
|
||||
switch (clk_id) {
|
||||
case OMAP_MCBSP_SYSCLK_CLK:
|
||||
regs->srgr2 |= CLKSM;
|
||||
break;
|
||||
case OMAP_MCBSP_SYSCLK_CLKS_FCLK:
|
||||
if (mcbsp_omap1()) {
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
err = omap2_mcbsp_set_clks_src(mcbsp,
|
||||
MCBSP_CLKS_PRCM_SRC);
|
||||
break;
|
||||
case OMAP_MCBSP_SYSCLK_CLKS_EXT:
|
||||
if (mcbsp_omap1()) {
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
err = omap2_mcbsp_set_clks_src(mcbsp,
|
||||
MCBSP_CLKS_PAD_SRC);
|
||||
break;
|
||||
|
||||
case OMAP_MCBSP_SYSCLK_CLKX_EXT:
|
||||
regs->srgr2 |= CLKSM;
|
||||
case OMAP_MCBSP_SYSCLK_CLKR_EXT:
|
||||
regs->pcr0 |= SCLKME;
|
||||
break;
|
||||
default:
|
||||
err = -ENODEV;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops mcbsp_dai_ops = {
|
||||
.startup = omap_mcbsp_dai_startup,
|
||||
.shutdown = omap_mcbsp_dai_shutdown,
|
||||
.trigger = omap_mcbsp_dai_trigger,
|
||||
.delay = omap_mcbsp_dai_delay,
|
||||
.hw_params = omap_mcbsp_dai_hw_params,
|
||||
.set_fmt = omap_mcbsp_dai_set_dai_fmt,
|
||||
.set_clkdiv = omap_mcbsp_dai_set_clkdiv,
|
||||
.set_sysclk = omap_mcbsp_dai_set_dai_sysclk,
|
||||
};
|
||||
|
||||
static int omap_mcbsp_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
pm_runtime_enable(mcbsp->dev);
|
||||
|
||||
snd_soc_dai_init_dma_data(dai,
|
||||
&mcbsp->dma_data[SNDRV_PCM_STREAM_PLAYBACK],
|
||||
&mcbsp->dma_data[SNDRV_PCM_STREAM_CAPTURE]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_mcbsp_remove(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
pm_runtime_disable(mcbsp->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_driver omap_mcbsp_dai = {
|
||||
.probe = omap_mcbsp_probe,
|
||||
.remove = omap_mcbsp_remove,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 16,
|
||||
.rates = OMAP_MCBSP_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 16,
|
||||
.rates = OMAP_MCBSP_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
|
||||
},
|
||||
.ops = &mcbsp_dai_ops,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver omap_mcbsp_component = {
|
||||
.name = "omap-mcbsp",
|
||||
};
|
||||
|
||||
static int omap_mcbsp_st_info_volsw(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
int max = mc->max;
|
||||
int min = mc->min;
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.integer.min = min;
|
||||
uinfo->value.integer.max = max;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OMAP_MCBSP_ST_CHANNEL_VOLUME(channel) \
|
||||
static int \
|
||||
omap_mcbsp_set_st_ch##channel##_volume(struct snd_kcontrol *kc, \
|
||||
struct snd_ctl_elem_value *uc) \
|
||||
{ \
|
||||
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc); \
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); \
|
||||
struct soc_mixer_control *mc = \
|
||||
(struct soc_mixer_control *)kc->private_value; \
|
||||
int max = mc->max; \
|
||||
int min = mc->min; \
|
||||
int val = uc->value.integer.value[0]; \
|
||||
\
|
||||
if (val < min || val > max) \
|
||||
return -EINVAL; \
|
||||
\
|
||||
/* OMAP McBSP implementation uses index values 0..4 */ \
|
||||
return omap_st_set_chgain(mcbsp, channel, val); \
|
||||
} \
|
||||
\
|
||||
static int \
|
||||
omap_mcbsp_get_st_ch##channel##_volume(struct snd_kcontrol *kc, \
|
||||
struct snd_ctl_elem_value *uc) \
|
||||
{ \
|
||||
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc); \
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); \
|
||||
s16 chgain; \
|
||||
\
|
||||
if (omap_st_get_chgain(mcbsp, channel, &chgain)) \
|
||||
return -EAGAIN; \
|
||||
\
|
||||
uc->value.integer.value[0] = chgain; \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
OMAP_MCBSP_ST_CHANNEL_VOLUME(0)
|
||||
OMAP_MCBSP_ST_CHANNEL_VOLUME(1)
|
||||
|
||||
static int omap_mcbsp_st_put_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
u8 value = ucontrol->value.integer.value[0];
|
||||
|
||||
if (value == omap_st_is_enabled(mcbsp))
|
||||
return 0;
|
||||
|
||||
if (value)
|
||||
omap_st_enable(mcbsp);
|
||||
else
|
||||
omap_st_disable(mcbsp);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int omap_mcbsp_st_get_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
|
||||
ucontrol->value.integer.value[0] = omap_st_is_enabled(mcbsp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OMAP_MCBSP_ST_CONTROLS(port) \
|
||||
static const struct snd_kcontrol_new omap_mcbsp##port##_st_controls[] = { \
|
||||
SOC_SINGLE_EXT("McBSP" #port " Sidetone Switch", 1, 0, 1, 0, \
|
||||
omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode), \
|
||||
OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 0 Volume", \
|
||||
-32768, 32767, \
|
||||
omap_mcbsp_get_st_ch0_volume, \
|
||||
omap_mcbsp_set_st_ch0_volume), \
|
||||
OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 1 Volume", \
|
||||
-32768, 32767, \
|
||||
omap_mcbsp_get_st_ch1_volume, \
|
||||
omap_mcbsp_set_st_ch1_volume), \
|
||||
}
|
||||
|
||||
OMAP_MCBSP_ST_CONTROLS(2);
|
||||
OMAP_MCBSP_ST_CONTROLS(3);
|
||||
|
||||
int omap_mcbsp_st_add_controls(struct snd_soc_pcm_runtime *rtd, int port_id)
|
||||
{
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
|
||||
if (!mcbsp->st_data) {
|
||||
dev_warn(mcbsp->dev, "No sidetone data for port\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (port_id) {
|
||||
case 2: /* McBSP 2 */
|
||||
return snd_soc_add_dai_controls(cpu_dai,
|
||||
omap_mcbsp2_st_controls,
|
||||
ARRAY_SIZE(omap_mcbsp2_st_controls));
|
||||
case 3: /* McBSP 3 */
|
||||
return snd_soc_add_dai_controls(cpu_dai,
|
||||
omap_mcbsp3_st_controls,
|
||||
ARRAY_SIZE(omap_mcbsp3_st_controls));
|
||||
default:
|
||||
dev_err(mcbsp->dev, "Port %d not supported\n", port_id);
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(omap_mcbsp_st_add_controls);
|
||||
|
||||
static struct omap_mcbsp_platform_data omap2420_pdata = {
|
||||
.reg_step = 4,
|
||||
.reg_size = 2,
|
||||
};
|
||||
|
||||
static struct omap_mcbsp_platform_data omap2430_pdata = {
|
||||
.reg_step = 4,
|
||||
.reg_size = 4,
|
||||
.has_ccr = true,
|
||||
};
|
||||
|
||||
static struct omap_mcbsp_platform_data omap3_pdata = {
|
||||
.reg_step = 4,
|
||||
.reg_size = 4,
|
||||
.has_ccr = true,
|
||||
.has_wakeup = true,
|
||||
};
|
||||
|
||||
static struct omap_mcbsp_platform_data omap4_pdata = {
|
||||
.reg_step = 4,
|
||||
.reg_size = 4,
|
||||
.has_ccr = true,
|
||||
.has_wakeup = true,
|
||||
};
|
||||
|
||||
static const struct of_device_id omap_mcbsp_of_match[] = {
|
||||
{
|
||||
.compatible = "ti,omap2420-mcbsp",
|
||||
.data = &omap2420_pdata,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,omap2430-mcbsp",
|
||||
.data = &omap2430_pdata,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,omap3-mcbsp",
|
||||
.data = &omap3_pdata,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,omap4-mcbsp",
|
||||
.data = &omap4_pdata,
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_mcbsp_of_match);
|
||||
|
||||
static int asoc_mcbsp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_mcbsp_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct omap_mcbsp *mcbsp;
|
||||
const struct of_device_id *match;
|
||||
int ret;
|
||||
|
||||
match = of_match_device(omap_mcbsp_of_match, &pdev->dev);
|
||||
if (match) {
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
int buffer_size;
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct omap_mcbsp_platform_data),
|
||||
GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(pdata, match->data, sizeof(*pdata));
|
||||
if (!of_property_read_u32(node, "ti,buffer-size", &buffer_size))
|
||||
pdata->buffer_size = buffer_size;
|
||||
} else if (!pdata) {
|
||||
dev_err(&pdev->dev, "missing platform data.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
mcbsp = devm_kzalloc(&pdev->dev, sizeof(struct omap_mcbsp), GFP_KERNEL);
|
||||
if (!mcbsp)
|
||||
return -ENOMEM;
|
||||
|
||||
mcbsp->id = pdev->id;
|
||||
mcbsp->pdata = pdata;
|
||||
mcbsp->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, mcbsp);
|
||||
|
||||
ret = omap_mcbsp_init(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_snd_soc_register_component(&pdev->dev,
|
||||
&omap_mcbsp_component,
|
||||
&omap_mcbsp_dai, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return omap_pcm_platform_register(&pdev->dev);
|
||||
}
|
||||
|
||||
static int asoc_mcbsp_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev);
|
||||
|
||||
if (mcbsp->pdata->ops && mcbsp->pdata->ops->free)
|
||||
mcbsp->pdata->ops->free(mcbsp->id);
|
||||
|
||||
omap_mcbsp_sysfs_remove(mcbsp);
|
||||
|
||||
clk_put(mcbsp->fclk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver asoc_mcbsp_driver = {
|
||||
.driver = {
|
||||
.name = "omap-mcbsp",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = omap_mcbsp_of_match,
|
||||
},
|
||||
|
||||
.probe = asoc_mcbsp_probe,
|
||||
.remove = asoc_mcbsp_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(asoc_mcbsp_driver);
|
||||
|
||||
MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@bitmer.com>");
|
||||
MODULE_DESCRIPTION("OMAP I2S SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:omap-mcbsp");
|
44
sound/soc/omap/omap-mcbsp.h
Normal file
44
sound/soc/omap/omap-mcbsp.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* omap-mcbsp.h
|
||||
*
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
|
||||
* Peter Ujfalusi <peter.ujfalusi@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __OMAP_I2S_H__
|
||||
#define __OMAP_I2S_H__
|
||||
|
||||
/* Source clocks for McBSP sample rate generator */
|
||||
enum omap_mcbsp_clksrg_clk {
|
||||
OMAP_MCBSP_SYSCLK_CLKS_FCLK, /* Internal FCLK */
|
||||
OMAP_MCBSP_SYSCLK_CLKS_EXT, /* External CLKS pin */
|
||||
OMAP_MCBSP_SYSCLK_CLK, /* Internal ICLK */
|
||||
OMAP_MCBSP_SYSCLK_CLKX_EXT, /* External CLKX pin */
|
||||
OMAP_MCBSP_SYSCLK_CLKR_EXT, /* External CLKR pin */
|
||||
};
|
||||
|
||||
/* McBSP dividers */
|
||||
enum omap_mcbsp_div {
|
||||
OMAP_MCBSP_CLKGDV, /* Sample rate generator divider */
|
||||
};
|
||||
|
||||
int omap_mcbsp_st_add_controls(struct snd_soc_pcm_runtime *rtd, int port_id);
|
||||
|
||||
#endif
|
527
sound/soc/omap/omap-mcpdm.c
Normal file
527
sound/soc/omap/omap-mcpdm.c
Normal file
|
@ -0,0 +1,527 @@
|
|||
/*
|
||||
* omap-mcpdm.c -- OMAP ALSA SoC DAI driver using McPDM port
|
||||
*
|
||||
* Copyright (C) 2009 - 2011 Texas Instruments
|
||||
*
|
||||
* Author: Misael Lopez Cruz <misael.lopez@ti.com>
|
||||
* Contact: Jorge Eduardo Candelaria <x0107209@ti.com>
|
||||
* Margarita Olaya <magi.olaya@ti.com>
|
||||
* Peter Ujfalusi <peter.ujfalusi@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/omap-pcm.h>
|
||||
|
||||
#include "omap-mcpdm.h"
|
||||
|
||||
struct mcpdm_link_config {
|
||||
u32 link_mask; /* channel mask for the direction */
|
||||
u32 threshold; /* FIFO threshold */
|
||||
};
|
||||
|
||||
struct omap_mcpdm {
|
||||
struct device *dev;
|
||||
unsigned long phys_base;
|
||||
void __iomem *io_base;
|
||||
int irq;
|
||||
|
||||
struct mutex mutex;
|
||||
|
||||
/* Playback/Capture configuration */
|
||||
struct mcpdm_link_config config[2];
|
||||
|
||||
/* McPDM dn offsets for rx1, and 2 channels */
|
||||
u32 dn_rx_offset;
|
||||
|
||||
/* McPDM needs to be restarted due to runtime reconfiguration */
|
||||
bool restart;
|
||||
|
||||
struct snd_dmaengine_dai_dma_data dma_data[2];
|
||||
};
|
||||
|
||||
/*
|
||||
* Stream DMA parameters
|
||||
*/
|
||||
|
||||
static inline void omap_mcpdm_write(struct omap_mcpdm *mcpdm, u16 reg, u32 val)
|
||||
{
|
||||
writel_relaxed(val, mcpdm->io_base + reg);
|
||||
}
|
||||
|
||||
static inline int omap_mcpdm_read(struct omap_mcpdm *mcpdm, u16 reg)
|
||||
{
|
||||
return readl_relaxed(mcpdm->io_base + reg);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm)
|
||||
{
|
||||
dev_dbg(mcpdm->dev, "***********************\n");
|
||||
dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS_RAW));
|
||||
dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS));
|
||||
dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_IRQENABLE_SET));
|
||||
dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_IRQENABLE_CLR));
|
||||
dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_IRQWAKE_EN));
|
||||
dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_DMAENABLE_SET));
|
||||
dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_DMAENABLE_CLR));
|
||||
dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_DMAWAKEEN));
|
||||
dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL));
|
||||
dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_DN_DATA));
|
||||
dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_UP_DATA));
|
||||
dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_FIFO_CTRL_DN));
|
||||
dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n",
|
||||
omap_mcpdm_read(mcpdm, MCPDM_REG_FIFO_CTRL_UP));
|
||||
dev_dbg(mcpdm->dev, "***********************\n");
|
||||
}
|
||||
#else
|
||||
static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm) {}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Enables the transfer through the PDM interface to/from the Phoenix
|
||||
* codec by enabling the corresponding UP or DN channels.
|
||||
*/
|
||||
static void omap_mcpdm_start(struct omap_mcpdm *mcpdm)
|
||||
{
|
||||
u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL);
|
||||
u32 link_mask = mcpdm->config[0].link_mask | mcpdm->config[1].link_mask;
|
||||
|
||||
ctrl |= (MCPDM_SW_DN_RST | MCPDM_SW_UP_RST);
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl);
|
||||
|
||||
ctrl |= link_mask;
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl);
|
||||
|
||||
ctrl &= ~(MCPDM_SW_DN_RST | MCPDM_SW_UP_RST);
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disables the transfer through the PDM interface to/from the Phoenix
|
||||
* codec by disabling the corresponding UP or DN channels.
|
||||
*/
|
||||
static void omap_mcpdm_stop(struct omap_mcpdm *mcpdm)
|
||||
{
|
||||
u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL);
|
||||
u32 link_mask = MCPDM_PDM_DN_MASK | MCPDM_PDM_UP_MASK;
|
||||
|
||||
ctrl |= (MCPDM_SW_DN_RST | MCPDM_SW_UP_RST);
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl);
|
||||
|
||||
ctrl &= ~(link_mask);
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl);
|
||||
|
||||
ctrl &= ~(MCPDM_SW_DN_RST | MCPDM_SW_UP_RST);
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Is the physical McPDM interface active.
|
||||
*/
|
||||
static inline int omap_mcpdm_active(struct omap_mcpdm *mcpdm)
|
||||
{
|
||||
return omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL) &
|
||||
(MCPDM_PDM_DN_MASK | MCPDM_PDM_UP_MASK);
|
||||
}
|
||||
|
||||
/*
|
||||
* Configures McPDM uplink, and downlink for audio.
|
||||
* This function should be called before omap_mcpdm_start.
|
||||
*/
|
||||
static void omap_mcpdm_open_streams(struct omap_mcpdm *mcpdm)
|
||||
{
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_SET,
|
||||
MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL |
|
||||
MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL);
|
||||
|
||||
/* Enable DN RX1/2 offset cancellation feature, if configured */
|
||||
if (mcpdm->dn_rx_offset) {
|
||||
u32 dn_offset = mcpdm->dn_rx_offset;
|
||||
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, dn_offset);
|
||||
dn_offset |= (MCPDM_DN_OFST_RX1_EN | MCPDM_DN_OFST_RX2_EN);
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, dn_offset);
|
||||
}
|
||||
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_FIFO_CTRL_DN,
|
||||
mcpdm->config[SNDRV_PCM_STREAM_PLAYBACK].threshold);
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_FIFO_CTRL_UP,
|
||||
mcpdm->config[SNDRV_PCM_STREAM_CAPTURE].threshold);
|
||||
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_SET,
|
||||
MCPDM_DMA_DN_ENABLE | MCPDM_DMA_UP_ENABLE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleans McPDM uplink, and downlink configuration.
|
||||
* This function should be called when the stream is closed.
|
||||
*/
|
||||
static void omap_mcpdm_close_streams(struct omap_mcpdm *mcpdm)
|
||||
{
|
||||
/* Disable irq request generation for downlink */
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_CLR,
|
||||
MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL);
|
||||
|
||||
/* Disable DMA request generation for downlink */
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_CLR, MCPDM_DMA_DN_ENABLE);
|
||||
|
||||
/* Disable irq request generation for uplink */
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_CLR,
|
||||
MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL);
|
||||
|
||||
/* Disable DMA request generation for uplink */
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_CLR, MCPDM_DMA_UP_ENABLE);
|
||||
|
||||
/* Disable RX1/2 offset cancellation */
|
||||
if (mcpdm->dn_rx_offset)
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, 0);
|
||||
}
|
||||
|
||||
static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm = dev_id;
|
||||
int irq_status;
|
||||
|
||||
irq_status = omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS);
|
||||
|
||||
/* Acknowledge irq event */
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_IRQSTATUS, irq_status);
|
||||
|
||||
if (irq_status & MCPDM_DN_IRQ_FULL)
|
||||
dev_dbg(mcpdm->dev, "DN (playback) FIFO Full\n");
|
||||
|
||||
if (irq_status & MCPDM_DN_IRQ_EMPTY)
|
||||
dev_dbg(mcpdm->dev, "DN (playback) FIFO Empty\n");
|
||||
|
||||
if (irq_status & MCPDM_DN_IRQ)
|
||||
dev_dbg(mcpdm->dev, "DN (playback) write request\n");
|
||||
|
||||
if (irq_status & MCPDM_UP_IRQ_FULL)
|
||||
dev_dbg(mcpdm->dev, "UP (capture) FIFO Full\n");
|
||||
|
||||
if (irq_status & MCPDM_UP_IRQ_EMPTY)
|
||||
dev_dbg(mcpdm->dev, "UP (capture) FIFO Empty\n");
|
||||
|
||||
if (irq_status & MCPDM_UP_IRQ)
|
||||
dev_dbg(mcpdm->dev, "UP (capture) write request\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
mutex_lock(&mcpdm->mutex);
|
||||
|
||||
if (!dai->active) {
|
||||
u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL);
|
||||
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl | MCPDM_WD_EN);
|
||||
omap_mcpdm_open_streams(mcpdm);
|
||||
}
|
||||
mutex_unlock(&mcpdm->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
mutex_lock(&mcpdm->mutex);
|
||||
|
||||
if (!dai->active) {
|
||||
if (omap_mcpdm_active(mcpdm)) {
|
||||
omap_mcpdm_stop(mcpdm);
|
||||
omap_mcpdm_close_streams(mcpdm);
|
||||
mcpdm->config[0].link_mask = 0;
|
||||
mcpdm->config[1].link_mask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&mcpdm->mutex);
|
||||
}
|
||||
|
||||
static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai);
|
||||
int stream = substream->stream;
|
||||
struct snd_dmaengine_dai_dma_data *dma_data;
|
||||
u32 threshold;
|
||||
int channels;
|
||||
int link_mask = 0;
|
||||
|
||||
channels = params_channels(params);
|
||||
switch (channels) {
|
||||
case 5:
|
||||
if (stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
/* up to 3 channels for capture */
|
||||
return -EINVAL;
|
||||
link_mask |= 1 << 4;
|
||||
case 4:
|
||||
if (stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
/* up to 3 channels for capture */
|
||||
return -EINVAL;
|
||||
link_mask |= 1 << 3;
|
||||
case 3:
|
||||
link_mask |= 1 << 2;
|
||||
case 2:
|
||||
link_mask |= 1 << 1;
|
||||
case 1:
|
||||
link_mask |= 1 << 0;
|
||||
break;
|
||||
default:
|
||||
/* unsupported number of channels */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dma_data = snd_soc_dai_get_dma_data(dai, substream);
|
||||
|
||||
threshold = mcpdm->config[stream].threshold;
|
||||
/* Configure McPDM channels, and DMA packet size */
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
link_mask <<= 3;
|
||||
|
||||
/* If capture is not running assume a stereo stream to come */
|
||||
if (!mcpdm->config[!stream].link_mask)
|
||||
mcpdm->config[!stream].link_mask = 0x3;
|
||||
|
||||
dma_data->maxburst =
|
||||
(MCPDM_DN_THRES_MAX - threshold) * channels;
|
||||
} else {
|
||||
/* If playback is not running assume a stereo stream to come */
|
||||
if (!mcpdm->config[!stream].link_mask)
|
||||
mcpdm->config[!stream].link_mask = (0x3 << 3);
|
||||
|
||||
dma_data->maxburst = threshold * channels;
|
||||
}
|
||||
|
||||
/* Check if we need to restart McPDM with this stream */
|
||||
if (mcpdm->config[stream].link_mask &&
|
||||
mcpdm->config[stream].link_mask != link_mask)
|
||||
mcpdm->restart = true;
|
||||
|
||||
mcpdm->config[stream].link_mask = link_mask;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_mcpdm_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
if (!omap_mcpdm_active(mcpdm)) {
|
||||
omap_mcpdm_start(mcpdm);
|
||||
omap_mcpdm_reg_dump(mcpdm);
|
||||
} else if (mcpdm->restart) {
|
||||
omap_mcpdm_stop(mcpdm);
|
||||
omap_mcpdm_start(mcpdm);
|
||||
mcpdm->restart = false;
|
||||
omap_mcpdm_reg_dump(mcpdm);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops omap_mcpdm_dai_ops = {
|
||||
.startup = omap_mcpdm_dai_startup,
|
||||
.shutdown = omap_mcpdm_dai_shutdown,
|
||||
.hw_params = omap_mcpdm_dai_hw_params,
|
||||
.prepare = omap_mcpdm_prepare,
|
||||
};
|
||||
|
||||
static int omap_mcpdm_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai);
|
||||
int ret;
|
||||
|
||||
pm_runtime_enable(mcpdm->dev);
|
||||
|
||||
/* Disable lines while request is ongoing */
|
||||
pm_runtime_get_sync(mcpdm->dev);
|
||||
omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, 0x00);
|
||||
|
||||
ret = devm_request_irq(mcpdm->dev, mcpdm->irq, omap_mcpdm_irq_handler,
|
||||
0, "McPDM", (void *)mcpdm);
|
||||
|
||||
pm_runtime_put_sync(mcpdm->dev);
|
||||
|
||||
if (ret) {
|
||||
dev_err(mcpdm->dev, "Request for IRQ failed\n");
|
||||
pm_runtime_disable(mcpdm->dev);
|
||||
}
|
||||
|
||||
/* Configure McPDM threshold values */
|
||||
mcpdm->config[SNDRV_PCM_STREAM_PLAYBACK].threshold = 2;
|
||||
mcpdm->config[SNDRV_PCM_STREAM_CAPTURE].threshold =
|
||||
MCPDM_UP_THRES_MAX - 3;
|
||||
|
||||
snd_soc_dai_init_dma_data(dai,
|
||||
&mcpdm->dma_data[SNDRV_PCM_STREAM_PLAYBACK],
|
||||
&mcpdm->dma_data[SNDRV_PCM_STREAM_CAPTURE]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap_mcpdm_remove(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
pm_runtime_disable(mcpdm->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
|
||||
#define OMAP_MCPDM_FORMATS SNDRV_PCM_FMTBIT_S32_LE
|
||||
|
||||
static struct snd_soc_dai_driver omap_mcpdm_dai = {
|
||||
.probe = omap_mcpdm_probe,
|
||||
.remove = omap_mcpdm_remove,
|
||||
.probe_order = SND_SOC_COMP_ORDER_LATE,
|
||||
.remove_order = SND_SOC_COMP_ORDER_EARLY,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 5,
|
||||
.rates = OMAP_MCPDM_RATES,
|
||||
.formats = OMAP_MCPDM_FORMATS,
|
||||
.sig_bits = 24,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 3,
|
||||
.rates = OMAP_MCPDM_RATES,
|
||||
.formats = OMAP_MCPDM_FORMATS,
|
||||
.sig_bits = 24,
|
||||
},
|
||||
.ops = &omap_mcpdm_dai_ops,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver omap_mcpdm_component = {
|
||||
.name = "omap-mcpdm",
|
||||
};
|
||||
|
||||
void omap_mcpdm_configure_dn_offsets(struct snd_soc_pcm_runtime *rtd,
|
||||
u8 rx1, u8 rx2)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(rtd->cpu_dai);
|
||||
|
||||
mcpdm->dn_rx_offset = MCPDM_DNOFST_RX1(rx1) | MCPDM_DNOFST_RX2(rx2);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(omap_mcpdm_configure_dn_offsets);
|
||||
|
||||
static int asoc_mcpdm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_mcpdm *mcpdm;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
mcpdm = devm_kzalloc(&pdev->dev, sizeof(struct omap_mcpdm), GFP_KERNEL);
|
||||
if (!mcpdm)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, mcpdm);
|
||||
|
||||
mutex_init(&mcpdm->mutex);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma");
|
||||
if (res == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
mcpdm->dma_data[0].addr = res->start + MCPDM_REG_DN_DATA;
|
||||
mcpdm->dma_data[1].addr = res->start + MCPDM_REG_UP_DATA;
|
||||
|
||||
mcpdm->dma_data[0].filter_data = "dn_link";
|
||||
mcpdm->dma_data[1].filter_data = "up_link";
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu");
|
||||
mcpdm->io_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(mcpdm->io_base))
|
||||
return PTR_ERR(mcpdm->io_base);
|
||||
|
||||
mcpdm->irq = platform_get_irq(pdev, 0);
|
||||
if (mcpdm->irq < 0)
|
||||
return mcpdm->irq;
|
||||
|
||||
mcpdm->dev = &pdev->dev;
|
||||
|
||||
ret = devm_snd_soc_register_component(&pdev->dev,
|
||||
&omap_mcpdm_component,
|
||||
&omap_mcpdm_dai, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return omap_pcm_platform_register(&pdev->dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id omap_mcpdm_of_match[] = {
|
||||
{ .compatible = "ti,omap4-mcpdm", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_mcpdm_of_match);
|
||||
|
||||
static struct platform_driver asoc_mcpdm_driver = {
|
||||
.driver = {
|
||||
.name = "omap-mcpdm",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = omap_mcpdm_of_match,
|
||||
},
|
||||
|
||||
.probe = asoc_mcpdm_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(asoc_mcpdm_driver);
|
||||
|
||||
MODULE_ALIAS("platform:omap-mcpdm");
|
||||
MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
|
||||
MODULE_DESCRIPTION("OMAP PDM SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
107
sound/soc/omap/omap-mcpdm.h
Normal file
107
sound/soc/omap/omap-mcpdm.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* omap-mcpdm.h
|
||||
*
|
||||
* Copyright (C) 2009 - 2011 Texas Instruments
|
||||
*
|
||||
* Contact: Misael Lopez Cruz <misael.lopez@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __OMAP_MCPDM_H__
|
||||
#define __OMAP_MCPDM_H__
|
||||
|
||||
#define MCPDM_REG_REVISION 0x00
|
||||
#define MCPDM_REG_SYSCONFIG 0x10
|
||||
#define MCPDM_REG_IRQSTATUS_RAW 0x24
|
||||
#define MCPDM_REG_IRQSTATUS 0x28
|
||||
#define MCPDM_REG_IRQENABLE_SET 0x2C
|
||||
#define MCPDM_REG_IRQENABLE_CLR 0x30
|
||||
#define MCPDM_REG_IRQWAKE_EN 0x34
|
||||
#define MCPDM_REG_DMAENABLE_SET 0x38
|
||||
#define MCPDM_REG_DMAENABLE_CLR 0x3C
|
||||
#define MCPDM_REG_DMAWAKEEN 0x40
|
||||
#define MCPDM_REG_CTRL 0x44
|
||||
#define MCPDM_REG_DN_DATA 0x48
|
||||
#define MCPDM_REG_UP_DATA 0x4C
|
||||
#define MCPDM_REG_FIFO_CTRL_DN 0x50
|
||||
#define MCPDM_REG_FIFO_CTRL_UP 0x54
|
||||
#define MCPDM_REG_DN_OFFSET 0x58
|
||||
|
||||
/*
|
||||
* MCPDM_IRQ bit fields
|
||||
* IRQSTATUS_RAW, IRQSTATUS, IRQENABLE_SET, IRQENABLE_CLR
|
||||
*/
|
||||
|
||||
#define MCPDM_DN_IRQ (1 << 0)
|
||||
#define MCPDM_DN_IRQ_EMPTY (1 << 1)
|
||||
#define MCPDM_DN_IRQ_ALMST_EMPTY (1 << 2)
|
||||
#define MCPDM_DN_IRQ_FULL (1 << 3)
|
||||
|
||||
#define MCPDM_UP_IRQ (1 << 8)
|
||||
#define MCPDM_UP_IRQ_EMPTY (1 << 9)
|
||||
#define MCPDM_UP_IRQ_ALMST_FULL (1 << 10)
|
||||
#define MCPDM_UP_IRQ_FULL (1 << 11)
|
||||
|
||||
#define MCPDM_DOWNLINK_IRQ_MASK 0x00F
|
||||
#define MCPDM_UPLINK_IRQ_MASK 0xF00
|
||||
|
||||
/*
|
||||
* MCPDM_DMAENABLE bit fields
|
||||
*/
|
||||
|
||||
#define MCPDM_DMA_DN_ENABLE (1 << 0)
|
||||
#define MCPDM_DMA_UP_ENABLE (1 << 1)
|
||||
|
||||
/*
|
||||
* MCPDM_CTRL bit fields
|
||||
*/
|
||||
|
||||
#define MCPDM_PDM_UPLINK_EN(x) (1 << (x - 1)) /* ch1 is at bit 0 */
|
||||
#define MCPDM_PDM_DOWNLINK_EN(x) (1 << (x + 2)) /* ch1 is at bit 3 */
|
||||
#define MCPDM_PDMOUTFORMAT (1 << 8)
|
||||
#define MCPDM_CMD_INT (1 << 9)
|
||||
#define MCPDM_STATUS_INT (1 << 10)
|
||||
#define MCPDM_SW_UP_RST (1 << 11)
|
||||
#define MCPDM_SW_DN_RST (1 << 12)
|
||||
#define MCPDM_WD_EN (1 << 14)
|
||||
#define MCPDM_PDM_UP_MASK 0x7
|
||||
#define MCPDM_PDM_DN_MASK (0x1f << 3)
|
||||
|
||||
|
||||
#define MCPDM_PDMOUTFORMAT_LJUST (0 << 8)
|
||||
#define MCPDM_PDMOUTFORMAT_RJUST (1 << 8)
|
||||
|
||||
/*
|
||||
* MCPDM_FIFO_CTRL bit fields
|
||||
*/
|
||||
|
||||
#define MCPDM_UP_THRES_MAX 0xF
|
||||
#define MCPDM_DN_THRES_MAX 0xF
|
||||
|
||||
/*
|
||||
* MCPDM_DN_OFFSET bit fields
|
||||
*/
|
||||
|
||||
#define MCPDM_DN_OFST_RX1_EN (1 << 0)
|
||||
#define MCPDM_DNOFST_RX1(x) ((x & 0x1f) << 1)
|
||||
#define MCPDM_DN_OFST_RX2_EN (1 << 8)
|
||||
#define MCPDM_DNOFST_RX2(x) ((x & 0x1f) << 9)
|
||||
|
||||
void omap_mcpdm_configure_dn_offsets(struct snd_soc_pcm_runtime *rtd,
|
||||
u8 rx1, u8 rx2);
|
||||
|
||||
#endif /* End of __OMAP_MCPDM_H__ */
|
244
sound/soc/omap/omap-pcm.c
Normal file
244
sound/soc/omap/omap-pcm.c
Normal file
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* omap-pcm.c -- ALSA PCM interface for the OMAP SoC
|
||||
*
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
|
||||
* Peter Ujfalusi <peter.ujfalusi@ti.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/omap-dma.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/omap-pcm.h>
|
||||
|
||||
#ifdef CONFIG_ARCH_OMAP1
|
||||
#define pcm_omap1510() cpu_is_omap1510()
|
||||
#else
|
||||
#define pcm_omap1510() 0
|
||||
#endif
|
||||
|
||||
static const struct snd_pcm_hardware omap_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME |
|
||||
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
|
||||
.period_bytes_min = 32,
|
||||
.period_bytes_max = 64 * 1024,
|
||||
.periods_min = 2,
|
||||
.periods_max = 255,
|
||||
.buffer_bytes_max = 128 * 1024,
|
||||
};
|
||||
|
||||
/* this may get called several times by oss emulation */
|
||||
static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct omap_pcm_dma_data *dma_data;
|
||||
struct dma_slave_config config;
|
||||
struct dma_chan *chan;
|
||||
int err = 0;
|
||||
|
||||
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
|
||||
/* return if this is a bufferless transfer e.g.
|
||||
* codec <--> BT codec or GSM modem -- lg FIXME */
|
||||
if (!dma_data)
|
||||
return 0;
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
runtime->dma_bytes = params_buffer_bytes(params);
|
||||
|
||||
chan = snd_dmaengine_pcm_get_chan(substream);
|
||||
if (!chan)
|
||||
return -EINVAL;
|
||||
|
||||
/* fills in addr_width and direction */
|
||||
err = snd_hwparams_to_dma_slave_config(substream, params, &config);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
snd_dmaengine_pcm_set_config_from_dai_data(substream,
|
||||
snd_soc_dai_get_dma_data(rtd->cpu_dai, substream),
|
||||
&config);
|
||||
|
||||
return dmaengine_slave_config(chan, &config);
|
||||
}
|
||||
|
||||
static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_uframes_t offset;
|
||||
|
||||
if (pcm_omap1510())
|
||||
offset = snd_dmaengine_pcm_pointer_no_residue(substream);
|
||||
else
|
||||
offset = snd_dmaengine_pcm_pointer(substream);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int omap_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_dmaengine_dai_dma_data *dma_data;
|
||||
int ret;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);
|
||||
|
||||
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
|
||||
/* DT boot: filter_data is the DMA name */
|
||||
if (rtd->cpu_dai->dev->of_node) {
|
||||
struct dma_chan *chan;
|
||||
|
||||
chan = dma_request_slave_channel(rtd->cpu_dai->dev,
|
||||
dma_data->filter_data);
|
||||
ret = snd_dmaengine_pcm_open(substream, chan);
|
||||
} else {
|
||||
ret = snd_dmaengine_pcm_open_request_chan(substream,
|
||||
omap_dma_filter_fn,
|
||||
dma_data->filter_data);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap_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);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops omap_pcm_ops = {
|
||||
.open = omap_pcm_open,
|
||||
.close = snd_dmaengine_pcm_close_release_chan,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = omap_pcm_hw_params,
|
||||
.hw_free = omap_pcm_hw_free,
|
||||
.trigger = snd_dmaengine_pcm_trigger,
|
||||
.pointer = omap_pcm_pointer,
|
||||
.mmap = omap_pcm_mmap,
|
||||
};
|
||||
|
||||
static int omap_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 = omap_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;
|
||||
}
|
||||
|
||||
static void omap_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;
|
||||
}
|
||||
}
|
||||
|
||||
static int omap_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret;
|
||||
|
||||
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
|
||||
ret = omap_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
|
||||
ret = omap_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
/* free preallocated buffers in case of error */
|
||||
if (ret)
|
||||
omap_pcm_free_dma_buffers(pcm);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_platform_driver omap_soc_platform = {
|
||||
.ops = &omap_pcm_ops,
|
||||
.pcm_new = omap_pcm_new,
|
||||
.pcm_free = omap_pcm_free_dma_buffers,
|
||||
};
|
||||
|
||||
int omap_pcm_platform_register(struct device *dev)
|
||||
{
|
||||
return devm_snd_soc_register_platform(dev, &omap_soc_platform);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(omap_pcm_platform_register);
|
||||
|
||||
MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@bitmer.com>");
|
||||
MODULE_DESCRIPTION("OMAP PCM DMA module");
|
||||
MODULE_LICENSE("GPL");
|
390
sound/soc/omap/omap-twl4030.c
Normal file
390
sound/soc/omap/omap-twl4030.c
Normal file
|
@ -0,0 +1,390 @@
|
|||
/*
|
||||
* omap-twl4030.c -- SoC audio for TI SoC based boards with twl4030 codec
|
||||
*
|
||||
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com
|
||||
* All rights reserved.
|
||||
*
|
||||
* Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
|
||||
*
|
||||
* This driver replaces the following machine drivers:
|
||||
* omap3beagle (Author: Steve Sakoman <steve@sakoman.com>)
|
||||
* omap3evm (Author: Anuj Aggarwal <anuj.aggarwal@ti.com>)
|
||||
* overo (Author: Steve Sakoman <steve@sakoman.com>)
|
||||
* igep0020 (Author: Enric Balletbo i Serra <eballetbo@iseebcn.com>)
|
||||
* zoom2 (Author: Misael Lopez Cruz <misael.lopez@ti.com>)
|
||||
* sdp3430 (Author: Misael Lopez Cruz <misael.lopez@ti.com>)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/omap-twl4030.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_gpio.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
#include "omap-mcbsp.h"
|
||||
|
||||
struct omap_twl4030 {
|
||||
int jack_detect; /* board can detect jack events */
|
||||
struct snd_soc_jack hs_jack;
|
||||
};
|
||||
|
||||
static int omap_twl4030_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
unsigned int fmt;
|
||||
int ret;
|
||||
|
||||
switch (params_channels(params)) {
|
||||
case 2: /* Stereo I2S mode */
|
||||
fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
break;
|
||||
case 4: /* Four channel TDM mode */
|
||||
fmt = SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_IB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
|
||||
if (ret < 0) {
|
||||
dev_err(card->dev, "can't set codec DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||||
if (ret < 0) {
|
||||
dev_err(card->dev, "can't set cpu DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops omap_twl4030_ops = {
|
||||
.hw_params = omap_twl4030_hw_params,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget dapm_widgets[] = {
|
||||
SND_SOC_DAPM_SPK("Earpiece Spk", NULL),
|
||||
SND_SOC_DAPM_SPK("Handsfree Spk", NULL),
|
||||
SND_SOC_DAPM_HP("Headset Stereophone", NULL),
|
||||
SND_SOC_DAPM_SPK("Ext Spk", NULL),
|
||||
SND_SOC_DAPM_SPK("Carkit Spk", NULL),
|
||||
|
||||
SND_SOC_DAPM_MIC("Main Mic", NULL),
|
||||
SND_SOC_DAPM_MIC("Sub Mic", NULL),
|
||||
SND_SOC_DAPM_MIC("Headset Mic", NULL),
|
||||
SND_SOC_DAPM_MIC("Carkit Mic", NULL),
|
||||
SND_SOC_DAPM_MIC("Digital0 Mic", NULL),
|
||||
SND_SOC_DAPM_MIC("Digital1 Mic", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
/* Headset Stereophone: HSOL, HSOR */
|
||||
{"Headset Stereophone", NULL, "HSOL"},
|
||||
{"Headset Stereophone", NULL, "HSOR"},
|
||||
/* External Speakers: HFL, HFR */
|
||||
{"Handsfree Spk", NULL, "HFL"},
|
||||
{"Handsfree Spk", NULL, "HFR"},
|
||||
/* External Speakers: PredrivL, PredrivR */
|
||||
{"Ext Spk", NULL, "PREDRIVEL"},
|
||||
{"Ext Spk", NULL, "PREDRIVER"},
|
||||
/* Carkit speakers: CARKITL, CARKITR */
|
||||
{"Carkit Spk", NULL, "CARKITL"},
|
||||
{"Carkit Spk", NULL, "CARKITR"},
|
||||
/* Earpiece */
|
||||
{"Earpiece Spk", NULL, "EARPIECE"},
|
||||
|
||||
/* External Mics: MAINMIC, SUBMIC with bias */
|
||||
{"MAINMIC", NULL, "Main Mic"},
|
||||
{"Main Mic", NULL, "Mic Bias 1"},
|
||||
{"SUBMIC", NULL, "Sub Mic"},
|
||||
{"Sub Mic", NULL, "Mic Bias 2"},
|
||||
/* Headset Mic: HSMIC with bias */
|
||||
{"HSMIC", NULL, "Headset Mic"},
|
||||
{"Headset Mic", NULL, "Headset Mic Bias"},
|
||||
/* Digital Mics: DIGIMIC0, DIGIMIC1 with bias */
|
||||
{"DIGIMIC0", NULL, "Digital0 Mic"},
|
||||
{"Digital0 Mic", NULL, "Mic Bias 1"},
|
||||
{"DIGIMIC1", NULL, "Digital1 Mic"},
|
||||
{"Digital1 Mic", NULL, "Mic Bias 2"},
|
||||
/* Carkit In: CARKITMIC */
|
||||
{"CARKITMIC", NULL, "Carkit Mic"},
|
||||
/* Aux In: AUXL, AUXR */
|
||||
{"AUXL", NULL, "Line In"},
|
||||
{"AUXR", NULL, "Line In"},
|
||||
};
|
||||
|
||||
/* Headset jack detection DAPM pins */
|
||||
static struct snd_soc_jack_pin hs_jack_pins[] = {
|
||||
{
|
||||
.pin = "Headset Mic",
|
||||
.mask = SND_JACK_MICROPHONE,
|
||||
},
|
||||
{
|
||||
.pin = "Headset Stereophone",
|
||||
.mask = SND_JACK_HEADPHONE,
|
||||
},
|
||||
};
|
||||
|
||||
/* Headset jack detection gpios */
|
||||
static struct snd_soc_jack_gpio hs_jack_gpios[] = {
|
||||
{
|
||||
.name = "hsdet-gpio",
|
||||
.report = SND_JACK_HEADSET,
|
||||
.debounce_time = 200,
|
||||
},
|
||||
};
|
||||
|
||||
static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm,
|
||||
int connected, char *pin)
|
||||
{
|
||||
if (!connected)
|
||||
snd_soc_dapm_disable_pin(dapm, pin);
|
||||
}
|
||||
|
||||
static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev);
|
||||
struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card);
|
||||
int ret = 0;
|
||||
|
||||
/* Headset jack detection only if it is supported */
|
||||
if (priv->jack_detect > 0) {
|
||||
hs_jack_gpios[0].gpio = priv->jack_detect;
|
||||
|
||||
ret = snd_soc_jack_new(codec, "Headset Jack", SND_JACK_HEADSET,
|
||||
&priv->hs_jack);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_jack_add_pins(&priv->hs_jack,
|
||||
ARRAY_SIZE(hs_jack_pins),
|
||||
hs_jack_pins);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_jack_add_gpios(&priv->hs_jack,
|
||||
ARRAY_SIZE(hs_jack_gpios),
|
||||
hs_jack_gpios);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* NULL pdata means we booted with DT. In this case the routing is
|
||||
* provided and the card is fully routed, no need to mark pins.
|
||||
*/
|
||||
if (!pdata || !pdata->custom_routing)
|
||||
return ret;
|
||||
|
||||
/* Disable not connected paths if not used */
|
||||
twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk");
|
||||
|
||||
twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic");
|
||||
twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap_twl4030_card_remove(struct snd_soc_card *card)
|
||||
{
|
||||
struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card);
|
||||
|
||||
if (priv->jack_detect > 0)
|
||||
snd_soc_jack_free_gpios(&priv->hs_jack,
|
||||
ARRAY_SIZE(hs_jack_gpios),
|
||||
hs_jack_gpios);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link omap_twl4030_dai_links[] = {
|
||||
{
|
||||
.name = "TWL4030 HiFi",
|
||||
.stream_name = "TWL4030 HiFi",
|
||||
.cpu_dai_name = "omap-mcbsp.2",
|
||||
.codec_dai_name = "twl4030-hifi",
|
||||
.platform_name = "omap-mcbsp.2",
|
||||
.codec_name = "twl4030-codec",
|
||||
.init = omap_twl4030_init,
|
||||
.ops = &omap_twl4030_ops,
|
||||
},
|
||||
{
|
||||
.name = "TWL4030 Voice",
|
||||
.stream_name = "TWL4030 Voice",
|
||||
.cpu_dai_name = "omap-mcbsp.3",
|
||||
.codec_dai_name = "twl4030-voice",
|
||||
.platform_name = "omap-mcbsp.3",
|
||||
.codec_name = "twl4030-codec",
|
||||
.dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM,
|
||||
},
|
||||
};
|
||||
|
||||
/* Audio machine driver */
|
||||
static struct snd_soc_card omap_twl4030_card = {
|
||||
.owner = THIS_MODULE,
|
||||
.remove = omap_twl4030_card_remove,
|
||||
.dai_link = omap_twl4030_dai_links,
|
||||
.num_links = ARRAY_SIZE(omap_twl4030_dai_links),
|
||||
|
||||
.dapm_widgets = dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
};
|
||||
|
||||
static int omap_twl4030_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct snd_soc_card *card = &omap_twl4030_card;
|
||||
struct omap_twl4030 *priv;
|
||||
int ret = 0;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(struct omap_twl4030), GFP_KERNEL);
|
||||
if (priv == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
if (node) {
|
||||
struct device_node *dai_node;
|
||||
struct property *prop;
|
||||
|
||||
if (snd_soc_of_parse_card_name(card, "ti,model")) {
|
||||
dev_err(&pdev->dev, "Card name is not provided\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dai_node = of_parse_phandle(node, "ti,mcbsp", 0);
|
||||
if (!dai_node) {
|
||||
dev_err(&pdev->dev, "McBSP node is not provided\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
omap_twl4030_dai_links[0].cpu_dai_name = NULL;
|
||||
omap_twl4030_dai_links[0].cpu_of_node = dai_node;
|
||||
|
||||
omap_twl4030_dai_links[0].platform_name = NULL;
|
||||
omap_twl4030_dai_links[0].platform_of_node = dai_node;
|
||||
|
||||
dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0);
|
||||
if (!dai_node) {
|
||||
card->num_links = 1;
|
||||
} else {
|
||||
omap_twl4030_dai_links[1].cpu_dai_name = NULL;
|
||||
omap_twl4030_dai_links[1].cpu_of_node = dai_node;
|
||||
|
||||
omap_twl4030_dai_links[1].platform_name = NULL;
|
||||
omap_twl4030_dai_links[1].platform_of_node = dai_node;
|
||||
}
|
||||
|
||||
priv->jack_detect = of_get_named_gpio(node,
|
||||
"ti,jack-det-gpio", 0);
|
||||
|
||||
/* Optional: audio routing can be provided */
|
||||
prop = of_find_property(node, "ti,audio-routing", NULL);
|
||||
if (prop) {
|
||||
ret = snd_soc_of_parse_audio_routing(card,
|
||||
"ti,audio-routing");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
card->fully_routed = 1;
|
||||
}
|
||||
} else if (pdata) {
|
||||
if (pdata->card_name) {
|
||||
card->name = pdata->card_name;
|
||||
} else {
|
||||
dev_err(&pdev->dev, "Card name is not provided\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!pdata->voice_connected)
|
||||
card->num_links = 1;
|
||||
|
||||
priv->jack_detect = pdata->jack_detect;
|
||||
} else {
|
||||
dev_err(&pdev->dev, "Missing pdata\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
snd_soc_card_set_drvdata(card, priv);
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id omap_twl4030_of_match[] = {
|
||||
{.compatible = "ti,omap-twl4030", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_twl4030_of_match);
|
||||
|
||||
static struct platform_driver omap_twl4030_driver = {
|
||||
.driver = {
|
||||
.name = "omap-twl4030",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = omap_twl4030_of_match,
|
||||
},
|
||||
.probe = omap_twl4030_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(omap_twl4030_driver);
|
||||
|
||||
MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC for TI SoC based boards with twl4030 codec");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:omap-twl4030");
|
317
sound/soc/omap/omap3pandora.c
Normal file
317
sound/soc/omap/omap3pandora.c
Normal file
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* omap3pandora.c -- SoC audio for Pandora Handheld Console
|
||||
*
|
||||
* Author: Gražvydas Ignotas <notasas@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
#include <linux/platform_data/asoc-ti-mcbsp.h>
|
||||
|
||||
#include "omap-mcbsp.h"
|
||||
|
||||
#define OMAP3_PANDORA_DAC_POWER_GPIO 118
|
||||
#define OMAP3_PANDORA_AMP_POWER_GPIO 14
|
||||
|
||||
#define PREFIX "ASoC omap3pandora: "
|
||||
|
||||
static struct regulator *omap3pandora_dac_reg;
|
||||
|
||||
static int omap3pandora_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
int ret;
|
||||
|
||||
/* Set the codec system clock for DAC and ADC */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
pr_err(PREFIX "can't set codec system clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set McBSP clock to external */
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_EXT,
|
||||
256 * params_rate(params),
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
pr_err(PREFIX "can't set cpu system clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, 8);
|
||||
if (ret < 0) {
|
||||
pr_err(PREFIX "can't set SRG clock divider\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* The PCM1773 DAC datasheet requires 1ms delay between switching
|
||||
* VCC power on/off and /PD pin high/low
|
||||
*/
|
||||
if (SND_SOC_DAPM_EVENT_ON(event)) {
|
||||
ret = regulator_enable(omap3pandora_dac_reg);
|
||||
if (ret) {
|
||||
dev_err(w->dapm->dev, "Failed to power DAC: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
mdelay(1);
|
||||
gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 1);
|
||||
} else {
|
||||
gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
|
||||
mdelay(1);
|
||||
regulator_disable(omap3pandora_dac_reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
if (SND_SOC_DAPM_EVENT_ON(event))
|
||||
gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 1);
|
||||
else
|
||||
gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Audio paths on Pandora board:
|
||||
*
|
||||
* |O| ---> PCM DAC +-> AMP -> Headphone Jack
|
||||
* |M| A +--------> Line Out
|
||||
* |A| <~~clk~~+
|
||||
* |P| <--- TWL4030 <--------- Line In and MICs
|
||||
*/
|
||||
static const struct snd_soc_dapm_widget omap3pandora_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC_E("PCM DAC", "HiFi Playback", SND_SOC_NOPM,
|
||||
0, 0, omap3pandora_dac_event,
|
||||
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
||||
SND_SOC_DAPM_PGA_E("Headphone Amplifier", SND_SOC_NOPM,
|
||||
0, 0, NULL, 0, omap3pandora_hp_event,
|
||||
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line Out", NULL),
|
||||
|
||||
SND_SOC_DAPM_MIC("Mic (internal)", NULL),
|
||||
SND_SOC_DAPM_MIC("Mic (external)", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route omap3pandora_map[] = {
|
||||
{"PCM DAC", NULL, "APLL Enable"},
|
||||
{"Headphone Amplifier", NULL, "PCM DAC"},
|
||||
{"Line Out", NULL, "PCM DAC"},
|
||||
{"Headphone Jack", NULL, "Headphone Amplifier"},
|
||||
|
||||
{"AUXL", NULL, "Line In"},
|
||||
{"AUXR", NULL, "Line In"},
|
||||
|
||||
{"MAINMIC", NULL, "Mic (internal)"},
|
||||
{"Mic (internal)", NULL, "Mic Bias 1"},
|
||||
|
||||
{"SUBMIC", NULL, "Mic (external)"},
|
||||
{"Mic (external)", NULL, "Mic Bias 2"},
|
||||
};
|
||||
|
||||
static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
|
||||
/* All TWL4030 output pins are floating */
|
||||
snd_soc_dapm_nc_pin(dapm, "EARPIECE");
|
||||
snd_soc_dapm_nc_pin(dapm, "PREDRIVEL");
|
||||
snd_soc_dapm_nc_pin(dapm, "PREDRIVER");
|
||||
snd_soc_dapm_nc_pin(dapm, "HSOL");
|
||||
snd_soc_dapm_nc_pin(dapm, "HSOR");
|
||||
snd_soc_dapm_nc_pin(dapm, "CARKITL");
|
||||
snd_soc_dapm_nc_pin(dapm, "CARKITR");
|
||||
snd_soc_dapm_nc_pin(dapm, "HFL");
|
||||
snd_soc_dapm_nc_pin(dapm, "HFR");
|
||||
snd_soc_dapm_nc_pin(dapm, "VIBRA");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
|
||||
/* Not comnnected */
|
||||
snd_soc_dapm_nc_pin(dapm, "HSMIC");
|
||||
snd_soc_dapm_nc_pin(dapm, "CARKITMIC");
|
||||
snd_soc_dapm_nc_pin(dapm, "DIGIMIC0");
|
||||
snd_soc_dapm_nc_pin(dapm, "DIGIMIC1");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops omap3pandora_ops = {
|
||||
.hw_params = omap3pandora_hw_params,
|
||||
};
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link omap3pandora_dai[] = {
|
||||
{
|
||||
.name = "PCM1773",
|
||||
.stream_name = "HiFi Out",
|
||||
.cpu_dai_name = "omap-mcbsp.2",
|
||||
.codec_dai_name = "twl4030-hifi",
|
||||
.platform_name = "omap-mcbsp.2",
|
||||
.codec_name = "twl4030-codec",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS,
|
||||
.ops = &omap3pandora_ops,
|
||||
.init = omap3pandora_out_init,
|
||||
}, {
|
||||
.name = "TWL4030",
|
||||
.stream_name = "Line/Mic In",
|
||||
.cpu_dai_name = "omap-mcbsp.4",
|
||||
.codec_dai_name = "twl4030-hifi",
|
||||
.platform_name = "omap-mcbsp.4",
|
||||
.codec_name = "twl4030-codec",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS,
|
||||
.ops = &omap3pandora_ops,
|
||||
.init = omap3pandora_in_init,
|
||||
}
|
||||
};
|
||||
|
||||
/* SoC card */
|
||||
static struct snd_soc_card snd_soc_card_omap3pandora = {
|
||||
.name = "omap3pandora",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = omap3pandora_dai,
|
||||
.num_links = ARRAY_SIZE(omap3pandora_dai),
|
||||
|
||||
.dapm_widgets = omap3pandora_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(omap3pandora_dapm_widgets),
|
||||
.dapm_routes = omap3pandora_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(omap3pandora_map),
|
||||
};
|
||||
|
||||
static struct platform_device *omap3pandora_snd_device;
|
||||
|
||||
static int __init omap3pandora_soc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!machine_is_omap3_pandora())
|
||||
return -ENODEV;
|
||||
|
||||
pr_info("OMAP3 Pandora SoC init\n");
|
||||
|
||||
ret = gpio_request(OMAP3_PANDORA_DAC_POWER_GPIO, "dac_power");
|
||||
if (ret) {
|
||||
pr_err(PREFIX "Failed to get DAC power GPIO\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gpio_direction_output(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
|
||||
if (ret) {
|
||||
pr_err(PREFIX "Failed to set DAC power GPIO direction\n");
|
||||
goto fail0;
|
||||
}
|
||||
|
||||
ret = gpio_request(OMAP3_PANDORA_AMP_POWER_GPIO, "amp_power");
|
||||
if (ret) {
|
||||
pr_err(PREFIX "Failed to get amp power GPIO\n");
|
||||
goto fail0;
|
||||
}
|
||||
|
||||
ret = gpio_direction_output(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
|
||||
if (ret) {
|
||||
pr_err(PREFIX "Failed to set amp power GPIO direction\n");
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
omap3pandora_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (omap3pandora_snd_device == NULL) {
|
||||
pr_err(PREFIX "Platform device allocation failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
platform_set_drvdata(omap3pandora_snd_device, &snd_soc_card_omap3pandora);
|
||||
|
||||
ret = platform_device_add(omap3pandora_snd_device);
|
||||
if (ret) {
|
||||
pr_err(PREFIX "Unable to add platform device\n");
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
omap3pandora_dac_reg = regulator_get(&omap3pandora_snd_device->dev, "vcc");
|
||||
if (IS_ERR(omap3pandora_dac_reg)) {
|
||||
pr_err(PREFIX "Failed to get DAC regulator from %s: %ld\n",
|
||||
dev_name(&omap3pandora_snd_device->dev),
|
||||
PTR_ERR(omap3pandora_dac_reg));
|
||||
ret = PTR_ERR(omap3pandora_dac_reg);
|
||||
goto fail3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail3:
|
||||
platform_device_del(omap3pandora_snd_device);
|
||||
fail2:
|
||||
platform_device_put(omap3pandora_snd_device);
|
||||
fail1:
|
||||
gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
|
||||
fail0:
|
||||
gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
|
||||
return ret;
|
||||
}
|
||||
module_init(omap3pandora_soc_init);
|
||||
|
||||
static void __exit omap3pandora_soc_exit(void)
|
||||
{
|
||||
regulator_put(omap3pandora_dac_reg);
|
||||
platform_device_unregister(omap3pandora_snd_device);
|
||||
gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
|
||||
gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
|
||||
}
|
||||
module_exit(omap3pandora_soc_exit);
|
||||
|
||||
MODULE_AUTHOR("Grazvydas Ignotas <notasas@gmail.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC OMAP3 Pandora");
|
||||
MODULE_LICENSE("GPL");
|
187
sound/soc/omap/osk5912.c
Normal file
187
sound/soc/omap/osk5912.c
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* osk5912.c -- SoC audio for OSK 5912
|
||||
*
|
||||
* Copyright (C) 2008 Mistral Solutions
|
||||
*
|
||||
* Contact: Arun KS <arunks@mistralsolutions.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/asoc-ti-mcbsp.h>
|
||||
|
||||
#include "omap-mcbsp.h"
|
||||
#include "../codecs/tlv320aic23.h"
|
||||
|
||||
#define CODEC_CLOCK 12000000
|
||||
|
||||
static struct clk *tlv320aic23_mclk;
|
||||
|
||||
static int osk_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return clk_enable(tlv320aic23_mclk);
|
||||
}
|
||||
|
||||
static void osk_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
clk_disable(tlv320aic23_mclk);
|
||||
}
|
||||
|
||||
static int osk_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
int err;
|
||||
|
||||
/* Set the codec system clock for DAC and ADC */
|
||||
err =
|
||||
snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
|
||||
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "can't set codec system clock\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops osk_ops = {
|
||||
.startup = osk_startup,
|
||||
.hw_params = osk_hw_params,
|
||||
.shutdown = osk_shutdown,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
SND_SOC_DAPM_MIC("Mic Jack", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{"Headphone Jack", NULL, "LHPOUT"},
|
||||
{"Headphone Jack", NULL, "RHPOUT"},
|
||||
|
||||
{"LLINEIN", NULL, "Line In"},
|
||||
{"RLINEIN", NULL, "Line In"},
|
||||
|
||||
{"MICIN", NULL, "Mic Jack"},
|
||||
};
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link osk_dai = {
|
||||
.name = "TLV320AIC23",
|
||||
.stream_name = "AIC23",
|
||||
.cpu_dai_name = "omap-mcbsp.1",
|
||||
.codec_dai_name = "tlv320aic23-hifi",
|
||||
.platform_name = "omap-mcbsp.1",
|
||||
.codec_name = "tlv320aic23-codec",
|
||||
.dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM,
|
||||
.ops = &osk_ops,
|
||||
};
|
||||
|
||||
/* Audio machine driver */
|
||||
static struct snd_soc_card snd_soc_card_osk = {
|
||||
.name = "OSK5912",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &osk_dai,
|
||||
.num_links = 1,
|
||||
|
||||
.dapm_widgets = tlv320aic23_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets),
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
};
|
||||
|
||||
static struct platform_device *osk_snd_device;
|
||||
|
||||
static int __init osk_soc_init(void)
|
||||
{
|
||||
int err;
|
||||
u32 curRate;
|
||||
struct device *dev;
|
||||
|
||||
if (!(machine_is_omap_osk()))
|
||||
return -ENODEV;
|
||||
|
||||
osk_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!osk_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(osk_snd_device, &snd_soc_card_osk);
|
||||
err = platform_device_add(osk_snd_device);
|
||||
if (err)
|
||||
goto err1;
|
||||
|
||||
dev = &osk_snd_device->dev;
|
||||
|
||||
tlv320aic23_mclk = clk_get(dev, "mclk");
|
||||
if (IS_ERR(tlv320aic23_mclk)) {
|
||||
printk(KERN_ERR "Could not get mclk clock\n");
|
||||
err = PTR_ERR(tlv320aic23_mclk);
|
||||
goto err2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure 12 MHz output on MCLK.
|
||||
*/
|
||||
curRate = (uint) clk_get_rate(tlv320aic23_mclk);
|
||||
if (curRate != CODEC_CLOCK) {
|
||||
if (clk_set_rate(tlv320aic23_mclk, CODEC_CLOCK)) {
|
||||
printk(KERN_ERR "Cannot set MCLK for AIC23 CODEC\n");
|
||||
err = -ECANCELED;
|
||||
goto err3;
|
||||
}
|
||||
}
|
||||
|
||||
printk(KERN_INFO "MCLK = %d [%d]\n",
|
||||
(uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK);
|
||||
|
||||
return 0;
|
||||
|
||||
err3:
|
||||
clk_put(tlv320aic23_mclk);
|
||||
err2:
|
||||
platform_device_del(osk_snd_device);
|
||||
err1:
|
||||
platform_device_put(osk_snd_device);
|
||||
|
||||
return err;
|
||||
|
||||
}
|
||||
|
||||
static void __exit osk_soc_exit(void)
|
||||
{
|
||||
clk_put(tlv320aic23_mclk);
|
||||
platform_device_unregister(osk_snd_device);
|
||||
}
|
||||
|
||||
module_init(osk_soc_init);
|
||||
module_exit(osk_soc_exit);
|
||||
|
||||
MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC OSK 5912");
|
||||
MODULE_LICENSE("GPL");
|
533
sound/soc/omap/rx51.c
Normal file
533
sound/soc/omap/rx51.c
Normal file
|
@ -0,0 +1,533 @@
|
|||
/*
|
||||
* rx51.c -- SoC audio for Nokia RX-51
|
||||
*
|
||||
* Copyright (C) 2008 - 2009 Nokia Corporation
|
||||
*
|
||||
* Contact: Peter Ujfalusi <peter.ujfalusi@ti.com>
|
||||
* Eduardo Valentin <eduardo.valentin@nokia.com>
|
||||
* Jarkko Nikula <jarkko.nikula@bitmer.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <linux/platform_data/asoc-ti-mcbsp.h>
|
||||
#include "../codecs/tpa6130a2.h"
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include "omap-mcbsp.h"
|
||||
|
||||
enum {
|
||||
RX51_JACK_DISABLED,
|
||||
RX51_JACK_TVOUT, /* tv-out with stereo output */
|
||||
RX51_JACK_HP, /* headphone: stereo output, no mic */
|
||||
RX51_JACK_HS, /* headset: stereo output with mic */
|
||||
};
|
||||
|
||||
struct rx51_audio_pdata {
|
||||
struct gpio_desc *tvout_selection_gpio;
|
||||
struct gpio_desc *jack_detection_gpio;
|
||||
struct gpio_desc *eci_sw_gpio;
|
||||
struct gpio_desc *speaker_amp_gpio;
|
||||
};
|
||||
|
||||
static int rx51_spk_func;
|
||||
static int rx51_dmic_func;
|
||||
static int rx51_jack_func;
|
||||
|
||||
static void rx51_ext_control(struct snd_soc_dapm_context *dapm)
|
||||
{
|
||||
struct snd_soc_card *card = dapm->card;
|
||||
struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card);
|
||||
int hp = 0, hs = 0, tvout = 0;
|
||||
|
||||
switch (rx51_jack_func) {
|
||||
case RX51_JACK_TVOUT:
|
||||
tvout = 1;
|
||||
hp = 1;
|
||||
break;
|
||||
case RX51_JACK_HS:
|
||||
hs = 1;
|
||||
case RX51_JACK_HP:
|
||||
hp = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
snd_soc_dapm_mutex_lock(dapm);
|
||||
|
||||
if (rx51_spk_func)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk");
|
||||
if (rx51_dmic_func)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "DMic");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "DMic");
|
||||
if (hp)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
|
||||
if (hs)
|
||||
snd_soc_dapm_enable_pin_unlocked(dapm, "HS Mic");
|
||||
else
|
||||
snd_soc_dapm_disable_pin_unlocked(dapm, "HS Mic");
|
||||
|
||||
gpiod_set_value(pdata->tvout_selection_gpio, tvout);
|
||||
|
||||
snd_soc_dapm_sync_unlocked(dapm);
|
||||
|
||||
snd_soc_dapm_mutex_unlock(dapm);
|
||||
}
|
||||
|
||||
static int rx51_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
snd_pcm_hw_constraint_minmax(runtime,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
|
||||
rx51_ext_control(&card->dapm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rx51_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
|
||||
/* Set the codec system clock for DAC and ADC */
|
||||
return snd_soc_dai_set_sysclk(codec_dai, 0, 19200000,
|
||||
SND_SOC_CLOCK_IN);
|
||||
}
|
||||
|
||||
static struct snd_soc_ops rx51_ops = {
|
||||
.startup = rx51_startup,
|
||||
.hw_params = rx51_hw_params,
|
||||
};
|
||||
|
||||
static int rx51_get_spk(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.integer.value[0] = rx51_spk_func;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rx51_set_spk(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (rx51_spk_func == ucontrol->value.integer.value[0])
|
||||
return 0;
|
||||
|
||||
rx51_spk_func = ucontrol->value.integer.value[0];
|
||||
rx51_ext_control(&card->dapm);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int rx51_spk_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
struct snd_soc_dapm_context *dapm = w->dapm;
|
||||
struct snd_soc_card *card = dapm->card;
|
||||
struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card);
|
||||
|
||||
gpiod_set_raw_value_cansleep(pdata->speaker_amp_gpio,
|
||||
!!SND_SOC_DAPM_EVENT_ON(event));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rx51_hp_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
|
||||
|
||||
if (SND_SOC_DAPM_EVENT_ON(event))
|
||||
tpa6130a2_stereo_enable(codec, 1);
|
||||
else
|
||||
tpa6130a2_stereo_enable(codec, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rx51_get_input(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.integer.value[0] = rx51_dmic_func;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rx51_set_input(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (rx51_dmic_func == ucontrol->value.integer.value[0])
|
||||
return 0;
|
||||
|
||||
rx51_dmic_func = ucontrol->value.integer.value[0];
|
||||
rx51_ext_control(&card->dapm);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int rx51_get_jack(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.integer.value[0] = rx51_jack_func;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rx51_set_jack(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
if (rx51_jack_func == ucontrol->value.integer.value[0])
|
||||
return 0;
|
||||
|
||||
rx51_jack_func = ucontrol->value.integer.value[0];
|
||||
rx51_ext_control(&card->dapm);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct snd_soc_jack rx51_av_jack;
|
||||
|
||||
static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = {
|
||||
{
|
||||
.name = "avdet-gpio",
|
||||
.report = SND_JACK_HEADSET,
|
||||
.invert = 1,
|
||||
.debounce_time = 200,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event),
|
||||
SND_SOC_DAPM_MIC("DMic", NULL),
|
||||
SND_SOC_DAPM_HP("Headphone Jack", rx51_hp_event),
|
||||
SND_SOC_DAPM_MIC("HS Mic", NULL),
|
||||
SND_SOC_DAPM_LINE("FM Transmitter", NULL),
|
||||
SND_SOC_DAPM_SPK("Earphone", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{"Ext Spk", NULL, "HPLOUT"},
|
||||
{"Ext Spk", NULL, "HPROUT"},
|
||||
{"Headphone Jack", NULL, "LLOUT"},
|
||||
{"Headphone Jack", NULL, "RLOUT"},
|
||||
{"FM Transmitter", NULL, "LLOUT"},
|
||||
{"FM Transmitter", NULL, "RLOUT"},
|
||||
|
||||
{"DMic Rate 64", NULL, "Mic Bias"},
|
||||
{"Mic Bias", NULL, "DMic"},
|
||||
|
||||
{"b LINE2R", NULL, "MONO_LOUT"},
|
||||
{"Earphone", NULL, "b HPLOUT"},
|
||||
|
||||
{"LINE1L", NULL, "b Mic Bias"},
|
||||
{"b Mic Bias", NULL, "HS Mic"}
|
||||
};
|
||||
|
||||
static const char * const spk_function[] = {"Off", "On"};
|
||||
static const char * const input_function[] = {"ADC", "Digital Mic"};
|
||||
static const char * const jack_function[] = {
|
||||
"Off", "TV-OUT", "Headphone", "Headset"
|
||||
};
|
||||
|
||||
static const struct soc_enum rx51_enum[] = {
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function),
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new aic34_rx51_controls[] = {
|
||||
SOC_ENUM_EXT("Speaker Function", rx51_enum[0],
|
||||
rx51_get_spk, rx51_set_spk),
|
||||
SOC_ENUM_EXT("Input Select", rx51_enum[1],
|
||||
rx51_get_input, rx51_set_input),
|
||||
SOC_ENUM_EXT("Jack Function", rx51_enum[2],
|
||||
rx51_get_jack, rx51_set_jack),
|
||||
SOC_DAPM_PIN_SWITCH("FM Transmitter"),
|
||||
SOC_DAPM_PIN_SWITCH("Earphone"),
|
||||
};
|
||||
|
||||
static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card);
|
||||
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
int err;
|
||||
|
||||
/* Set up NC codec pins */
|
||||
snd_soc_dapm_nc_pin(dapm, "MIC3L");
|
||||
snd_soc_dapm_nc_pin(dapm, "MIC3R");
|
||||
snd_soc_dapm_nc_pin(dapm, "LINE1R");
|
||||
|
||||
err = tpa6130a2_add_controls(codec);
|
||||
if (err < 0) {
|
||||
dev_err(card->dev, "Failed to add TPA6130A2 controls\n");
|
||||
return err;
|
||||
}
|
||||
snd_soc_limit_volume(codec, "TPA6130A2 Headphone Playback Volume", 42);
|
||||
|
||||
err = omap_mcbsp_st_add_controls(rtd, 2);
|
||||
if (err < 0) {
|
||||
dev_err(card->dev, "Failed to add MCBSP controls\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* AV jack detection */
|
||||
err = snd_soc_jack_new(codec, "AV Jack",
|
||||
SND_JACK_HEADSET | SND_JACK_VIDEOOUT,
|
||||
&rx51_av_jack);
|
||||
if (err) {
|
||||
dev_err(card->dev, "Failed to add AV Jack\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* prepare gpio for snd_soc_jack_add_gpios */
|
||||
rx51_av_jack_gpios[0].gpio = desc_to_gpio(pdata->jack_detection_gpio);
|
||||
devm_gpiod_put(card->dev, pdata->jack_detection_gpio);
|
||||
|
||||
err = snd_soc_jack_add_gpios(&rx51_av_jack,
|
||||
ARRAY_SIZE(rx51_av_jack_gpios),
|
||||
rx51_av_jack_gpios);
|
||||
if (err) {
|
||||
dev_err(card->dev, "Failed to add GPIOs\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int rx51_card_remove(struct snd_soc_card *card)
|
||||
{
|
||||
snd_soc_jack_free_gpios(&rx51_av_jack, ARRAY_SIZE(rx51_av_jack_gpios),
|
||||
rx51_av_jack_gpios);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link rx51_dai[] = {
|
||||
{
|
||||
.name = "TLV320AIC34",
|
||||
.stream_name = "AIC34",
|
||||
.cpu_dai_name = "omap-mcbsp.2",
|
||||
.codec_dai_name = "tlv320aic3x-hifi",
|
||||
.platform_name = "omap-mcbsp.2",
|
||||
.codec_name = "tlv320aic3x-codec.2-0018",
|
||||
.dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM,
|
||||
.init = rx51_aic34_init,
|
||||
.ops = &rx51_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_aux_dev rx51_aux_dev[] = {
|
||||
{
|
||||
.name = "TLV320AIC34b",
|
||||
.codec_name = "tlv320aic3x-codec.2-0019",
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_codec_conf rx51_codec_conf[] = {
|
||||
{
|
||||
.dev_name = "tlv320aic3x-codec.2-0019",
|
||||
.name_prefix = "b",
|
||||
},
|
||||
};
|
||||
|
||||
/* Audio card */
|
||||
static struct snd_soc_card rx51_sound_card = {
|
||||
.name = "RX-51",
|
||||
.owner = THIS_MODULE,
|
||||
.remove = rx51_card_remove,
|
||||
.dai_link = rx51_dai,
|
||||
.num_links = ARRAY_SIZE(rx51_dai),
|
||||
.aux_dev = rx51_aux_dev,
|
||||
.num_aux_devs = ARRAY_SIZE(rx51_aux_dev),
|
||||
.codec_conf = rx51_codec_conf,
|
||||
.num_configs = ARRAY_SIZE(rx51_codec_conf),
|
||||
|
||||
.controls = aic34_rx51_controls,
|
||||
.num_controls = ARRAY_SIZE(aic34_rx51_controls),
|
||||
.dapm_widgets = aic34_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(aic34_dapm_widgets),
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
};
|
||||
|
||||
static int rx51_soc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rx51_audio_pdata *pdata;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct snd_soc_card *card = &rx51_sound_card;
|
||||
int err;
|
||||
|
||||
if (!machine_is_nokia_rx51() && !of_machine_is_compatible("nokia,omap3-n900"))
|
||||
return -ENODEV;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
if (np) {
|
||||
struct device_node *dai_node;
|
||||
|
||||
dai_node = of_parse_phandle(np, "nokia,cpu-dai", 0);
|
||||
if (!dai_node) {
|
||||
dev_err(&pdev->dev, "McBSP node is not provided\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
rx51_dai[0].cpu_dai_name = NULL;
|
||||
rx51_dai[0].platform_name = NULL;
|
||||
rx51_dai[0].cpu_of_node = dai_node;
|
||||
rx51_dai[0].platform_of_node = dai_node;
|
||||
|
||||
dai_node = of_parse_phandle(np, "nokia,audio-codec", 0);
|
||||
if (!dai_node) {
|
||||
dev_err(&pdev->dev, "Codec node is not provided\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
rx51_dai[0].codec_name = NULL;
|
||||
rx51_dai[0].codec_of_node = dai_node;
|
||||
|
||||
dai_node = of_parse_phandle(np, "nokia,audio-codec", 1);
|
||||
if (!dai_node) {
|
||||
dev_err(&pdev->dev, "Auxiliary Codec node is not provided\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
rx51_aux_dev[0].codec_name = NULL;
|
||||
rx51_aux_dev[0].codec_of_node = dai_node;
|
||||
rx51_codec_conf[0].dev_name = NULL;
|
||||
rx51_codec_conf[0].of_node = dai_node;
|
||||
|
||||
dai_node = of_parse_phandle(np, "nokia,headphone-amplifier", 0);
|
||||
if (!dai_node) {
|
||||
dev_err(&pdev->dev, "Headphone amplifier node is not provided\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* TODO: tpa6130a2a driver supports only a single instance, so
|
||||
* this driver ignores the headphone-amplifier node for now.
|
||||
* It's already mandatory in the DT binding to be future proof.
|
||||
*/
|
||||
}
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (pdata == NULL) {
|
||||
dev_err(card->dev, "failed to create private data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
snd_soc_card_set_drvdata(card, pdata);
|
||||
|
||||
pdata->tvout_selection_gpio = devm_gpiod_get(card->dev,
|
||||
"tvout-selection");
|
||||
if (IS_ERR(pdata->tvout_selection_gpio)) {
|
||||
dev_err(card->dev, "could not get tvout selection gpio\n");
|
||||
return PTR_ERR(pdata->tvout_selection_gpio);
|
||||
}
|
||||
|
||||
err = gpiod_direction_output(pdata->tvout_selection_gpio, 0);
|
||||
if (err) {
|
||||
dev_err(card->dev, "could not setup tvout selection gpio\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
pdata->jack_detection_gpio = devm_gpiod_get(card->dev,
|
||||
"jack-detection");
|
||||
if (IS_ERR(pdata->jack_detection_gpio)) {
|
||||
dev_err(card->dev, "could not get jack detection gpio\n");
|
||||
return PTR_ERR(pdata->jack_detection_gpio);
|
||||
}
|
||||
|
||||
pdata->eci_sw_gpio = devm_gpiod_get(card->dev, "eci-switch");
|
||||
if (IS_ERR(pdata->eci_sw_gpio)) {
|
||||
dev_err(card->dev, "could not get eci switch gpio\n");
|
||||
return PTR_ERR(pdata->eci_sw_gpio);
|
||||
}
|
||||
|
||||
err = gpiod_direction_output(pdata->eci_sw_gpio, 1);
|
||||
if (err) {
|
||||
dev_err(card->dev, "could not setup eci switch gpio\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
pdata->speaker_amp_gpio = devm_gpiod_get(card->dev,
|
||||
"speaker-amplifier");
|
||||
if (IS_ERR(pdata->speaker_amp_gpio)) {
|
||||
dev_err(card->dev, "could not get speaker enable gpio\n");
|
||||
return PTR_ERR(pdata->speaker_amp_gpio);
|
||||
}
|
||||
|
||||
err = gpiod_direction_output(pdata->speaker_amp_gpio, 0);
|
||||
if (err) {
|
||||
dev_err(card->dev, "could not setup speaker enable gpio\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = devm_snd_soc_register_card(card->dev, card);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static const struct of_device_id rx51_audio_of_match[] = {
|
||||
{ .compatible = "nokia,n900-audio", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rx51_audio_of_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver rx51_soc_driver = {
|
||||
.driver = {
|
||||
.name = "rx51-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(rx51_audio_of_match),
|
||||
},
|
||||
.probe = rx51_soc_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(rx51_soc_driver);
|
||||
|
||||
MODULE_AUTHOR("Nokia Corporation");
|
||||
MODULE_DESCRIPTION("ALSA SoC Nokia RX-51");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:rx51-audio");
|
Loading…
Add table
Add a link
Reference in a new issue