--- /dev/null
+/*
+ * sound/arm/omap/omap-alsa-tsc2102-mixer.c
+ *
+ * Alsa mixer driver for TSC2102 chip for OMAP platforms.
+ *
+ * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
+ * Code based on the TSC2101 ALSA driver.
+ *
+ * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/types.h>
+#include <linux/spi/tsc2102.h>
+
+#include <asm/arch/omap-alsa.h>
+
+#include <sound/driver.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+
+#include "omap-alsa-tsc2102.h"
+#include "omap-alsa-dma.h"
+
+static int vol[2], mute[2], filter[2];
+
+/*
+ * Converts the Alsa mixer volume (0 - 100) to actual Digital
+ * Gain Control (DGC) value that can be written or read from the
+ * TSC2102 registers.
+ *
+ * Note that the number "OUTPUT_VOLUME_MAX" is smaller than
+ * OUTPUT_VOLUME_MIN because DGC works as a volume decreaser. (The
+ * higher the value sent to DAC, the more the volume of controlled
+ * channel is decreased)
+ */
+static void set_dac_gain_stereo(int left_ch, int right_ch)
+{
+ int lch, rch;
+
+ if (left_ch > 100)
+ vol[0] = 100;
+ else if (left_ch < 0)
+ vol[0] = 0;
+ else
+ vol[0] = left_ch;
+ lch = OUTPUT_VOLUME_MIN - vol[0] *
+ (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX) / 100;
+
+ if (right_ch > 100)
+ vol[1] = 100;
+ else if (right_ch < 0)
+ vol[1] = 0;
+ else
+ vol[1] = right_ch;
+ rch = OUTPUT_VOLUME_MIN - vol[1] *
+ (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX) / 100;
+
+ tsc2102_set_volume(lch, rch);
+}
+
+void init_playback_targets(void)
+{
+ set_dac_gain_stereo(DEFAULT_OUTPUT_VOLUME, DEFAULT_OUTPUT_VOLUME);
+
+ /* Unmute */
+ tsc2102_set_mute(0, 0);
+
+ mute[0] = mute[1] = 0;
+ filter[0] = filter[1] = 0;
+}
+
+/*
+ * Initializes TSC 2102 and playback target.
+ */
+void snd_omap_init_mixer(void)
+{
+ FN_IN;
+
+ init_playback_targets();
+
+ FN_OUT(0);
+}
+
+static int __pcm_playback_volume_info(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 100;
+ return 0;
+}
+
+static int __pcm_playback_volume_get(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ucontrol->value.integer.value[0] = vol[0]; /* L */
+ ucontrol->value.integer.value[1] = vol[1]; /* R */
+
+ return 0;
+}
+
+static int __pcm_playback_volume_put(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ set_dac_gain_stereo(
+ ucontrol->value.integer.value[0], /* L */
+ ucontrol->value.integer.value[1]); /* R */
+ return 1;
+}
+
+static int __pcm_playback_switch_info(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int __pcm_playback_switch_get(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ucontrol->value.integer.value[0] = !mute[0]; /* L */
+ ucontrol->value.integer.value[1] = !mute[1]; /* R */
+
+ return 0;
+}
+
+static int __pcm_playback_switch_put(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ mute[0] = (ucontrol->value.integer.value[0] == 0); /* L */
+ mute[1] = (ucontrol->value.integer.value[1] == 0); /* R */
+
+ tsc2102_set_mute(mute[0], mute[1]);
+ return 1;
+}
+
+static int __pcm_playback_deemphasis_info(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int __pcm_playback_deemphasis_get(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ucontrol->value.integer.value[0] = filter[0];
+ return 0;
+}
+
+static int __pcm_playback_deemphasis_put(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ filter[0] = (ucontrol->value.integer.value[0] > 0);
+
+ tsc2102_set_deemphasis(filter[0]);
+ return 1;
+}
+
+static int __pcm_playback_bassboost_info(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int __pcm_playback_bassboost_get(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ucontrol->value.integer.value[0] = filter[1];
+ return 0;
+}
+
+static int __pcm_playback_bassboost_put(
+ snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ filter[1] = (ucontrol->value.integer.value[0] > 0);
+
+ tsc2102_set_bassboost(filter[1]);
+ return 1;
+}
+
+static snd_kcontrol_new_t tsc2102_control[] __devinitdata = {
+ {
+ .name = "Master Playback Volume",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = __pcm_playback_volume_info,
+ .get = __pcm_playback_volume_get,
+ .put = __pcm_playback_volume_put,
+ },
+ {
+ .name = "Master Playback Switch",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = __pcm_playback_switch_info,
+ .get = __pcm_playback_switch_get,
+ .put = __pcm_playback_switch_put,
+ },
+ {
+ .name = "De-emphasis Filter Switch",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = __pcm_playback_deemphasis_info,
+ .get = __pcm_playback_deemphasis_get,
+ .put = __pcm_playback_deemphasis_put,
+ },
+ {
+ .name = "Bass-boost Filter Switch",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = __pcm_playback_bassboost_info,
+ .get = __pcm_playback_bassboost_get,
+ .put = __pcm_playback_bassboost_put,
+ },
+};
+
+#ifdef CONFIG_PM
+void snd_omap_suspend_mixer(void)
+{
+ /* Nothing to do */
+}
+
+void snd_omap_resume_mixer(void)
+{
+ /* The chip was reset, restore the last used values */
+ set_dac_gain_stereo(vol[0], vol[1]);
+
+ tsc2102_set_mute(mute[0], mute[1]);
+ tsc2102_set_deemphasis(filter[0]);
+ tsc2102_set_bassboost(filter[1]);
+}
+#endif
+
+int snd_omap_mixer(struct snd_card_omap_codec *tsc2102)
+{
+ int i, err;
+
+ if (!tsc2102)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(tsc2102_control); i ++) {
+ err = snd_ctl_add(tsc2102->card,
+ snd_ctl_new1(&tsc2102_control[i],
+ tsc2102->card));
+
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * sound/arm/omap/omap-alsa-tsc2102.c
+ *
+ * Alsa codec driver for TSC2102 chip for OMAP platforms.
+ *
+ * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
+ * Code based on the TSC2101 ALSA driver.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/soundcard.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/spi/tsc2102.h>
+
+#include <asm/arch/mcbsp.h>
+#include <asm/arch/dma.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/omap-alsa.h>
+
+#include "omap-alsa-tsc2102.h"
+
+static struct clk *tsc2102_bclk = 0;
+
+/*
+ * Hardware capabilities
+ */
+
+/* DAC sampling rates (BCLK = 12 MHz) */
+static unsigned int rates[] = {
+ 7350, 8000, 8820, 9600, 11025, 12000, 14700,
+ 16000, 22050, 24000, 29400, 32000, 44100, 48000,
+};
+
+static snd_pcm_hw_constraint_list_t tsc2102_hw_constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static snd_pcm_hardware_t tsc2102_snd_omap_alsa_playback = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_KNOT,
+ .rate_min = 7350,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 128 * 1024,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8 * 1024,
+ .periods_min = 16,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+#ifdef DUMP_TSC2102_AUDIO_REGISTERS
+static void dump_tsc2102_audio_regs(void) {
+ printk("TSC2102_AUDIO1_CTRL = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_AUDIO1_CTRL));
+ printk("TSC2102_DAC_GAIN_CTRL = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_DAC_GAIN_CTRL));
+ printk("TSC2102_AUDIO2_CTRL = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_AUDIO2_CTRL));
+ printk("TSC2102_DAC_POWER_CTRL = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_DAC_POWER_CTRL));
+ printk("TSC2102_AUDIO3_CTRL = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_AUDIO_CTRL_3));
+ printk("TSC2102_LCH_BASS_BOOST_N0 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N0));
+ printk("TSC2102_LCH_BASS_BOOST_N1 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N1));
+ printk("TSC2102_LCH_BASS_BOOST_N2 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N2));
+ printk("TSC2102_LCH_BASS_BOOST_N3 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N3));
+ printk("TSC2102_LCH_BASS_BOOST_N4 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N4));
+ printk("TSC2102_LCH_BASS_BOOST_N5 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N5));
+ printk("TSC2102_LCH_BASS_BOOST_D1 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D1));
+ printk("TSC2102_LCH_BASS_BOOST_D2 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D2));
+ printk("TSC2102_LCH_BASS_BOOST_D4 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D4));
+ printk("TSC2102_LCH_BASS_BOOST_D5 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D5));
+ printk("TSC2102_RCH_BASS_BOOST_N0 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N0));
+ printk("TSC2102_RCH_BASS_BOOST_N1 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N1));
+ printk("TSC2102_RCH_BASS_BOOST_N2 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N2));
+ printk("TSC2102_RCH_BASS_BOOST_N3 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N3));
+ printk("TSC2102_RCH_BASS_BOOST_N4 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N4));
+ printk("TSC2102_RCH_BASS_BOOST_N5 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N5));
+ printk("TSC2102_RCH_BASS_BOOST_D1 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D1));
+ printk("TSC2102_RCH_BASS_BOOST_D2 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D2));
+ printk("TSC2102_RCH_BASS_BOOST_D4 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D4));
+ printk("TSC2102_RCH_BASS_BOOST_D5 = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D5));
+ printk("TSC2102_PLL1_CTRL = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_PLL1_CTRL));
+ printk("TSC2102_PLL2_CTRL = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_PLL2_CTRL));
+ printk("TSC2102_AUDIO4_CTRL = 0x%04x\n",
+ tsc2102_read_sync(TSC2102_AUDIO4_CTRL));
+}
+#endif
+
+/*
+ * ALSA operations according to board file
+ */
+
+static long current_rate = 0;
+
+/*
+ * Sample rate changing
+ */
+static void tsc2102_set_samplerate(long sample_rate)
+{
+ int clkgdv = 0;
+ u16 srgr1, srgr2;
+
+ if (sample_rate == current_rate)
+ return;
+ current_rate = 0;
+
+ if (tsc2102_set_rate(sample_rate))
+ return;
+
+ /* Set the sample rate */
+#ifndef TSC_MASTER
+ clkgdv = CODEC_CLOCK / (sample_rate * (DEFAULT_BITPERSAMPLE * 2 - 1));
+ if (clkgdv)
+ srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv));
+ else
+ return;
+
+ /* Stereo Mode */
+ srgr2 = CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1);
+#else
+ srgr1 = FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv);
+ srgr2 = GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1);
+#endif
+ OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR2, srgr2);
+ OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR1, srgr1);
+ current_rate = sample_rate;
+}
+
+static void tsc2102_configure(void)
+{
+ tsc2102_dac_power(1);
+
+#ifdef TSC_MASTER
+ tsc2102_set_i2s_master(1);
+#else
+ tsc2102_set_i2s_master(0);
+#endif
+}
+
+/*
+ * Omap McBSP clock and Power Management configuration
+ *
+ * Here we have some functions that allow clock to be enabled and
+ * disabled only when needed. Besides doing clock configuration
+ * they allow turn audio on and off when necessary.
+ */
+
+/*
+ * Do clock framework bclk search
+ */
+static void tsc2102_clock_setup(void)
+{
+ tsc2102_bclk = clk_get(0, "bclk");
+}
+
+/*
+ * Do some sanity checks, set clock rate, start it.
+ */
+static int tsc2102_clock_on(void)
+{
+ int err;
+
+ if (clk_get_usecount(tsc2102_bclk) > 0 &&
+ clk_get_rate(tsc2102_bclk) != CODEC_CLOCK) {
+ /* BCLK is already in use */
+ printk(KERN_WARNING
+ "BCLK already in use at %d Hz. We change it to %d Hz\n",
+ (uint) clk_get_rate(tsc2102_bclk), CODEC_CLOCK);
+
+ err = clk_set_rate(tsc2102_bclk, CODEC_CLOCK);
+ if (err)
+ printk(KERN_WARNING "Cannot set BCLK clock rate "
+ "for TSC2102 codec, error code = %d\n", err);
+ }
+
+ clk_enable(tsc2102_bclk);
+ return 0;
+}
+
+/*
+ * Turn off the audio codec and then stop the clock.
+ */
+static int tsc2102_clock_off(void)
+{
+ DPRINTK("clock use count = %d\n", clk_get_usecount(tsc2102_bclk));
+
+ clk_disable(tsc2102_bclk);
+ return 0;
+}
+
+static int tsc2102_get_default_samplerate(void)
+{
+ return DEFAULT_SAMPLE_RATE;
+}
+
+static int snd_omap_alsa_tsc2102_suspend(
+ struct platform_device *pdev, pm_message_t state)
+{
+ tsc2102_dac_power(0);
+ current_rate = 0;
+
+ return snd_omap_alsa_suspend(pdev, state);
+}
+
+static int snd_omap_alsa_tsc2102_resume(struct platform_device *pdev)
+{
+ tsc2102_dac_power(1);
+
+#ifdef TSC_MASTER
+ tsc2102_set_i2s_master(1);
+#else
+ tsc2102_set_i2s_master(0);
+#endif
+
+ return snd_omap_alsa_resume(pdev);
+}
+
+static int __init snd_omap_alsa_tsc2102_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct omap_alsa_codec_config *codec_cfg = pdev->dev.platform_data;
+
+ if (codec_cfg) {
+ codec_cfg->hw_constraints_rates =
+ &tsc2102_hw_constraints_rates;
+ codec_cfg->snd_omap_alsa_playback =
+ &tsc2102_snd_omap_alsa_playback;
+ codec_cfg->codec_configure_dev = tsc2102_configure;
+ codec_cfg->codec_set_samplerate = tsc2102_set_samplerate;
+ codec_cfg->codec_clock_setup = tsc2102_clock_setup;
+ codec_cfg->codec_clock_on = tsc2102_clock_on;
+ codec_cfg->codec_clock_off = tsc2102_clock_off;
+ codec_cfg->get_default_samplerate =
+ tsc2102_get_default_samplerate;
+ ret = snd_omap_alsa_post_probe(pdev, codec_cfg);
+ } else
+ ret = -ENODEV;
+
+ return ret;
+}
+
+static int snd_omap_alsa_tsc2102_remove(struct platform_device *pdev)
+{
+ tsc2102_dac_power(0);
+
+ return snd_omap_alsa_remove(pdev);
+}
+
+static struct platform_driver omap_alsa_driver = {
+ .probe = snd_omap_alsa_tsc2102_probe,
+ .remove = snd_omap_alsa_tsc2102_remove,
+ .suspend = snd_omap_alsa_tsc2102_suspend,
+ .resume = snd_omap_alsa_tsc2102_resume,
+ .driver = {
+ .name = "tsc2102-alsa",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init omap_alsa_tsc2102_init(void)
+{
+ int err;
+
+ ADEBUG();
+ err = platform_driver_register(&omap_alsa_driver);
+
+ return err;
+}
+
+static void __exit omap_alsa_tsc2102_exit(void)
+{
+ ADEBUG();
+ platform_driver_unregister(&omap_alsa_driver);
+}
+
+module_init(omap_alsa_tsc2102_init);
+module_exit(omap_alsa_tsc2102_exit);