diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /sound/soc/omap |
Initial import
Diffstat (limited to 'sound/soc/omap')
-rw-r--r-- | sound/soc/omap/Kconfig | 124 | ||||
-rw-r--r-- | sound/soc/omap/Makefile | 31 | ||||
-rw-r--r-- | sound/soc/omap/am3517evm.c | 141 | ||||
-rw-r--r-- | sound/soc/omap/ams-delta.c | 600 | ||||
-rw-r--r-- | sound/soc/omap/mcbsp.c | 1100 | ||||
-rw-r--r-- | sound/soc/omap/mcbsp.h | 354 | ||||
-rw-r--r-- | sound/soc/omap/n810.c | 372 | ||||
-rw-r--r-- | sound/soc/omap/omap-abe-twl6040.c | 369 | ||||
-rw-r--r-- | sound/soc/omap/omap-dmic.c | 522 | ||||
-rw-r--r-- | sound/soc/omap/omap-dmic.h | 69 | ||||
-rw-r--r-- | sound/soc/omap/omap-hdmi-audio.c | 407 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcbsp.c | 857 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcbsp.h | 44 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcpdm.c | 526 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcpdm.h | 107 | ||||
-rw-r--r-- | sound/soc/omap/omap-pcm.c | 263 | ||||
-rw-r--r-- | sound/soc/omap/omap-twl4030.c | 367 | ||||
-rw-r--r-- | sound/soc/omap/omap3pandora.c | 317 | ||||
-rw-r--r-- | sound/soc/omap/osk5912.c | 187 | ||||
-rw-r--r-- | sound/soc/omap/rx51.c | 532 |
20 files changed, 7289 insertions, 0 deletions
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig new file mode 100644 index 000000000..30d010970 --- /dev/null +++ b/sound/soc/omap/Kconfig @@ -0,0 +1,124 @@ +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_AUDIO + tristate "HDMI audio support for OMAP4+ based SoCs" + depends on SND_OMAP_SOC + help + For HDMI audio to work OMAPDSS HDMI support should be + enabled. + The hdmi audio driver implements cpu-dai component using the + callbacks provided by OMAPDSS and registers the component + under DSS HDMI device. Omap-pcm is registered for platform + component also under DSS HDMI device. Dummy codec is used as + as codec component. The hdmi audio driver implements also + the card and registers it under its own platform device. + The device for the dirver is registered by OMAPDSS hdmi + driver. + +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 N900 (RX-51)" + depends on SND_OMAP_SOC && ARM && 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 N900 + cellphone. + +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 + depends on ARCH_OMAP4 || (SOC_OMAP5 && MFD_PALMAS) || COMPILE_TEST + select SND_OMAP_SOC_DMIC + select SND_OMAP_SOC_MCPDM + select SND_SOC_TWL6040 + select SND_SOC_DMIC + select COMMON_CLK_PALMAS if (SOC_OMAP5 && MFD_PALMAS) + 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) + - omap5-uevm (5432) + +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. diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile new file mode 100644 index 000000000..db36fbd5d --- /dev/null +++ b/sound/soc/omap/Makefile @@ -0,0 +1,31 @@ +# 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-audio-objs := omap-hdmi-audio.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_AUDIO) += snd-soc-omap-hdmi-audio.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 + +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 diff --git a/sound/soc/omap/am3517evm.c b/sound/soc/omap/am3517evm.c new file mode 100644 index 000000000..25a33e9d4 --- /dev/null +++ b/sound/soc/omap/am3517evm.c @@ -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"); diff --git a/sound/soc/omap/ams-delta.c b/sound/soc/omap/ams-delta.c new file mode 100644 index 000000000..16cc95fa4 --- /dev/null +++ b/sound/soc/omap/ams-delta.c @@ -0,0 +1,600 @@ +/* + * 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 struct snd_soc_ops ams_delta_ops; + + +/* 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_card_jack_new(card, "hook_switch", SND_JACK_HEADSET, + &ams_delta_hook_switch, NULL, 0); + 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, + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, +}; + +/* 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, + }, + .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); diff --git a/sound/soc/omap/mcbsp.c b/sound/soc/omap/mcbsp.c new file mode 100644 index 000000000..68a125205 --- /dev/null +++ b/sound/soc/omap/mcbsp.c @@ -0,0 +1,1100 @@ +/* + * sound/soc/omap/mcbsp.c + * + * Copyright (C) 2004 Nokia Corporation + * Author: Samuel Ortiz <samuel.ortiz@nokia.com> + * + * 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. + * + * Multichannel mode not supported. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> + +#include <linux/platform_data/asoc-ti-mcbsp.h> + +#include "mcbsp.h" + +static void omap_mcbsp_write(struct omap_mcbsp *mcbsp, u16 reg, u32 val) +{ + void __iomem *addr = mcbsp->io_base + reg * mcbsp->pdata->reg_step; + + if (mcbsp->pdata->reg_size == 2) { + ((u16 *)mcbsp->reg_cache)[reg] = (u16)val; + writew_relaxed((u16)val, addr); + } else { + ((u32 *)mcbsp->reg_cache)[reg] = val; + writel_relaxed(val, addr); + } +} + +static int omap_mcbsp_read(struct omap_mcbsp *mcbsp, u16 reg, bool from_cache) +{ + void __iomem *addr = mcbsp->io_base + reg * mcbsp->pdata->reg_step; + + if (mcbsp->pdata->reg_size == 2) { + return !from_cache ? readw_relaxed(addr) : + ((u16 *)mcbsp->reg_cache)[reg]; + } else { + return !from_cache ? readl_relaxed(addr) : + ((u32 *)mcbsp->reg_cache)[reg]; + } +} + +static void omap_mcbsp_st_write(struct omap_mcbsp *mcbsp, u16 reg, u32 val) +{ + writel_relaxed(val, mcbsp->st_data->io_base_st + reg); +} + +static int omap_mcbsp_st_read(struct omap_mcbsp *mcbsp, u16 reg) +{ + return readl_relaxed(mcbsp->st_data->io_base_st + reg); +} + +#define MCBSP_READ(mcbsp, reg) \ + omap_mcbsp_read(mcbsp, OMAP_MCBSP_REG_##reg, 0) +#define MCBSP_WRITE(mcbsp, reg, val) \ + omap_mcbsp_write(mcbsp, OMAP_MCBSP_REG_##reg, val) +#define MCBSP_READ_CACHE(mcbsp, reg) \ + omap_mcbsp_read(mcbsp, OMAP_MCBSP_REG_##reg, 1) + +#define MCBSP_ST_READ(mcbsp, reg) \ + omap_mcbsp_st_read(mcbsp, OMAP_ST_REG_##reg) +#define MCBSP_ST_WRITE(mcbsp, reg, val) \ + omap_mcbsp_st_write(mcbsp, OMAP_ST_REG_##reg, val) + +static void omap_mcbsp_dump_reg(struct omap_mcbsp *mcbsp) +{ + dev_dbg(mcbsp->dev, "**** McBSP%d regs ****\n", mcbsp->id); + dev_dbg(mcbsp->dev, "DRR2: 0x%04x\n", + MCBSP_READ(mcbsp, DRR2)); + dev_dbg(mcbsp->dev, "DRR1: 0x%04x\n", + MCBSP_READ(mcbsp, DRR1)); + dev_dbg(mcbsp->dev, "DXR2: 0x%04x\n", + MCBSP_READ(mcbsp, DXR2)); + dev_dbg(mcbsp->dev, "DXR1: 0x%04x\n", + MCBSP_READ(mcbsp, DXR1)); + dev_dbg(mcbsp->dev, "SPCR2: 0x%04x\n", + MCBSP_READ(mcbsp, SPCR2)); + dev_dbg(mcbsp->dev, "SPCR1: 0x%04x\n", + MCBSP_READ(mcbsp, SPCR1)); + dev_dbg(mcbsp->dev, "RCR2: 0x%04x\n", + MCBSP_READ(mcbsp, RCR2)); + dev_dbg(mcbsp->dev, "RCR1: 0x%04x\n", + MCBSP_READ(mcbsp, RCR1)); + dev_dbg(mcbsp->dev, "XCR2: 0x%04x\n", + MCBSP_READ(mcbsp, XCR2)); + dev_dbg(mcbsp->dev, "XCR1: 0x%04x\n", + MCBSP_READ(mcbsp, XCR1)); + dev_dbg(mcbsp->dev, "SRGR2: 0x%04x\n", + MCBSP_READ(mcbsp, SRGR2)); + dev_dbg(mcbsp->dev, "SRGR1: 0x%04x\n", + MCBSP_READ(mcbsp, SRGR1)); + dev_dbg(mcbsp->dev, "PCR0: 0x%04x\n", + MCBSP_READ(mcbsp, PCR0)); + dev_dbg(mcbsp->dev, "***********************\n"); +} + +static irqreturn_t omap_mcbsp_irq_handler(int irq, void *dev_id) +{ + struct omap_mcbsp *mcbsp = dev_id; + u16 irqst; + + irqst = MCBSP_READ(mcbsp, IRQST); + dev_dbg(mcbsp->dev, "IRQ callback : 0x%x\n", irqst); + + if (irqst & RSYNCERREN) + dev_err(mcbsp->dev, "RX Frame Sync Error!\n"); + if (irqst & RFSREN) + dev_dbg(mcbsp->dev, "RX Frame Sync\n"); + if (irqst & REOFEN) + dev_dbg(mcbsp->dev, "RX End Of Frame\n"); + if (irqst & RRDYEN) + dev_dbg(mcbsp->dev, "RX Buffer Threshold Reached\n"); + if (irqst & RUNDFLEN) + dev_err(mcbsp->dev, "RX Buffer Underflow!\n"); + if (irqst & ROVFLEN) + dev_err(mcbsp->dev, "RX Buffer Overflow!\n"); + + if (irqst & XSYNCERREN) + dev_err(mcbsp->dev, "TX Frame Sync Error!\n"); + if (irqst & XFSXEN) + dev_dbg(mcbsp->dev, "TX Frame Sync\n"); + if (irqst & XEOFEN) + dev_dbg(mcbsp->dev, "TX End Of Frame\n"); + if (irqst & XRDYEN) + dev_dbg(mcbsp->dev, "TX Buffer threshold Reached\n"); + if (irqst & XUNDFLEN) + dev_err(mcbsp->dev, "TX Buffer Underflow!\n"); + if (irqst & XOVFLEN) + dev_err(mcbsp->dev, "TX Buffer Overflow!\n"); + if (irqst & XEMPTYEOFEN) + dev_dbg(mcbsp->dev, "TX Buffer empty at end of frame\n"); + + MCBSP_WRITE(mcbsp, IRQST, irqst); + + return IRQ_HANDLED; +} + +static irqreturn_t omap_mcbsp_tx_irq_handler(int irq, void *dev_id) +{ + struct omap_mcbsp *mcbsp_tx = dev_id; + u16 irqst_spcr2; + + irqst_spcr2 = MCBSP_READ(mcbsp_tx, SPCR2); + dev_dbg(mcbsp_tx->dev, "TX IRQ callback : 0x%x\n", irqst_spcr2); + + if (irqst_spcr2 & XSYNC_ERR) { + dev_err(mcbsp_tx->dev, "TX Frame Sync Error! : 0x%x\n", + irqst_spcr2); + /* Writing zero to XSYNC_ERR clears the IRQ */ + MCBSP_WRITE(mcbsp_tx, SPCR2, MCBSP_READ_CACHE(mcbsp_tx, SPCR2)); + } + + return IRQ_HANDLED; +} + +static irqreturn_t omap_mcbsp_rx_irq_handler(int irq, void *dev_id) +{ + struct omap_mcbsp *mcbsp_rx = dev_id; + u16 irqst_spcr1; + + irqst_spcr1 = MCBSP_READ(mcbsp_rx, SPCR1); + dev_dbg(mcbsp_rx->dev, "RX IRQ callback : 0x%x\n", irqst_spcr1); + + if (irqst_spcr1 & RSYNC_ERR) { + dev_err(mcbsp_rx->dev, "RX Frame Sync Error! : 0x%x\n", + irqst_spcr1); + /* Writing zero to RSYNC_ERR clears the IRQ */ + MCBSP_WRITE(mcbsp_rx, SPCR1, MCBSP_READ_CACHE(mcbsp_rx, SPCR1)); + } + + return IRQ_HANDLED; +} + +/* + * omap_mcbsp_config simply write a config to the + * appropriate McBSP. + * You either call this function or set the McBSP registers + * by yourself before calling omap_mcbsp_start(). + */ +void omap_mcbsp_config(struct omap_mcbsp *mcbsp, + const struct omap_mcbsp_reg_cfg *config) +{ + dev_dbg(mcbsp->dev, "Configuring McBSP%d phys_base: 0x%08lx\n", + mcbsp->id, mcbsp->phys_base); + + /* We write the given config */ + MCBSP_WRITE(mcbsp, SPCR2, config->spcr2); + MCBSP_WRITE(mcbsp, SPCR1, config->spcr1); + MCBSP_WRITE(mcbsp, RCR2, config->rcr2); + MCBSP_WRITE(mcbsp, RCR1, config->rcr1); + MCBSP_WRITE(mcbsp, XCR2, config->xcr2); + MCBSP_WRITE(mcbsp, XCR1, config->xcr1); + MCBSP_WRITE(mcbsp, SRGR2, config->srgr2); + MCBSP_WRITE(mcbsp, SRGR1, config->srgr1); + MCBSP_WRITE(mcbsp, MCR2, config->mcr2); + MCBSP_WRITE(mcbsp, MCR1, config->mcr1); + MCBSP_WRITE(mcbsp, PCR0, config->pcr0); + if (mcbsp->pdata->has_ccr) { + MCBSP_WRITE(mcbsp, XCCR, config->xccr); + MCBSP_WRITE(mcbsp, RCCR, config->rccr); + } + /* Enable wakeup behavior */ + if (mcbsp->pdata->has_wakeup) + MCBSP_WRITE(mcbsp, WAKEUPEN, XRDYEN | RRDYEN); + + /* Enable TX/RX sync error interrupts by default */ + if (mcbsp->irq) + MCBSP_WRITE(mcbsp, IRQEN, RSYNCERREN | XSYNCERREN); +} + +/** + * omap_mcbsp_dma_reg_params - returns the address of mcbsp data register + * @id - mcbsp id + * @stream - indicates the direction of data flow (rx or tx) + * + * Returns the address of mcbsp data transmit register or data receive register + * to be used by DMA for transferring/receiving data based on the value of + * @stream for the requested mcbsp given by @id + */ +static int omap_mcbsp_dma_reg_params(struct omap_mcbsp *mcbsp, + unsigned int stream) +{ + int data_reg; + + if (mcbsp->pdata->reg_size == 2) { + if (stream) + data_reg = OMAP_MCBSP_REG_DRR1; + else + data_reg = OMAP_MCBSP_REG_DXR1; + } else { + if (stream) + data_reg = OMAP_MCBSP_REG_DRR; + else + data_reg = OMAP_MCBSP_REG_DXR; + } + + return mcbsp->phys_dma_base + data_reg * mcbsp->pdata->reg_step; +} + +static void omap_st_on(struct omap_mcbsp *mcbsp) +{ + unsigned int w; + + if (mcbsp->pdata->enable_st_clock) + mcbsp->pdata->enable_st_clock(mcbsp->id, 1); + + /* Enable McBSP Sidetone */ + w = MCBSP_READ(mcbsp, SSELCR); + MCBSP_WRITE(mcbsp, SSELCR, w | SIDETONEEN); + + /* Enable Sidetone from Sidetone Core */ + w = MCBSP_ST_READ(mcbsp, SSELCR); + MCBSP_ST_WRITE(mcbsp, SSELCR, w | ST_SIDETONEEN); +} + +static void omap_st_off(struct omap_mcbsp *mcbsp) +{ + unsigned int w; + + w = MCBSP_ST_READ(mcbsp, SSELCR); + MCBSP_ST_WRITE(mcbsp, SSELCR, w & ~(ST_SIDETONEEN)); + + w = MCBSP_READ(mcbsp, SSELCR); + MCBSP_WRITE(mcbsp, SSELCR, w & ~(SIDETONEEN)); + + if (mcbsp->pdata->enable_st_clock) + mcbsp->pdata->enable_st_clock(mcbsp->id, 0); +} + +static void omap_st_fir_write(struct omap_mcbsp *mcbsp, s16 *fir) +{ + u16 val, i; + + val = MCBSP_ST_READ(mcbsp, SSELCR); + + if (val & ST_COEFFWREN) + MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); + + MCBSP_ST_WRITE(mcbsp, SSELCR, val | ST_COEFFWREN); + + for (i = 0; i < 128; i++) + MCBSP_ST_WRITE(mcbsp, SFIRCR, fir[i]); + + i = 0; + + val = MCBSP_ST_READ(mcbsp, SSELCR); + while (!(val & ST_COEFFWRDONE) && (++i < 1000)) + val = MCBSP_ST_READ(mcbsp, SSELCR); + + MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); + + if (i == 1000) + dev_err(mcbsp->dev, "McBSP FIR load error!\n"); +} + +static void omap_st_chgain(struct omap_mcbsp *mcbsp) +{ + u16 w; + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + w = MCBSP_ST_READ(mcbsp, SSELCR); + + MCBSP_ST_WRITE(mcbsp, SGAINCR, ST_CH0GAIN(st_data->ch0gain) | \ + ST_CH1GAIN(st_data->ch1gain)); +} + +int omap_st_set_chgain(struct omap_mcbsp *mcbsp, int channel, s16 chgain) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + int ret = 0; + + if (!st_data) + return -ENOENT; + + spin_lock_irq(&mcbsp->lock); + if (channel == 0) + st_data->ch0gain = chgain; + else if (channel == 1) + st_data->ch1gain = chgain; + else + ret = -EINVAL; + + if (st_data->enabled) + omap_st_chgain(mcbsp); + spin_unlock_irq(&mcbsp->lock); + + return ret; +} + +int omap_st_get_chgain(struct omap_mcbsp *mcbsp, int channel, s16 *chgain) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + int ret = 0; + + if (!st_data) + return -ENOENT; + + spin_lock_irq(&mcbsp->lock); + if (channel == 0) + *chgain = st_data->ch0gain; + else if (channel == 1) + *chgain = st_data->ch1gain; + else + ret = -EINVAL; + spin_unlock_irq(&mcbsp->lock); + + return ret; +} + +static int omap_st_start(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (st_data->enabled && !st_data->running) { + omap_st_fir_write(mcbsp, st_data->taps); + omap_st_chgain(mcbsp); + + if (!mcbsp->free) { + omap_st_on(mcbsp); + st_data->running = 1; + } + } + + return 0; +} + +int omap_st_enable(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (!st_data) + return -ENODEV; + + spin_lock_irq(&mcbsp->lock); + st_data->enabled = 1; + omap_st_start(mcbsp); + spin_unlock_irq(&mcbsp->lock); + + return 0; +} + +static int omap_st_stop(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (st_data->running) { + if (!mcbsp->free) { + omap_st_off(mcbsp); + st_data->running = 0; + } + } + + return 0; +} + +int omap_st_disable(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + int ret = 0; + + if (!st_data) + return -ENODEV; + + spin_lock_irq(&mcbsp->lock); + omap_st_stop(mcbsp); + st_data->enabled = 0; + spin_unlock_irq(&mcbsp->lock); + + return ret; +} + +int omap_st_is_enabled(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (!st_data) + return -ENODEV; + + return st_data->enabled; +} + +/* + * omap_mcbsp_set_rx_threshold configures the transmit threshold in words. + * The threshold parameter is 1 based, and it is converted (threshold - 1) + * for the THRSH2 register. + */ +void omap_mcbsp_set_tx_threshold(struct omap_mcbsp *mcbsp, u16 threshold) +{ + if (mcbsp->pdata->buffer_size == 0) + return; + + if (threshold && threshold <= mcbsp->max_tx_thres) + MCBSP_WRITE(mcbsp, THRSH2, threshold - 1); +} + +/* + * omap_mcbsp_set_rx_threshold configures the receive threshold in words. + * The threshold parameter is 1 based, and it is converted (threshold - 1) + * for the THRSH1 register. + */ +void omap_mcbsp_set_rx_threshold(struct omap_mcbsp *mcbsp, u16 threshold) +{ + if (mcbsp->pdata->buffer_size == 0) + return; + + if (threshold && threshold <= mcbsp->max_rx_thres) + MCBSP_WRITE(mcbsp, THRSH1, threshold - 1); +} + +/* + * omap_mcbsp_get_tx_delay returns the number of used slots in the McBSP FIFO + */ +u16 omap_mcbsp_get_tx_delay(struct omap_mcbsp *mcbsp) +{ + u16 buffstat; + + if (mcbsp->pdata->buffer_size == 0) + return 0; + + /* Returns the number of free locations in the buffer */ + buffstat = MCBSP_READ(mcbsp, XBUFFSTAT); + + /* Number of slots are different in McBSP ports */ + return mcbsp->pdata->buffer_size - buffstat; +} + +/* + * omap_mcbsp_get_rx_delay returns the number of free slots in the McBSP FIFO + * to reach the threshold value (when the DMA will be triggered to read it) + */ +u16 omap_mcbsp_get_rx_delay(struct omap_mcbsp *mcbsp) +{ + u16 buffstat, threshold; + + if (mcbsp->pdata->buffer_size == 0) + return 0; + + /* Returns the number of used locations in the buffer */ + buffstat = MCBSP_READ(mcbsp, RBUFFSTAT); + /* RX threshold */ + threshold = MCBSP_READ(mcbsp, THRSH1); + + /* Return the number of location till we reach the threshold limit */ + if (threshold <= buffstat) + return 0; + else + return threshold - buffstat; +} + +int omap_mcbsp_request(struct omap_mcbsp *mcbsp) +{ + void *reg_cache; + int err; + + reg_cache = kzalloc(mcbsp->reg_cache_size, GFP_KERNEL); + if (!reg_cache) { + return -ENOMEM; + } + + spin_lock(&mcbsp->lock); + if (!mcbsp->free) { + dev_err(mcbsp->dev, "McBSP%d is currently in use\n", + mcbsp->id); + err = -EBUSY; + goto err_kfree; + } + + mcbsp->free = false; + mcbsp->reg_cache = reg_cache; + spin_unlock(&mcbsp->lock); + + if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->request) + mcbsp->pdata->ops->request(mcbsp->id - 1); + + /* + * Make sure that transmitter, receiver and sample-rate generator are + * not running before activating IRQs. + */ + MCBSP_WRITE(mcbsp, SPCR1, 0); + MCBSP_WRITE(mcbsp, SPCR2, 0); + + if (mcbsp->irq) { + err = request_irq(mcbsp->irq, omap_mcbsp_irq_handler, 0, + "McBSP", (void *)mcbsp); + if (err != 0) { + dev_err(mcbsp->dev, "Unable to request IRQ\n"); + goto err_clk_disable; + } + } else { + err = request_irq(mcbsp->tx_irq, omap_mcbsp_tx_irq_handler, 0, + "McBSP TX", (void *)mcbsp); + if (err != 0) { + dev_err(mcbsp->dev, "Unable to request TX IRQ\n"); + goto err_clk_disable; + } + + err = request_irq(mcbsp->rx_irq, omap_mcbsp_rx_irq_handler, 0, + "McBSP RX", (void *)mcbsp); + if (err != 0) { + dev_err(mcbsp->dev, "Unable to request RX IRQ\n"); + goto err_free_irq; + } + } + + return 0; +err_free_irq: + free_irq(mcbsp->tx_irq, (void *)mcbsp); +err_clk_disable: + if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->free) + mcbsp->pdata->ops->free(mcbsp->id - 1); + + /* Disable wakeup behavior */ + if (mcbsp->pdata->has_wakeup) + MCBSP_WRITE(mcbsp, WAKEUPEN, 0); + + spin_lock(&mcbsp->lock); + mcbsp->free = true; + mcbsp->reg_cache = NULL; +err_kfree: + spin_unlock(&mcbsp->lock); + kfree(reg_cache); + + return err; +} + +void omap_mcbsp_free(struct omap_mcbsp *mcbsp) +{ + void *reg_cache; + + if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->free) + mcbsp->pdata->ops->free(mcbsp->id - 1); + + /* Disable wakeup behavior */ + if (mcbsp->pdata->has_wakeup) + MCBSP_WRITE(mcbsp, WAKEUPEN, 0); + + /* Disable interrupt requests */ + if (mcbsp->irq) + MCBSP_WRITE(mcbsp, IRQEN, 0); + + if (mcbsp->irq) { + free_irq(mcbsp->irq, (void *)mcbsp); + } else { + free_irq(mcbsp->rx_irq, (void *)mcbsp); + free_irq(mcbsp->tx_irq, (void *)mcbsp); + } + + reg_cache = mcbsp->reg_cache; + + /* + * Select CLKS source from internal source unconditionally before + * marking the McBSP port as free. + * If the external clock source via MCBSP_CLKS pin has been selected the + * system will refuse to enter idle if the CLKS pin source is not reset + * back to internal source. + */ + if (!mcbsp_omap1()) + omap2_mcbsp_set_clks_src(mcbsp, MCBSP_CLKS_PRCM_SRC); + + spin_lock(&mcbsp->lock); + if (mcbsp->free) + dev_err(mcbsp->dev, "McBSP%d was not reserved\n", mcbsp->id); + else + mcbsp->free = true; + mcbsp->reg_cache = NULL; + spin_unlock(&mcbsp->lock); + + kfree(reg_cache); +} + +/* + * Here we start the McBSP, by enabling transmitter, receiver or both. + * If no transmitter or receiver is active prior calling, then sample-rate + * generator and frame sync are started. + */ +void omap_mcbsp_start(struct omap_mcbsp *mcbsp, int tx, int rx) +{ + int enable_srg = 0; + u16 w; + + if (mcbsp->st_data) + omap_st_start(mcbsp); + + /* Only enable SRG, if McBSP is master */ + w = MCBSP_READ_CACHE(mcbsp, PCR0); + if (w & (FSXM | FSRM | CLKXM | CLKRM)) + enable_srg = !((MCBSP_READ_CACHE(mcbsp, SPCR2) | + MCBSP_READ_CACHE(mcbsp, SPCR1)) & 1); + + if (enable_srg) { + /* Start the sample generator */ + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w | (1 << 6)); + } + + /* Enable transmitter and receiver */ + tx &= 1; + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w | tx); + + rx &= 1; + w = MCBSP_READ_CACHE(mcbsp, SPCR1); + MCBSP_WRITE(mcbsp, SPCR1, w | rx); + + /* + * Worst case: CLKSRG*2 = 8000khz: (1/8000) * 2 * 2 usec + * REVISIT: 100us may give enough time for two CLKSRG, however + * due to some unknown PM related, clock gating etc. reason it + * is now at 500us. + */ + udelay(500); + + if (enable_srg) { + /* Start frame sync */ + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w | (1 << 7)); + } + + if (mcbsp->pdata->has_ccr) { + /* Release the transmitter and receiver */ + w = MCBSP_READ_CACHE(mcbsp, XCCR); + w &= ~(tx ? XDISABLE : 0); + MCBSP_WRITE(mcbsp, XCCR, w); + w = MCBSP_READ_CACHE(mcbsp, RCCR); + w &= ~(rx ? RDISABLE : 0); + MCBSP_WRITE(mcbsp, RCCR, w); + } + + /* Dump McBSP Regs */ + omap_mcbsp_dump_reg(mcbsp); +} + +void omap_mcbsp_stop(struct omap_mcbsp *mcbsp, int tx, int rx) +{ + int idle; + u16 w; + + /* Reset transmitter */ + tx &= 1; + if (mcbsp->pdata->has_ccr) { + w = MCBSP_READ_CACHE(mcbsp, XCCR); + w |= (tx ? XDISABLE : 0); + MCBSP_WRITE(mcbsp, XCCR, w); + } + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w & ~tx); + + /* Reset receiver */ + rx &= 1; + if (mcbsp->pdata->has_ccr) { + w = MCBSP_READ_CACHE(mcbsp, RCCR); + w |= (rx ? RDISABLE : 0); + MCBSP_WRITE(mcbsp, RCCR, w); + } + w = MCBSP_READ_CACHE(mcbsp, SPCR1); + MCBSP_WRITE(mcbsp, SPCR1, w & ~rx); + + idle = !((MCBSP_READ_CACHE(mcbsp, SPCR2) | + MCBSP_READ_CACHE(mcbsp, SPCR1)) & 1); + + if (idle) { + /* Reset the sample rate generator */ + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w & ~(1 << 6)); + } + + if (mcbsp->st_data) + omap_st_stop(mcbsp); +} + +int omap2_mcbsp_set_clks_src(struct omap_mcbsp *mcbsp, u8 fck_src_id) +{ + struct clk *fck_src; + const char *src; + int r; + + if (fck_src_id == MCBSP_CLKS_PAD_SRC) + src = "pad_fck"; + else if (fck_src_id == MCBSP_CLKS_PRCM_SRC) + src = "prcm_fck"; + else + return -EINVAL; + + fck_src = clk_get(mcbsp->dev, src); + if (IS_ERR(fck_src)) { + dev_err(mcbsp->dev, "CLKS: could not clk_get() %s\n", src); + return -EINVAL; + } + + pm_runtime_put_sync(mcbsp->dev); + + r = clk_set_parent(mcbsp->fclk, fck_src); + if (r) { + dev_err(mcbsp->dev, "CLKS: could not clk_set_parent() to %s\n", + src); + clk_put(fck_src); + return r; + } + + pm_runtime_get_sync(mcbsp->dev); + + clk_put(fck_src); + + return 0; + +} + +#define max_thres(m) (mcbsp->pdata->buffer_size) +#define valid_threshold(m, val) ((val) <= max_thres(m)) +#define THRESHOLD_PROP_BUILDER(prop) \ +static ssize_t prop##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \ + \ + return sprintf(buf, "%u\n", mcbsp->prop); \ +} \ + \ +static ssize_t prop##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \ + unsigned long val; \ + int status; \ + \ + status = kstrtoul(buf, 0, &val); \ + if (status) \ + return status; \ + \ + if (!valid_threshold(mcbsp, val)) \ + return -EDOM; \ + \ + mcbsp->prop = val; \ + return size; \ +} \ + \ +static DEVICE_ATTR(prop, 0644, prop##_show, prop##_store); + +THRESHOLD_PROP_BUILDER(max_tx_thres); +THRESHOLD_PROP_BUILDER(max_rx_thres); + +static const char *dma_op_modes[] = { + "element", "threshold", +}; + +static ssize_t dma_op_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + int dma_op_mode, i = 0; + ssize_t len = 0; + const char * const *s; + + dma_op_mode = mcbsp->dma_op_mode; + + for (s = &dma_op_modes[i]; i < ARRAY_SIZE(dma_op_modes); s++, i++) { + if (dma_op_mode == i) + len += sprintf(buf + len, "[%s] ", *s); + else + len += sprintf(buf + len, "%s ", *s); + } + len += sprintf(buf + len, "\n"); + + return len; +} + +static ssize_t dma_op_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + const char * const *s; + int i = 0; + + for (s = &dma_op_modes[i]; i < ARRAY_SIZE(dma_op_modes); s++, i++) + if (sysfs_streq(buf, *s)) + break; + + if (i == ARRAY_SIZE(dma_op_modes)) + return -EINVAL; + + spin_lock_irq(&mcbsp->lock); + if (!mcbsp->free) { + size = -EBUSY; + goto unlock; + } + mcbsp->dma_op_mode = i; + +unlock: + spin_unlock_irq(&mcbsp->lock); + + return size; +} + +static DEVICE_ATTR(dma_op_mode, 0644, dma_op_mode_show, dma_op_mode_store); + +static const struct attribute *additional_attrs[] = { + &dev_attr_max_tx_thres.attr, + &dev_attr_max_rx_thres.attr, + &dev_attr_dma_op_mode.attr, + NULL, +}; + +static const struct attribute_group additional_attr_group = { + .attrs = (struct attribute **)additional_attrs, +}; + +static ssize_t st_taps_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + ssize_t status = 0; + int i; + + spin_lock_irq(&mcbsp->lock); + for (i = 0; i < st_data->nr_taps; i++) + status += sprintf(&buf[status], (i ? ", %d" : "%d"), + st_data->taps[i]); + if (i) + status += sprintf(&buf[status], "\n"); + spin_unlock_irq(&mcbsp->lock); + + return status; +} + +static ssize_t st_taps_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + int val, tmp, status, i = 0; + + spin_lock_irq(&mcbsp->lock); + memset(st_data->taps, 0, sizeof(st_data->taps)); + st_data->nr_taps = 0; + + do { + status = sscanf(buf, "%d%n", &val, &tmp); + if (status < 0 || status == 0) { + size = -EINVAL; + goto out; + } + if (val < -32768 || val > 32767) { + size = -EINVAL; + goto out; + } + st_data->taps[i++] = val; + buf += tmp; + if (*buf != ',') + break; + buf++; + } while (1); + + st_data->nr_taps = i; + +out: + spin_unlock_irq(&mcbsp->lock); + + return size; +} + +static DEVICE_ATTR(st_taps, 0644, st_taps_show, st_taps_store); + +static const struct attribute *sidetone_attrs[] = { + &dev_attr_st_taps.attr, + NULL, +}; + +static const struct attribute_group sidetone_attr_group = { + .attrs = (struct attribute **)sidetone_attrs, +}; + +static int omap_st_add(struct omap_mcbsp *mcbsp, struct resource *res) +{ + struct omap_mcbsp_st_data *st_data; + int err; + + st_data = devm_kzalloc(mcbsp->dev, sizeof(*mcbsp->st_data), GFP_KERNEL); + if (!st_data) + return -ENOMEM; + + st_data->io_base_st = devm_ioremap(mcbsp->dev, res->start, + resource_size(res)); + if (!st_data->io_base_st) + return -ENOMEM; + + err = sysfs_create_group(&mcbsp->dev->kobj, &sidetone_attr_group); + if (err) + return err; + + mcbsp->st_data = st_data; + return 0; +} + +/* + * McBSP1 and McBSP3 are directly mapped on 1610 and 1510. + * 730 has only 2 McBSP, and both of them are MPU peripherals. + */ +int omap_mcbsp_init(struct platform_device *pdev) +{ + struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev); + struct resource *res; + int ret = 0; + + spin_lock_init(&mcbsp->lock); + mcbsp->free = true; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu"); + if (!res) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(mcbsp->dev, "invalid memory resource\n"); + return -ENOMEM; + } + } + if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), + dev_name(&pdev->dev))) { + dev_err(mcbsp->dev, "memory region already claimed\n"); + return -ENODEV; + } + + mcbsp->phys_base = res->start; + mcbsp->reg_cache_size = resource_size(res); + mcbsp->io_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!mcbsp->io_base) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (!res) + mcbsp->phys_dma_base = mcbsp->phys_base; + else + mcbsp->phys_dma_base = res->start; + + /* + * OMAP1, 2 uses two interrupt lines: TX, RX + * OMAP2430, OMAP3 SoC have combined IRQ line as well. + * OMAP4 and newer SoC only have the combined IRQ line. + * Use the combined IRQ if available since it gives better debugging + * possibilities. + */ + mcbsp->irq = platform_get_irq_byname(pdev, "common"); + if (mcbsp->irq == -ENXIO) { + mcbsp->tx_irq = platform_get_irq_byname(pdev, "tx"); + + if (mcbsp->tx_irq == -ENXIO) { + mcbsp->irq = platform_get_irq(pdev, 0); + mcbsp->tx_irq = 0; + } else { + mcbsp->rx_irq = platform_get_irq_byname(pdev, "rx"); + mcbsp->irq = 0; + } + } + + if (!pdev->dev.of_node) { + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); + if (!res) { + dev_err(&pdev->dev, "invalid tx DMA channel\n"); + return -ENODEV; + } + mcbsp->dma_req[0] = res->start; + mcbsp->dma_data[0].filter_data = &mcbsp->dma_req[0]; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); + if (!res) { + dev_err(&pdev->dev, "invalid rx DMA channel\n"); + return -ENODEV; + } + mcbsp->dma_req[1] = res->start; + mcbsp->dma_data[1].filter_data = &mcbsp->dma_req[1]; + } else { + mcbsp->dma_data[0].filter_data = "tx"; + mcbsp->dma_data[1].filter_data = "rx"; + } + + mcbsp->dma_data[0].addr = omap_mcbsp_dma_reg_params(mcbsp, 0); + mcbsp->dma_data[0].maxburst = 4; + + mcbsp->dma_data[1].addr = omap_mcbsp_dma_reg_params(mcbsp, 1); + mcbsp->dma_data[1].maxburst = 4; + + mcbsp->fclk = clk_get(&pdev->dev, "fck"); + if (IS_ERR(mcbsp->fclk)) { + ret = PTR_ERR(mcbsp->fclk); + dev_err(mcbsp->dev, "unable to get fck: %d\n", ret); + return ret; + } + + mcbsp->dma_op_mode = MCBSP_DMA_MODE_ELEMENT; + if (mcbsp->pdata->buffer_size) { + /* + * Initially configure the maximum thresholds to a safe value. + * The McBSP FIFO usage with these values should not go under + * 16 locations. + * If the whole FIFO without safety buffer is used, than there + * is a possibility that the DMA will be not able to push the + * new data on time, causing channel shifts in runtime. + */ + mcbsp->max_tx_thres = max_thres(mcbsp) - 0x10; + mcbsp->max_rx_thres = max_thres(mcbsp) - 0x10; + + ret = sysfs_create_group(&mcbsp->dev->kobj, + &additional_attr_group); + if (ret) { + dev_err(mcbsp->dev, + "Unable to create additional controls\n"); + goto err_thres; + } + } else { + mcbsp->max_tx_thres = -EINVAL; + mcbsp->max_rx_thres = -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sidetone"); + if (res) { + ret = omap_st_add(mcbsp, res); + if (ret) { + dev_err(mcbsp->dev, + "Unable to create sidetone controls\n"); + goto err_st; + } + } + + return 0; + +err_st: + if (mcbsp->pdata->buffer_size) + sysfs_remove_group(&mcbsp->dev->kobj, &additional_attr_group); +err_thres: + clk_put(mcbsp->fclk); + return ret; +} + +void omap_mcbsp_sysfs_remove(struct omap_mcbsp *mcbsp) +{ + if (mcbsp->pdata->buffer_size) + sysfs_remove_group(&mcbsp->dev->kobj, &additional_attr_group); + + if (mcbsp->st_data) + sysfs_remove_group(&mcbsp->dev->kobj, &sidetone_attr_group); +} diff --git a/sound/soc/omap/mcbsp.h b/sound/soc/omap/mcbsp.h new file mode 100644 index 000000000..96d1b086b --- /dev/null +++ b/sound/soc/omap/mcbsp.h @@ -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 */ diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c new file mode 100644 index 000000000..dcb5336b5 --- /dev/null +++ b/sound/soc/omap/n810.c @@ -0,0 +1,372 @@ +/* + * 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; + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); + + n810_ext_control(&rtd->card->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), +}; + +/* 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, + .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), + .fully_routed = true, +}; + +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"); diff --git a/sound/soc/omap/omap-abe-twl6040.c b/sound/soc/omap/omap-abe-twl6040.c new file mode 100644 index 000000000..0843a68f2 --- /dev/null +++ b/sound/soc/omap/omap-abe-twl6040.c @@ -0,0 +1,369 @@ +/* + * 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_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET, &hs_jack, + hs_jack_pins, + ARRAY_SIZE(hs_jack_pins)); + if (ret) + return ret; + + twl6040_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADSET); + } + + return 0; +} + +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", + .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"); diff --git a/sound/soc/omap/omap-dmic.c b/sound/soc/omap/omap-dmic.c new file mode 100644 index 000000000..09db2aec1 --- /dev/null +++ b/sound/soc/omap/omap-dmic.c @@ -0,0 +1,522 @@ +/* + * 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", + .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"); diff --git a/sound/soc/omap/omap-dmic.h b/sound/soc/omap/omap-dmic.h new file mode 100644 index 000000000..231e728bf --- /dev/null +++ b/sound/soc/omap/omap-dmic.h @@ -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 diff --git a/sound/soc/omap/omap-hdmi-audio.c b/sound/soc/omap/omap-hdmi-audio.c new file mode 100644 index 000000000..4775da4c4 --- /dev/null +++ b/sound/soc/omap/omap-hdmi-audio.c @@ -0,0 +1,407 @@ +/* + * omap-hdmi-audio.c -- OMAP4+ DSS HDMI audio support library + * + * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Jyri Sarha <jsarha@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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <uapi/sound/asound.h> +#include <sound/asoundef.h> +#include <sound/omap-pcm.h> +#include <sound/omap-hdmi-audio.h> +#include <video/omapdss.h> + +#define DRV_NAME "omap-hdmi-audio" + +struct hdmi_audio_data { + struct snd_soc_card *card; + + const struct omap_hdmi_audio_ops *ops; + struct device *dssdev; + struct snd_dmaengine_dai_dma_data dma_data; + struct omap_dss_audio dss_audio; + struct snd_aes_iec958 iec; + struct snd_cea_861_aud_if cea; + + struct mutex current_stream_lock; + struct snd_pcm_substream *current_stream; +}; + +static +struct hdmi_audio_data *card_drvdata_substream(struct snd_pcm_substream *ss) +{ + struct snd_soc_pcm_runtime *rtd = ss->private_data; + + return snd_soc_card_get_drvdata(rtd->card); +} + +static void hdmi_dai_abort(struct device *dev) +{ + struct hdmi_audio_data *ad = dev_get_drvdata(dev); + + mutex_lock(&ad->current_stream_lock); + if (ad->current_stream && ad->current_stream->runtime && + snd_pcm_running(ad->current_stream)) { + dev_err(dev, "HDMI display disabled, aborting playback\n"); + snd_pcm_stream_lock_irq(ad->current_stream); + snd_pcm_stop(ad->current_stream, SNDRV_PCM_STATE_DISCONNECTED); + snd_pcm_stream_unlock_irq(ad->current_stream); + } + mutex_unlock(&ad->current_stream_lock); +} + +static int hdmi_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_audio_data *ad = card_drvdata_substream(substream); + int ret; + /* + * 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 + */ + ret = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128); + if (ret < 0) { + dev_err(dai->dev, "could not apply constraint\n"); + return ret; + } + + snd_soc_dai_set_dma_data(dai, substream, &ad->dma_data); + + mutex_lock(&ad->current_stream_lock); + ad->current_stream = substream; + mutex_unlock(&ad->current_stream_lock); + + ret = ad->ops->audio_startup(ad->dssdev, hdmi_dai_abort); + + if (ret) { + mutex_lock(&ad->current_stream_lock); + ad->current_stream = NULL; + mutex_unlock(&ad->current_stream_lock); + } + + return ret; +} + +static int hdmi_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdmi_audio_data *ad = card_drvdata_substream(substream); + struct snd_aes_iec958 *iec = &ad->iec; + struct snd_cea_861_aud_if *cea = &ad->cea; + + WARN_ON(ad->current_stream != substream); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + ad->dma_data.maxburst = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + ad->dma_data.maxburst = 32; + break; + default: + dev_err(dai->dev, "format not supported!\n"); + return -EINVAL; + } + + ad->dss_audio.iec = iec; + ad->dss_audio.cea = cea; + /* + * 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[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); + + return ad->ops->audio_config(ad->dssdev, &ad->dss_audio); +} + +static int hdmi_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct hdmi_audio_data *ad = card_drvdata_substream(substream); + int err = 0; + + WARN_ON(ad->current_stream != substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + err = ad->ops->audio_start(ad->dssdev); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ad->ops->audio_stop(ad->dssdev); + break; + default: + err = -EINVAL; + } + return err; +} + +static void hdmi_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_audio_data *ad = card_drvdata_substream(substream); + + WARN_ON(ad->current_stream != substream); + + ad->ops->audio_shutdown(ad->dssdev); + + mutex_lock(&ad->current_stream_lock); + ad->current_stream = NULL; + mutex_unlock(&ad->current_stream_lock); +} + +static const struct snd_soc_dai_ops hdmi_dai_ops = { + .startup = hdmi_dai_startup, + .hw_params = hdmi_dai_hw_params, + .trigger = hdmi_dai_trigger, + .shutdown = hdmi_dai_shutdown, +}; + +static const struct snd_soc_component_driver omap_hdmi_component = { + .name = "omapdss_hdmi", +}; + +static struct snd_soc_dai_driver omap5_hdmi_dai = { + .name = "omap5-hdmi-dai", + .playback = { + .channels_min = 2, + .channels_max = 8, + .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), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &hdmi_dai_ops, +}; + +static struct snd_soc_dai_driver omap4_hdmi_dai = { + .name = "omap4-hdmi-dai", + .playback = { + .channels_min = 2, + .channels_max = 8, + .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), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &hdmi_dai_ops, +}; + +static int omap_hdmi_audio_probe(struct platform_device *pdev) +{ + struct omap_hdmi_audio_pdata *ha = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct hdmi_audio_data *ad; + struct snd_soc_dai_driver *dai_drv; + struct snd_soc_card *card; + int ret; + + if (!ha) { + dev_err(dev, "No platform data\n"); + return -EINVAL; + } + + ad = devm_kzalloc(dev, sizeof(*ad), GFP_KERNEL); + if (!ad) + return -ENOMEM; + ad->dssdev = ha->dev; + ad->ops = ha->ops; + ad->dma_data.addr = ha->audio_dma_addr; + ad->dma_data.filter_data = "audio_tx"; + ad->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + mutex_init(&ad->current_stream_lock); + + switch (ha->dss_version) { + case OMAPDSS_VER_OMAP4430_ES1: + case OMAPDSS_VER_OMAP4430_ES2: + case OMAPDSS_VER_OMAP4: + dai_drv = &omap4_hdmi_dai; + break; + case OMAPDSS_VER_OMAP5: + dai_drv = &omap5_hdmi_dai; + break; + default: + return -EINVAL; + } + ret = snd_soc_register_component(ad->dssdev, &omap_hdmi_component, + dai_drv, 1); + if (ret) + return ret; + + ret = omap_pcm_platform_register(ad->dssdev); + if (ret) + return ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = devm_kasprintf(dev, GFP_KERNEL, + "HDMI %s", dev_name(ad->dssdev)); + card->owner = THIS_MODULE; + card->dai_link = + devm_kzalloc(dev, sizeof(*(card->dai_link)), GFP_KERNEL); + card->dai_link->name = card->name; + card->dai_link->stream_name = card->name; + card->dai_link->cpu_dai_name = dev_name(ad->dssdev); + card->dai_link->platform_name = dev_name(ad->dssdev); + card->dai_link->codec_name = "snd-soc-dummy"; + card->dai_link->codec_dai_name = "snd-soc-dummy-dai"; + card->num_links = 1; + card->dev = dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(dev, "snd_soc_register_card failed (%d)\n", ret); + snd_soc_unregister_component(ad->dssdev); + return ret; + } + + ad->card = card; + snd_soc_card_set_drvdata(card, ad); + + dev_set_drvdata(dev, ad); + + return 0; +} + +static int omap_hdmi_audio_remove(struct platform_device *pdev) +{ + struct hdmi_audio_data *ad = platform_get_drvdata(pdev); + + snd_soc_unregister_card(ad->card); + snd_soc_unregister_component(ad->dssdev); + return 0; +} + +static struct platform_driver hdmi_audio_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = omap_hdmi_audio_probe, + .remove = omap_hdmi_audio_remove, +}; + +module_platform_driver(hdmi_audio_driver); + +MODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>"); +MODULE_DESCRIPTION("OMAP HDMI Audio Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c new file mode 100644 index 000000000..fd99d89de --- /dev/null +++ b/sound/soc/omap/omap-mcbsp.c @@ -0,0 +1,857 @@ +/* + * 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; + regs->pcr0 |= SCLKME; + /* + * If McBSP is master but yet the CLKX/CLKR pin drives the SRG, + * disable output on those pins. This enables to inject the + * reference clock through CLKX/CLKR. For this to work + * set_dai_sysclk() _needs_ to be called after set_dai_fmt(). + */ + regs->pcr0 &= ~CLKXM; + break; + case OMAP_MCBSP_SYSCLK_CLKR_EXT: + regs->pcr0 |= SCLKME; + /* Disable ouput on CLKR pin in master mode */ + regs->pcr0 &= ~CLKRM; + 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", + .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"); diff --git a/sound/soc/omap/omap-mcbsp.h b/sound/soc/omap/omap-mcbsp.h new file mode 100644 index 000000000..2e3369c27 --- /dev/null +++ b/sound/soc/omap/omap-mcbsp.h @@ -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 diff --git a/sound/soc/omap/omap-mcpdm.c b/sound/soc/omap/omap-mcpdm.c new file mode 100644 index 000000000..b837265ac --- /dev/null +++ b/sound/soc/omap/omap-mcpdm.c @@ -0,0 +1,526 @@ +/* + * 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", + .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"); diff --git a/sound/soc/omap/omap-mcpdm.h b/sound/soc/omap/omap-mcpdm.h new file mode 100644 index 000000000..de8cf2659 --- /dev/null +++ b/sound/soc/omap/omap-mcpdm.h @@ -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__ */ diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c new file mode 100644 index 000000000..6bb623a2a --- /dev/null +++ b/sound/soc/omap/omap-pcm.c @@ -0,0 +1,263 @@ +/* + * 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 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, +}; + +/* sDMA supports only 1, 2, and 4 byte transfer elements. */ +static void omap_pcm_limit_supported_formats(void) +{ + int i; + + for (i = 0; i < SNDRV_PCM_FORMAT_LAST; i++) { + switch (snd_pcm_format_physical_width(i)) { + case 8: + case 16: + case 32: + omap_pcm_hardware.formats |= (1LL << i); + break; + default: + break; + } + } +} + +/* 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) +{ + omap_pcm_limit_supported_formats(); + 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"); diff --git a/sound/soc/omap/omap-twl4030.c b/sound/soc/omap/omap-twl4030.c new file mode 100644 index 000000000..3673ada43 --- /dev/null +++ b/sound/soc/omap/omap-twl4030.c @@ -0,0 +1,367 @@ +/* + * 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; + unsigned int fmt; + + 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; + } + + return snd_soc_runtime_set_dai_fmt(rtd, fmt); +} + +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_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET, &priv->hs_jack, + hs_jack_pins, + ARRAY_SIZE(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", + .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"); diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c new file mode 100644 index 000000000..076bec606 --- /dev/null +++ b/sound/soc/omap/omap3pandora.c @@ -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"); diff --git a/sound/soc/omap/osk5912.c b/sound/soc/omap/osk5912.c new file mode 100644 index 000000000..aa4053bf6 --- /dev/null +++ b/sound/soc/omap/osk5912.c @@ -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"); diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c new file mode 100644 index 000000000..c2ddf0fbf --- /dev/null +++ b/sound/soc/omap/rx51.c @@ -0,0 +1,532 @@ +/* + * 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, "DMic"}, + {"DMic", NULL, "Mic Bias"}, + + {"b LINE2R", NULL, "MONO_LOUT"}, + {"Earphone", NULL, "b HPLOUT"}, + + {"LINE1L", NULL, "HS Mic"}, + {"HS Mic", NULL, "b Mic Bias"}, +}; + +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_card_jack_new(rtd->card, "AV Jack", + SND_JACK_HEADSET | SND_JACK_VIDEOOUT, + &rx51_av_jack, NULL, 0); + 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", + .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"); |