* [patch 3/5] cs42l51: add asoc driver
2010-05-25 12:22 [patch 0/5] kirkwood openrd client audio support - v3 apatard
2010-05-25 12:22 ` [patch 1/5] orion/kirkwood: add audio functions apatard
2010-05-25 12:22 ` [patch 2/5] openrd-client: initialise audio apatard
@ 2010-05-25 12:22 ` apatard
2010-05-25 22:51 ` Mark Brown
2010-05-25 12:22 ` [patch 4/5] orion/kirkwood: Add i2s support apatard
` (2 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: apatard @ 2010-05-25 12:22 UTC (permalink / raw)
To: alsa-devel
Cc: nico, broonie, saeed, Arnaud Patard, tbm, linux-arm-kernel, lrg
[-- Attachment #1: cs42l51_support.patch --]
[-- Type: text/plain, Size: 29854 bytes --]
This patch is adding a ASoC driver for the cs42l51 from Cirrus Logic.
Master mode and spi mode are not supported.
Signed-off-by: Arnaud Patard <apatard@mandriva.com>
Index: sound-2.6/sound/soc/codecs/cs42l51.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ sound-2.6/sound/soc/codecs/cs42l51.c 2010-05-24 23:51:02.892579937 +0200
@@ -0,0 +1,770 @@
+/*
+ * cs42l51.c
+ *
+ * ASoC Driver for Cirrus Logic CS42L51 codecs
+ *
+ * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ *
+ * Based on cs4270.c - Copyright (c) Freescale Semiconductor
+ *
+ * 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
+ *
+ * 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.
+ *
+ * For now:
+ * - Only I2C is support. Not SPI
+ * - master mode *NOT* supported
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm.h>
+#include <linux/i2c.h>
+
+#include "cs42l51.h"
+
+enum master_slave_mode {
+ MODE_SLAVE,
+ MODE_SLAVE_AUTO,
+ MODE_MASTER,
+};
+
+struct cs42l51_private {
+ unsigned int mclk;
+ unsigned int audio_mode; /* The mode (I2S or left-justified) */
+ enum master_slave_mode func;
+ struct snd_soc_codec codec;
+ u8 reg_cache[CS42L51_NUMREGS];
+};
+
+static struct snd_soc_codec *cs42l51_codec;
+
+#define CS42L51_FORMATS ( \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE)
+
+static int cs42l51_fill_cache(struct snd_soc_codec *codec)
+{
+ u8 *cache = codec->reg_cache + 1;
+ struct i2c_client *i2c_client = codec->control_data;
+ s32 length;
+
+ length = i2c_smbus_read_i2c_block_data(i2c_client,
+ CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache);
+ if (length != CS42L51_NUMREGS) {
+ dev_err(&i2c_client->dev,
+ "I2C read failure, addr=0x%x (ret=%d vs %d)\n",
+ i2c_client->addr, length, CS42L51_NUMREGS);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int cs42l51_i2c_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_codec *codec;
+ struct cs42l51_private *cs42l51;
+ int ret = 0;
+ int reg;
+
+ if (cs42l51_codec)
+ return -EBUSY;
+
+ /* Verify that we have a CS42L51 */
+ ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "failed to read I2C\n");
+ goto error;
+ }
+
+ if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) &&
+ (ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) {
+ dev_err(&i2c_client->dev, "Invalid chip id\n");
+ ret = -ENODEV;
+ goto error;
+ }
+
+ dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n",
+ ret & 7);
+
+ cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL);
+ if (!cs42l51) {
+ dev_err(&i2c_client->dev, "could not allocate codec\n");
+ return -ENOMEM;
+ }
+ codec = &cs42l51->codec;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->dev = &i2c_client->dev;
+ codec->name = "CS42L51";
+ codec->owner = THIS_MODULE;
+ codec->dai = &cs42l51_dai;
+ codec->num_dai = 1;
+ snd_soc_codec_set_drvdata(codec, cs42l51);
+
+ codec->control_data = i2c_client;
+ codec->reg_cache = cs42l51->reg_cache;
+ codec->reg_cache_size = CS42L51_NUMREGS;
+ i2c_set_clientdata(i2c_client, codec);
+
+ ret = cs42l51_fill_cache(codec);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "failed to fill register cache\n");
+ goto error_alloc;
+ }
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "Failed to set cache I/O: %d\n", ret);
+ goto error_alloc;
+ }
+
+ /*
+ * DAC configuration
+ * - Use signal processor
+ * - auto mute
+ * - vol changes immediate
+ * - no de-emphasize
+ */
+ reg = CS42L51_DAC_CTL_DATA_SEL(1)
+ | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0);
+ ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg);
+ if (ret < 0)
+ goto error_alloc;
+
+ /* route microphone */
+ ret = snd_soc_write(codec, CS42L51_ADC_INPUT, 0xF0);
+ if (ret < 0)
+ goto error_alloc;
+
+ cs42l51_dai.dev = codec->dev;
+ cs42l51_codec = codec;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ goto error_alloc;
+ }
+
+ ret = snd_soc_register_dai(&cs42l51_dai);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "failed to register DAIe\n");
+ goto error_reg;
+ }
+
+ return 0;
+
+error_reg:
+ snd_soc_unregister_codec(codec);
+error_alloc:
+ kfree(cs42l51);
+error:
+ return ret;
+}
+
+static int cs42l51_i2c_remove(struct i2c_client *client)
+{
+ struct cs42l51_private *cs42l51 = i2c_get_clientdata(client);
+ snd_soc_unregister_dai(&cs42l51_dai);
+ snd_soc_unregister_codec(cs42l51_codec);
+ cs42l51_codec = NULL;
+ kfree(cs42l51);
+ return 0;
+}
+
+
+static const struct i2c_device_id cs42l51_id[] = {
+ {"cs42l51", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs42l51_id);
+
+static struct i2c_driver cs42l51_i2c_driver = {
+ .driver = {
+ .name = "CS42L51 I2C",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs42l51_id,
+ .probe = cs42l51_i2c_probe,
+ .remove = cs42l51_i2c_remove,
+};
+
+static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3;
+
+ switch (value) {
+ default:
+ case 0:
+ ucontrol->value.integer.value[0] = 0;
+ break;
+ /* same value : (L+R)/2 and (R+L)/2 */
+ case 1:
+ case 2:
+ ucontrol->value.integer.value[0] = 1;
+ break;
+ case 3:
+ ucontrol->value.integer.value[0] = 2;
+ break;
+ }
+
+ return 0;
+}
+
+#define CHAN_MIX_NORMAL 0x00
+#define CHAN_MIX_BOTH 0x55
+#define CHAN_MIX_SWAP 0xFF
+
+static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+
+ switch (ucontrol->value.integer.value[0]) {
+ default:
+ case 0:
+ val = CHAN_MIX_NORMAL;
+ break;
+ case 1:
+ val = CHAN_MIX_BOTH;
+ break;
+ case 2:
+ val = CHAN_MIX_SWAP;
+ break;
+ }
+
+ snd_soc_write(codec, CS42L51_PCM_MIXER, val);
+
+ return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0);
+static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0);
+/* This is a lie. after -102 db, it stays at -102 */
+/* maybe a range would be better */
+static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0);
+
+static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0);
+static const char *chan_mix[] = {
+ "L R",
+ "L+R",
+ "R L",
+};
+
+static const struct soc_enum cs42l51_chan_mix =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix);
+
+static const struct snd_kcontrol_new cs42l51_snd_controls[] = {
+ SOC_DOUBLE_R_SX_TLV("PCM Playback Volume",
+ CS42L51_PCMA_VOL, CS42L51_PCMB_VOL,
+ 7, 0xffffff99, 0x18, adc_pcm_tlv),
+ SOC_DOUBLE_R("PCM Playback Switch",
+ CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1),
+ SOC_DOUBLE_R_SX_TLV("Analog Playback Volume",
+ CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL,
+ 8, 0xffffff19, 0x18, aout_tlv),
+ SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume",
+ CS42L51_ADCA_VOL, CS42L51_ADCB_VOL,
+ 7, 0xffffff99, 0x18, adc_pcm_tlv),
+ SOC_DOUBLE_R("ADC Mixer Switch",
+ CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1),
+ SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0),
+ SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0),
+ SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0),
+ SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0),
+ SOC_DOUBLE_TLV("Mic Boost Volume",
+ CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv),
+ SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv),
+ SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv),
+ SOC_ENUM_EXT("PCM channel mixer",
+ cs42l51_chan_mix,
+ cs42l51_get_chan_mix, cs42l51_set_chan_mix),
+};
+
+/*
+ * to power down, one must:
+ * 1.) Enable the PDN bit
+ * 2.) enable power-down for the select channels
+ * 3.) disable the PDN bit.
+ */
+static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ unsigned long value;
+
+ value = snd_soc_read(w->codec, CS42L51_POWER_CTL1);
+ value &= ~CS42L51_POWER_CTL1_PDN;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMD:
+ value |= CS42L51_POWER_CTL1_PDN;
+ break;
+ default:
+ case SND_SOC_DAPM_POST_PMD:
+ break;
+ }
+ snd_soc_write(w->codec, CS42L51_POWER_CTL1, value);
+
+ return 0;
+}
+
+static const char *cs42l51_dac_names[] = {"Direct PCM",
+ "DSP PCM", "ADC"};
+static const struct soc_enum cs42l51_dac_mux_enum =
+ SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names);
+static const struct snd_kcontrol_new cs42l51_dac_mux_controls =
+ SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum);
+
+static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left",
+ "MIC Left", "MIC+preamp Left"};
+static const struct soc_enum cs42l51_adcl_mux_enum =
+ SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names);
+static const struct snd_kcontrol_new cs42l51_adcl_mux_controls =
+ SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum);
+
+static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right",
+ "MIC Right", "MIC+preamp Right"};
+static const struct soc_enum cs42l51_adcr_mux_enum =
+ SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names);
+static const struct snd_kcontrol_new cs42l51_adcr_mux_controls =
+ SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum);
+
+#define DAPM_EVENT_PRE_POST_PMD (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD)
+
+static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = {
+ SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1),
+ SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0,
+ cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD),
+ SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0,
+ cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD),
+ SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture",
+ CS42L51_POWER_CTL1, 1, 1,
+ cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD),
+ SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture",
+ CS42L51_POWER_CTL1, 2, 1,
+ cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD),
+ SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback",
+ CS42L51_POWER_CTL1, 5, 1,
+ cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD),
+ SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback",
+ CS42L51_POWER_CTL1, 6, 1,
+ cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD),
+
+ /* analog/mic */
+ SND_SOC_DAPM_INPUT("AIN1L"),
+ SND_SOC_DAPM_INPUT("AIN1R"),
+ SND_SOC_DAPM_INPUT("AIN2L"),
+ SND_SOC_DAPM_INPUT("AIN2R"),
+ SND_SOC_DAPM_INPUT("MICL"),
+ SND_SOC_DAPM_INPUT("MICR"),
+
+ SND_SOC_DAPM_MIXER("Mic Preamp Left",
+ CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0),
+ SND_SOC_DAPM_MIXER("Mic Preamp Right",
+ CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0),
+
+ /* HP */
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+
+ /* mux */
+ SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0,
+ &cs42l51_dac_mux_controls),
+ SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0,
+ &cs42l51_adcl_mux_controls),
+ SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0,
+ &cs42l51_adcr_mux_controls),
+};
+
+static const struct snd_soc_dapm_route cs42l51_routes[] = {
+ {"HPL", NULL, "Left DAC"},
+ {"HPR", NULL, "Right DAC"},
+
+ {"Left ADC", NULL, "Left PGA"},
+ {"Right ADC", NULL, "Right PGA"},
+
+ {"Mic Preamp Left", NULL, "MICL"},
+ {"Mic Preamp Right", NULL, "MICR"},
+
+ {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" },
+ {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" },
+ {"PGA-ADC Mux Left", "MIC Left", "MICL" },
+ {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" },
+ {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" },
+ {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" },
+ {"PGA-ADC Mux Right", "MIC Right", "MICR" },
+ {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" },
+
+ {"Left PGA", NULL, "PGA-ADC Mux Left"},
+ {"Right PGA", NULL, "PGA-ADC Mux Right"},
+};
+
+static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_LEFT_J:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ dev_err(codec->dev, "invalid DAI format\n");
+ ret = -EINVAL;
+ }
+
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs42l51->func = MODE_MASTER;
+ break;
+ default:
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs42l51->func = MODE_SLAVE_AUTO;
+ break;
+ }
+
+ return ret;
+}
+
+struct cs42l51_ratios {
+ unsigned int ratio;
+ unsigned char speed_mode;
+ unsigned char mclk;
+};
+
+static struct cs42l51_ratios slave_ratios[] = {
+ { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 },
+ { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
+ { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 },
+ { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 },
+ { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 },
+ { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 },
+ { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 },
+ { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 },
+ { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 },
+ { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 },
+ { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 },
+};
+
+static struct cs42l51_ratios slave_auto_ratios[] = {
+ { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
+ { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 },
+ { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 },
+ { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 },
+ { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 },
+ { 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 },
+ { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 },
+ { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 },
+};
+
+static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+ struct cs42l51_ratios *ratios = NULL;
+ int nr_ratios = 0;
+ unsigned int rates = 0;
+ unsigned int rate_min = -1;
+ unsigned int rate_max = 0;
+ int i;
+
+ cs42l51->mclk = freq;
+
+ switch (cs42l51->func) {
+ case MODE_MASTER:
+ return -EINVAL;
+ case MODE_SLAVE:
+ ratios = slave_ratios;
+ nr_ratios = ARRAY_SIZE(slave_ratios);
+ break;
+ case MODE_SLAVE_AUTO:
+ ratios = slave_auto_ratios;
+ nr_ratios = ARRAY_SIZE(slave_auto_ratios);
+ break;
+ }
+
+ for (i = 0; i < nr_ratios; i++) {
+ unsigned int rate = freq / ratios[i].ratio;
+ rates |= snd_pcm_rate_to_rate_bit(rate);
+ if (rate < rate_min)
+ rate_min = rate;
+ if (rate > rate_max)
+ rate_max = rate;
+ }
+ rates &= ~SNDRV_PCM_RATE_KNOT;
+
+ if (!rates) {
+ dev_err(codec->dev, "could not find a valid sample rate\n");
+ return -EINVAL;
+ }
+
+ codec_dai->playback.rates = rates;
+ codec_dai->playback.rate_min = rate_min;
+ codec_dai->playback.rate_max = rate_max;
+
+ codec_dai->capture.rates = rates;
+ codec_dai->capture.rate_min = rate_min;
+ codec_dai->capture.rate_max = rate_max;
+
+ return 0;
+}
+
+static int cs42l51_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+ unsigned int i;
+ unsigned int rate;
+ unsigned int ratio;
+ struct cs42l51_ratios *ratios = NULL;
+ int nr_ratios = 0;
+ int intf_ctl, power_ctl, fmt;
+
+ switch (cs42l51->func) {
+ case MODE_MASTER:
+ return -EINVAL;
+ case MODE_SLAVE:
+ ratios = slave_ratios;
+ nr_ratios = ARRAY_SIZE(slave_ratios);
+ break;
+ case MODE_SLAVE_AUTO:
+ ratios = slave_auto_ratios;
+ nr_ratios = ARRAY_SIZE(slave_auto_ratios);
+ break;
+ }
+
+ /* Figure out which MCLK/LRCK ratio to use */
+ rate = params_rate(params); /* Sampling rate, in Hz */
+ ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */
+ for (i = 0; i < nr_ratios; i++) {
+ if (ratios[i].ratio == ratio)
+ break;
+ }
+
+ if (i == nr_ratios) {
+ /* We did not find a matching ratio */
+ dev_err(codec->dev, "could not find matching ratio\n");
+ return -EINVAL;
+ }
+
+ intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL);
+ power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL);
+
+ intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S
+ | CS42L51_INTF_CTL_DAC_FORMAT(7));
+ power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3)
+ | CS42L51_MIC_POWER_CTL_MCLK_DIV2);
+
+ switch (cs42l51->func) {
+ case MODE_MASTER:
+ intf_ctl |= CS42L51_INTF_CTL_MASTER;
+ power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
+ break;
+ case MODE_SLAVE:
+ power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
+ break;
+ case MODE_SLAVE_AUTO:
+ power_ctl |= CS42L51_MIC_POWER_CTL_AUTO;
+ break;
+ }
+
+ switch (cs42l51->audio_mode) {
+ case SND_SOC_DAIFMT_I2S:
+ intf_ctl |= CS42L51_INTF_CTL_ADC_I2S;
+ intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24);
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ case SNDRV_PCM_FORMAT_S16_BE:
+ fmt = CS42L51_DAC_DIF_RJ16;
+ break;
+ case SNDRV_PCM_FORMAT_S18_3LE:
+ case SNDRV_PCM_FORMAT_S18_3BE:
+ fmt = CS42L51_DAC_DIF_RJ18;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S20_3BE:
+ fmt = CS42L51_DAC_DIF_RJ20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S24_BE:
+ fmt = CS42L51_DAC_DIF_RJ24;
+ break;
+ default:
+ dev_err(codec->dev, "unknown format\n");
+ return -EINVAL;
+ }
+ intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt);
+ break;
+ default:
+ dev_err(codec->dev, "unknown format\n");
+ return -EINVAL;
+ }
+
+ if (ratios[i].mclk)
+ power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2;
+
+ ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int reg;
+ int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE;
+
+ reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL);
+
+ if (mute)
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg);
+}
+
+static struct snd_soc_dai_ops cs42l51_dai_ops = {
+ .hw_params = cs42l51_hw_params,
+ .set_sysclk = cs42l51_set_dai_sysclk,
+ .set_fmt = cs42l51_set_dai_fmt,
+ .digital_mute = cs42l51_dai_mute,
+};
+
+struct snd_soc_dai cs42l51_dai = {
+ .name = "CS42L51 HiFi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = CS42L51_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = CS42L51_FORMATS,
+ },
+ .ops = &cs42l51_dai_ops,
+};
+EXPORT_SYMBOL_GPL(cs42l51_dai);
+
+
+static int cs42l51_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ if (!cs42l51_codec) {
+ dev_err(&pdev->dev, "CS42L51 codec not yet registered\n");
+ return -EINVAL;
+ }
+
+ dev_info(&pdev->dev, "CS42L51 ALSA SoC Codec\n");
+
+ socdev->card->codec = cs42l51_codec;
+ codec = socdev->card->codec;
+
+ /* Register PCMs */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to create PCMs\n");
+ return ret;
+ }
+
+ snd_soc_add_controls(codec, cs42l51_snd_controls,
+ ARRAY_SIZE(cs42l51_snd_controls));
+ snd_soc_dapm_new_controls(codec, cs42l51_dapm_widgets,
+ ARRAY_SIZE(cs42l51_dapm_widgets));
+ snd_soc_dapm_add_routes(codec, cs42l51_routes,
+ ARRAY_SIZE(cs42l51_routes));
+
+ return 0;
+}
+
+
+static int cs42l51_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_device_cs42l51 = {
+ .probe = cs42l51_probe,
+ .remove = cs42l51_remove
+};
+EXPORT_SYMBOL_GPL(soc_codec_device_cs42l51);
+
+static int __init cs42l51_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&cs42l51_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "%s: can't add i2c driver\n", __func__);
+ return ret;
+ }
+ return 0;
+}
+module_init(cs42l51_init);
+
+static void __exit cs42l51_exit(void)
+{
+ i2c_del_driver(&cs42l51_i2c_driver);
+}
+module_exit(cs42l51_exit);
+
+MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
+MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
+
Index: sound-2.6/sound/soc/codecs/cs42l51.h
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ sound-2.6/sound/soc/codecs/cs42l51.h 2010-05-21 16:23:42.115905635 +0200
@@ -0,0 +1,168 @@
+/*
+ * cs42l51.h
+ *
+ * ASoC Driver for Cirrus Logic CS42L51 codecs
+ *
+ * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.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.
+ */
+#ifndef _CS42L51_H
+#define _CS42L51_H
+
+#define CS42L51_CHIP_ID 0x1B
+#define CS42L51_CHIP_REV_A 0x00
+#define CS42L51_CHIP_REV_B 0x01
+
+#define CS42L51_CHIP_REV_ID 0x01
+#define CS42L51_MK_CHIP_REV(a, b) ((a)<<3|(b))
+
+#define CS42L51_POWER_CTL1 0x02
+#define CS42L51_POWER_CTL1_PDN_DACB (1<<6)
+#define CS42L51_POWER_CTL1_PDN_DACA (1<<5)
+#define CS42L51_POWER_CTL1_PDN_PGAB (1<<4)
+#define CS42L51_POWER_CTL1_PDN_PGAA (1<<3)
+#define CS42L51_POWER_CTL1_PDN_ADCB (1<<2)
+#define CS42L51_POWER_CTL1_PDN_ADCA (1<<1)
+#define CS42L51_POWER_CTL1_PDN (1<<0)
+
+#define CS42L51_MIC_POWER_CTL 0x03
+#define CS42L51_MIC_POWER_CTL_AUTO (1<<7)
+#define CS42L51_MIC_POWER_CTL_SPEED(x) (((x)&3)<<5)
+#define CS42L51_QSM_MODE 3
+#define CS42L51_HSM_MODE 2
+#define CS42L51_SSM_MODE 1
+#define CS42L51_DSM_MODE 0
+#define CS42L51_MIC_POWER_CTL_3ST_SP (1<<4)
+#define CS42L51_MIC_POWER_CTL_PDN_MICB (1<<3)
+#define CS42L51_MIC_POWER_CTL_PDN_MICA (1<<2)
+#define CS42L51_MIC_POWER_CTL_PDN_BIAS (1<<1)
+#define CS42L51_MIC_POWER_CTL_MCLK_DIV2 (1<<0)
+
+#define CS42L51_INTF_CTL 0x04
+#define CS42L51_INTF_CTL_LOOPBACK (1<<7)
+#define CS42L51_INTF_CTL_MASTER (1<<6)
+#define CS42L51_INTF_CTL_DAC_FORMAT(x) (((x)&7)<<3)
+#define CS42L51_DAC_DIF_LJ24 0x00
+#define CS42L51_DAC_DIF_I2S 0x01
+#define CS42L51_DAC_DIF_RJ24 0x02
+#define CS42L51_DAC_DIF_RJ20 0x03
+#define CS42L51_DAC_DIF_RJ18 0x04
+#define CS42L51_DAC_DIF_RJ16 0x05
+#define CS42L51_INTF_CTL_ADC_I2S (1<<2)
+#define CS42L51_INTF_CTL_DIGMIX (1<<1)
+#define CS42L51_INTF_CTL_MICMIX (1<<0)
+
+#define CS42L51_MIC_CTL 0x05
+#define CS42L51_MIC_CTL_ADC_SNGVOL (1<<7)
+#define CS42L51_MIC_CTL_ADCD_DBOOST (1<<6)
+#define CS42L51_MIC_CTL_ADCA_DBOOST (1<<5)
+#define CS42L51_MIC_CTL_MICBIAS_SEL (1<<4)
+#define CS42L51_MIC_CTL_MICBIAS_LVL(x) (((x)&3)<<2)
+#define CS42L51_MIC_CTL_MICB_BOOST (1<<1)
+#define CS42L51_MIC_CTL_MICA_BOOST (1<<0)
+
+#define CS42L51_ADC_CTL 0x06
+#define CS42L51_ADC_CTL_ADCB_HPFEN (1<<7)
+#define CS42L51_ADC_CTL_ADCB_HPFRZ (1<<6)
+#define CS42L51_ADC_CTL_ADCA_HPFEN (1<<5)
+#define CS42L51_ADC_CTL_ADCA_HPFRZ (1<<4)
+#define CS42L51_ADC_CTL_SOFTB (1<<3)
+#define CS42L51_ADC_CTL_ZCROSSB (1<<2)
+#define CS42L51_ADC_CTL_SOFTA (1<<1)
+#define CS42L51_ADC_CTL_ZCROSSA (1<<0)
+
+#define CS42L51_ADC_INPUT 0x07
+#define CS42L51_ADC_INPUT_AINB_MUX(x) (((x)&3)<<6)
+#define CS42L51_ADC_INPUT_AINA_MUX(x) (((x)&3)<<4)
+#define CS42L51_ADC_INPUT_INV_ADCB (1<<3)
+#define CS42L51_ADC_INPUT_INV_ADCA (1<<2)
+#define CS42L51_ADC_INPUT_ADCB_MUTE (1<<1)
+#define CS42L51_ADC_INPUT_ADCA_MUTE (1<<0)
+
+#define CS42L51_DAC_OUT_CTL 0x08
+#define CS42L51_DAC_OUT_CTL_HP_GAIN(x) (((x)&7)<<5)
+#define CS42L51_DAC_OUT_CTL_DAC_SNGVOL (1<<4)
+#define CS42L51_DAC_OUT_CTL_INV_PCMB (1<<3)
+#define CS42L51_DAC_OUT_CTL_INV_PCMA (1<<2)
+#define CS42L51_DAC_OUT_CTL_DACB_MUTE (1<<1)
+#define CS42L51_DAC_OUT_CTL_DACA_MUTE (1<<0)
+
+#define CS42L51_DAC_CTL 0x09
+#define CS42L51_DAC_CTL_DATA_SEL(x) (((x)&3)<<6)
+#define CS42L51_DAC_CTL_FREEZE (1<<5)
+#define CS42L51_DAC_CTL_DEEMPH (1<<3)
+#define CS42L51_DAC_CTL_AMUTE (1<<2)
+#define CS42L51_DAC_CTL_DACSZ(x) (((x)&3)<<0)
+
+#define CS42L51_ALC_PGA_CTL 0x0A
+#define CS42L51_ALC_PGB_CTL 0x0B
+#define CS42L51_ALC_PGX_ALCX_SRDIS (1<<7)
+#define CS42L51_ALC_PGX_ALCX_ZCDIS (1<<6)
+#define CS42L51_ALC_PGX_PGX_VOL(x) (((x)&0x1f)<<0)
+
+#define CS42L51_ADCA_ATT 0x0C
+#define CS42L51_ADCB_ATT 0x0D
+
+#define CS42L51_ADCA_VOL 0x0E
+#define CS42L51_ADCB_VOL 0x0F
+#define CS42L51_PCMA_VOL 0x10
+#define CS42L51_PCMB_VOL 0x11
+#define CS42L51_MIX_MUTE_ADCMIX (1<<7)
+#define CS42L51_MIX_VOLUME(x) (((x)&0x7f)<<0)
+
+#define CS42L51_BEEP_FREQ 0x12
+#define CS42L51_BEEP_VOL 0x13
+#define CS42L51_BEEP_CONF 0x14
+
+#define CS42L51_TONE_CTL 0x15
+#define CS42L51_TONE_CTL_TREB(x) (((x)&0xf)<<4)
+#define CS42L51_TONE_CTL_BASS(x) (((x)&0xf)<<0)
+
+#define CS42L51_AOUTA_VOL 0x16
+#define CS42L51_AOUTB_VOL 0x17
+#define CS42L51_PCM_MIXER 0x18
+#define CS42L51_LIMIT_THRES_DIS 0x19
+#define CS42L51_LIMIT_REL 0x1A
+#define CS42L51_LIMIT_ATT 0x1B
+#define CS42L51_ALC_EN 0x1C
+#define CS42L51_ALC_REL 0x1D
+#define CS42L51_ALC_THRES 0x1E
+#define CS42L51_NOISE_CONF 0x1F
+
+#define CS42L51_STATUS 0x20
+#define CS42L51_STATUS_SP_CLKERR (1<<6)
+#define CS42L51_STATUS_SPEA_OVFL (1<<5)
+#define CS42L51_STATUS_SPEB_OVFL (1<<4)
+#define CS42L51_STATUS_PCMA_OVFL (1<<3)
+#define CS42L51_STATUS_PCMB_OVFL (1<<2)
+#define CS42L51_STATUS_ADCA_OVFL (1<<1)
+#define CS42L51_STATUS_ADCB_OVFL (1<<0)
+
+#define CS42L51_CHARGE_FREQ 0x21
+
+#define CS42L51_FIRSTREG 0x01
+/*
+ * Hack: with register 0x21, it makes 33 registers. Looks like someone in the
+ * i2c layer doesn't like i2c smbus block read of 33 regs. Workaround by using
+ * 32 regs
+ */
+#define CS42L51_LASTREG 0x20
+#define CS42L51_NUMREGS (CS42L51_LASTREG - CS42L51_FIRSTREG + 1)
+
+struct cs42l51_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai cs42l51_dai;
+extern struct snd_soc_codec_device soc_codec_device_cs42l51;
+#endif
Index: sound-2.6/sound/soc/codecs/Kconfig
===================================================================
--- sound-2.6.orig/sound/soc/codecs/Kconfig 2010-05-17 08:56:49.486730791 +0200
+++ sound-2.6/sound/soc/codecs/Kconfig 2010-05-24 22:26:16.380580036 +0200
@@ -22,6 +22,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AK4642 if I2C
select SND_SOC_AK4671 if I2C
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
+ select SND_SOC_CS42L51 if I2C
select SND_SOC_CS4270 if I2C
select SND_SOC_MAX9877 if I2C
select SND_SOC_DA7210 if I2C
@@ -120,6 +121,9 @@ config SND_SOC_AK4671
config SND_SOC_CQ0093VC
tristate
+config SND_SOC_CS42L51
+ tristate
+
# Cirrus Logic CS4270 Codec
config SND_SOC_CS4270
tristate
Index: sound-2.6/sound/soc/codecs/Makefile
===================================================================
--- sound-2.6.orig/sound/soc/codecs/Makefile 2010-05-17 08:56:49.514733876 +0200
+++ sound-2.6/sound/soc/codecs/Makefile 2010-05-24 22:26:16.456580923 +0200
@@ -9,6 +9,7 @@ snd-soc-ak4535-objs := ak4535.o
snd-soc-ak4642-objs := ak4642.o
snd-soc-ak4671-objs := ak4671.o
snd-soc-cq93vc-objs := cq93vc.o
+snd-soc-cs42l51-objs := cs42l51.o
snd-soc-cs4270-objs := cs4270.o
snd-soc-cx20442-objs := cx20442.o
snd-soc-da7210-objs := da7210.o
@@ -74,6 +75,7 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-
obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
+obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
^ permalink raw reply [flat|nested] 18+ messages in thread* [patch 4/5] orion/kirkwood: Add i2s support
2010-05-25 12:22 [patch 0/5] kirkwood openrd client audio support - v3 apatard
` (2 preceding siblings ...)
2010-05-25 12:22 ` [patch 3/5] cs42l51: add asoc driver apatard
@ 2010-05-25 12:22 ` apatard
2010-05-26 0:31 ` Nicolas Pitre
2010-05-25 12:22 ` [patch 5/5] kirkwood: Add audio support to openrd client platforms apatard
2010-05-25 23:18 ` [patch 0/5] kirkwood openrd client audio support - v3 Mark Brown
5 siblings, 1 reply; 18+ messages in thread
From: apatard @ 2010-05-25 12:22 UTC (permalink / raw)
To: alsa-devel
Cc: nico, broonie, saeed, Arnaud Patard, tbm, linux-arm-kernel, lrg
[-- Attachment #1: kirkwood_i2s.patch --]
[-- Type: text/plain, Size: 31238 bytes --]
This patch enables support for the i2s controller available on orion/kirkwood platforms
Signed-off-by: Arnaud Patard <apatard@mandriva.com>
Index: sound-2.6/sound/soc/orion/Kconfig
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ sound-2.6/sound/soc/orion/Kconfig 2010-05-25 10:31:24.601080937 +0200
@@ -0,0 +1,11 @@
+config SND_ORION_SOC
+ tristate "SoC Audio for the Marvell Orion/Kirkwood chip"
+ depends on ARCH_KIRKWOOD || ARCH_ARCH_ORION5X
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Orion/Kirkwood I2S interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_ORION_SOC_I2S
+ tristate
+
Index: sound-2.6/sound/soc/orion/orion-dma.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ sound-2.6/sound/soc/orion/orion-dma.c 2010-05-25 10:31:34.405079748 +0200
@@ -0,0 +1,381 @@
+/*
+ * orion-dma.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mbus.h>
+#include <sound/soc.h>
+#include "orion-dma.h"
+#include "orion.h"
+
+#define ORION_RATES \
+ (SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+#define ORION_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+struct orion_dma_priv {
+ struct snd_pcm_substream *play_stream;
+ struct snd_pcm_substream *rec_stream;
+ struct orion_dma_data *data;
+};
+
+static struct snd_pcm_hardware orion_dma_snd_hw = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = ORION_FORMATS,
+ .rates = ORION_RATES,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = ORION_SND_MAX_PERIOD_BYTES * ORION_SND_MAX_PERIODS,
+ .period_bytes_min = ORION_SND_MIN_PERIOD_BYTES,
+ .period_bytes_max = ORION_SND_MAX_PERIOD_BYTES,
+ .periods_min = ORION_SND_MIN_PERIODS,
+ .periods_max = ORION_SND_MAX_PERIODS,
+ .fifo_size = 0,
+};
+
+static u64 orion_dma_dmamask = 0xFFFFFFFFUL;
+
+static irqreturn_t orion_dma_irq(int irq, void *dev_id)
+{
+ struct orion_dma_priv *prdata = dev_id;
+ struct orion_dma_data *priv = prdata->data;
+ unsigned long mask, status, cause;
+
+ mask = readl(priv->io + ORION_INT_MASK);
+ status = readl(priv->io + ORION_INT_CAUSE) & mask;
+
+ cause = readl(priv->io + ORION_ERR_CAUSE);
+ if (unlikely(cause)) {
+ printk(KERN_WARNING "%s: got err interrupt 0x%lx\n",
+ __func__, cause);
+ writel(cause, priv->io + ORION_ERR_CAUSE);
+ return IRQ_HANDLED;
+ }
+
+ /* we've enabled only bytes interrupts ... */
+ if (status & ~(ORION_INT_CAUSE_PLAY_BYTES | \
+ ORION_INT_CAUSE_REC_BYTES)) {
+ printk(KERN_WARNING "%s: unexpected interrupt %lx\n",
+ __func__, status);
+ return IRQ_NONE;
+ }
+
+ /* ack int */
+ writel(status, priv->io + ORION_INT_CAUSE);
+
+ if (status & ORION_INT_CAUSE_PLAY_BYTES)
+ snd_pcm_period_elapsed(prdata->play_stream);
+
+ if (status & ORION_INT_CAUSE_REC_BYTES)
+ snd_pcm_period_elapsed(prdata->rec_stream);
+
+ return IRQ_HANDLED;
+}
+
+static void orion_dma_conf_mbus_windows(void __iomem *base, int win,
+ unsigned long dma,
+ struct mbus_dram_target_info *dram)
+{
+ int i;
+
+ /* First disable and clear windows */
+ writel(0, base + ORION_AUDIO_WIN_CTRL_REG(win));
+ writel(0, base + ORION_AUDIO_WIN_BASE_REG(win));
+
+ /* try to find matching cs for current dma address */
+ for (i = 0; i < dram->num_cs; i++) {
+ struct mbus_dram_window *cs = dram->cs + i;
+ if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) {
+ writel(cs->base & 0xffff0000,
+ base + ORION_AUDIO_WIN_BASE_REG(win));
+ writel(((cs->size - 1) & 0xffff0000) |
+ (cs->mbus_attr << 8) |
+ (dram->mbus_dram_target_id << 4) | 1,
+ base + ORION_AUDIO_WIN_CTRL_REG(win));
+ }
+ }
+}
+
+static int orion_dma_open(struct snd_pcm_substream *substream)
+{
+ int err;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
+ struct orion_dma_data *priv;
+ struct orion_dma_priv *prdata = cpu_dai->private_data;
+ unsigned long addr;
+
+ priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+ snd_soc_set_runtime_hwparams(substream, &orion_dma_snd_hw);
+
+ /* Ensure that all constraints linked to dma burst are fullfilled */
+ err = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ priv->burst * 2,
+ ORION_AUDIO_BUF_MAX-1);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ priv->burst);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ priv->burst);
+ if (err < 0)
+ return err;
+
+ if (soc_runtime->dai->cpu_dai->private_data == NULL) {
+ prdata = kzalloc(sizeof(struct orion_dma_priv), GFP_KERNEL);
+ if (prdata == NULL)
+ return -ENOMEM;
+
+ prdata->data = priv;
+
+ err = request_irq(priv->irq, orion_dma_irq, IRQF_SHARED,
+ "orion-i2s", prdata);
+ if (err) {
+ kfree(prdata);
+ return -EBUSY;
+ }
+
+ soc_runtime->dai->cpu_dai->private_data = prdata;
+
+ /*
+ * Enable Error interrupts. We're only ack'ing them but
+ * it's usefull for diagnostics
+ */
+ writel((unsigned long)-1, priv->io + ORION_ERR_MASK);
+ }
+
+ addr = virt_to_phys(substream->dma_buffer.area);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ prdata->play_stream = substream;
+ orion_dma_conf_mbus_windows(priv->io, 1, addr, priv->dram);
+ } else {
+ prdata->rec_stream = substream;
+ orion_dma_conf_mbus_windows(priv->io, 0, addr, priv->dram);
+ }
+
+ return 0;
+}
+
+static int orion_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
+ struct orion_dma_priv *prdata = cpu_dai->private_data;
+ struct orion_dma_data *priv;
+
+ priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ if (!prdata || !priv)
+ return 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ prdata->play_stream = NULL;
+ else
+ prdata->rec_stream = NULL;
+
+ if (!prdata->play_stream && !prdata->rec_stream) {
+ writel(0, priv->io + ORION_ERR_MASK);
+ free_irq(priv->irq, prdata);
+ kfree(prdata);
+ soc_runtime->dai->cpu_dai->private_data = NULL;
+ }
+
+ return 0;
+}
+
+static int orion_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ return 0;
+}
+
+static int orion_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static int orion_dma_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
+ struct orion_dma_data *priv;
+ unsigned long size, count;
+
+ priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ /* compute buffer size in term of "words" as requested in specs */
+ size = frames_to_bytes(runtime, runtime->buffer_size);
+ size = (size>>2)-1;
+ count = snd_pcm_lib_period_bytes(substream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ writel(count, priv->io + ORION_PLAY_BYTE_INT_COUNT);
+ writel(runtime->dma_addr, priv->io + ORION_PLAY_BUF_ADDR);
+ writel(size, priv->io + ORION_PLAY_BUF_SIZE);
+ } else {
+ writel(count, priv->io + ORION_REC_BYTE_INT_COUNT);
+ writel(runtime->dma_addr, priv->io + ORION_REC_BUF_ADDR);
+ writel(size, priv->io + ORION_REC_BUF_SIZE);
+ }
+
+
+ return 0;
+}
+
+static snd_pcm_uframes_t orion_dma_pointer(struct snd_pcm_substream
+ *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
+ struct orion_dma_data *priv;
+ snd_pcm_uframes_t count;
+
+ priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ count = bytes_to_frames(substream->runtime,
+ readl(priv->io + ORION_PLAY_BYTE_COUNT));
+ else
+ count = bytes_to_frames(substream->runtime,
+ readl(priv->io + ORION_REC_BYTE_COUNT));
+
+ return count;
+}
+
+struct snd_pcm_ops orion_dma_ops = {
+ .open = orion_dma_open,
+ .close = orion_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = orion_dma_hw_params,
+ .hw_free = orion_dma_hw_free,
+ .prepare = orion_dma_prepare,
+ .pointer = orion_dma_pointer,
+};
+
+static int orion_dma_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 = orion_dma_snd_hw.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+ buf->bytes = size;
+ buf->private_data = NULL;
+
+ return 0;
+}
+
+static int orion_dma_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &orion_dma_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->playback.channels_min) {
+ ret = orion_dma_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ return ret;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = orion_dma_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void orion_dma_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_coherent(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+struct snd_soc_platform orion_soc_platform = {
+ .name = "orion-dma",
+ .pcm_ops = &orion_dma_ops,
+ .pcm_new = orion_dma_new,
+ .pcm_free = orion_dma_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(orion_soc_platform);
+
+static int __init orion_soc_platform_init(void)
+{
+ return snd_soc_register_platform(&orion_soc_platform);
+}
+module_init(orion_soc_platform_init);
+
+static void __exit orion_soc_platform_exit(void)
+{
+ snd_soc_unregister_platform(&orion_soc_platform);
+}
+module_exit(orion_soc_platform_exit);
+
+MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
+MODULE_DESCRIPTION("Marvell Orion/Kirkwood Audio DMA module");
+MODULE_LICENSE("GPL");
+
Index: sound-2.6/sound/soc/orion/orion.h
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ sound-2.6/sound/soc/orion/orion.h 2010-05-24 23:51:07.028579877 +0200
@@ -0,0 +1,126 @@
+/*
+ * orion.h
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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.
+ */
+
+#ifndef _ORION_AUDIO_H
+#define _ORION_AUDIO_H
+
+#define ORION_RECORD_WIN 0
+#define ORION_PLAYBACK_WIN 1
+#define ORION_MAX_AUDIO_WIN 2
+
+#define ORION_AUDIO_WIN_BASE_REG(win) (0xA00 + ((win)<<3))
+#define ORION_AUDIO_WIN_CTRL_REG(win) (0xA04 + ((win)<<3))
+
+
+#define ORION_RECCTL 0x1000
+#define ORION_RECCTL_SPDIF_EN (1<<11)
+#define ORION_RECCTL_I2S_EN (1<<10)
+#define ORION_RECCTL_PAUSE (1<<9)
+#define ORION_RECCTL_MUTE (1<<8)
+#define ORION_RECCTL_BURST_MASK (3<<5)
+#define ORION_RECCTL_BURST_128 (2<<5)
+#define ORION_RECCTL_BURST_32 (1<<5)
+#define ORION_RECCTL_MONO (1<<4)
+#define ORION_RECCTL_MONO_CHAN_RIGHT (1<<3)
+#define ORION_RECCTL_MONO_CHAN_LEFT (0<<3)
+#define ORION_RECCTL_SIZE_MASK (7<<0)
+#define ORION_RECCTL_SIZE_16 (7<<0)
+#define ORION_RECCTL_SIZE_16_C (3<<0)
+#define ORION_RECCTL_SIZE_20 (2<<0)
+#define ORION_RECCTL_SIZE_24 (1<<0)
+#define ORION_RECCTL_SIZE_32 (0<<0)
+
+#define ORION_REC_BUF_ADDR 0x1004
+#define ORION_REC_BUF_SIZE 0x1008
+#define ORION_REC_BYTE_COUNT 0x100C
+
+#define ORION_PLAYCTL 0x1100
+#define ORION_PLAYCTL_PLAY_BUSY (1<<16)
+#define ORION_PLAYCTL_BURST_MASK (3<<11)
+#define ORION_PLAYCTL_BURST_128 (2<<11)
+#define ORION_PLAYCTL_BURST_32 (1<<11)
+#define ORION_PLAYCTL_PAUSE (1<<9)
+#define ORION_PLAYCTL_SPDIF_MUTE (1<<8)
+#define ORION_PLAYCTL_I2S_MUTE (1<<7)
+#define ORION_PLAYCTL_SPDIF_EN (1<<4)
+#define ORION_PLAYCTL_I2S_EN (1<<3)
+#define ORION_PLAYCTL_SIZE_MASK (7<<0)
+#define ORION_PLAYCTL_SIZE_16 (7<<0)
+#define ORION_PLAYCTL_SIZE_16_C (3<<0)
+#define ORION_PLAYCTL_SIZE_20 (2<<0)
+#define ORION_PLAYCTL_SIZE_24 (1<<0)
+#define ORION_PLAYCTL_SIZE_32 (0<<0)
+
+#define ORION_PLAY_BUF_ADDR 0x1104
+#define ORION_PLAY_BUF_SIZE 0x1108
+#define ORION_PLAY_BYTE_COUNT 0x110C
+
+#define ORION_DCO_CTL 0x1204
+#define ORION_DCO_CTL_OFFSET_MASK (0xFFF<<2)
+#define ORION_DCO_CTL_OFFSET_0 (0x800<<2)
+#define ORION_DCO_CTL_FREQ_MASK (3<<0)
+#define ORION_DCO_CTL_FREQ_11 (0<<0)
+#define ORION_DCO_CTL_FREQ_12 (1<<0)
+#define ORION_DCO_CTL_FREQ_24 (2<<0)
+
+#define ORION_DCO_SPCR_STATUS 0x120c
+#define ORION_DCO_SPCR_STATUS_DCO_LOCK (1<<16)
+
+#define ORION_ERR_CAUSE 0x1300
+#define ORION_ERR_MASK 0x1304
+
+#define ORION_INT_CAUSE 0x1308
+#define ORION_INT_MASK 0x130C
+#define ORION_INT_CAUSE_PLAY_BYTES (1<<14)
+#define ORION_INT_CAUSE_REC_BYTES (1<<13)
+#define ORION_INT_CAUSE_DMA_PLAY_END (1<<7)
+#define ORION_INT_CAUSE_DMA_PLAY_3Q (1<<6)
+#define ORION_INT_CAUSE_DMA_PLAY_HALF (1<<5)
+#define ORION_INT_CAUSE_DMA_PLAY_1Q (1<<4)
+#define ORION_INT_CAUSE_DMA_REC_END (1<<3)
+#define ORION_INT_CAUSE_DMA_REC_3Q (1<<2)
+#define ORION_INT_CAUSE_DMA_REC_HALF (1<<1)
+#define ORION_INT_CAUSE_DMA_REC_1Q (1<<0)
+
+#define ORION_REC_BYTE_INT_COUNT 0x1310
+#define ORION_PLAY_BYTE_INT_COUNT 0x1314
+#define ORION_BYTE_INT_COUNT_MASK 0xffffff
+
+#define ORION_I2S_PLAYCTL 0x2508
+#define ORION_I2S_RECCTL 0x2408
+#define ORION_I2S_CTL_JUST_MASK (0xf<<26)
+#define ORION_I2S_CTL_LJ (0<<26)
+#define ORION_I2S_CTL_I2S (5<<26)
+#define ORION_I2S_CTL_RJ (8<<26)
+#define ORION_I2S_CTL_SIZE_MASK (3<<30)
+#define ORION_I2S_CTL_SIZE_16 (3<<30)
+#define ORION_I2S_CTL_SIZE_20 (2<<30)
+#define ORION_I2S_CTL_SIZE_24 (1<<30)
+#define ORION_I2S_CTL_SIZE_32 (0<<30)
+
+#define ORION_AUDIO_BUF_MAX (16*1024*1024)
+
+/* Theses values come from the marvell alsa driver */
+/* need to find where they come from */
+#define ORION_SND_MIN_PERIODS 8
+#define ORION_SND_MAX_PERIODS 16
+#define ORION_SND_MIN_PERIOD_BYTES 0x4000
+#define ORION_SND_MAX_PERIOD_BYTES 0x4000
+
+struct orion_dma_data {
+ struct resource *mem;
+ void __iomem *io;
+ int irq;
+ int burst;
+ struct mbus_dram_target_info *dram;
+};
+
+#endif
Index: sound-2.6/sound/soc/orion/orion-i2s.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ sound-2.6/sound/soc/orion/orion-i2s.c 2010-05-25 08:37:31.000000000 +0200
@@ -0,0 +1,485 @@
+/*
+ * orion-i2s.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/mbus.h>
+#include <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <plat/audio.h>
+#include "orion-i2s.h"
+#include "orion.h"
+
+#define DRV_NAME "orion-i2s"
+
+#define ORION_I2S_RATES \
+ (SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+#define ORION_I2S_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+
+struct snd_soc_dai orion_i2s_dai;
+static struct orion_dma_data *priv;
+
+static int orion_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ unsigned long mask;
+ unsigned long value;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ mask = ORION_I2S_CTL_RJ;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ mask = ORION_I2S_CTL_LJ;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ mask = ORION_I2S_CTL_I2S;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Set same format for playback and record
+ * This avoids some troubles.
+ */
+ value = readl(priv->io+ORION_I2S_PLAYCTL);
+ value &= ~ORION_I2S_CTL_JUST_MASK;
+ value |= mask;
+ writel(value, priv->io+ORION_I2S_PLAYCTL);
+
+ value = readl(priv->io+ORION_I2S_RECCTL);
+ value &= ~ORION_I2S_CTL_JUST_MASK;
+ value |= mask;
+ writel(value, priv->io+ORION_I2S_RECCTL);
+
+ return 0;
+}
+
+static inline void orion_set_dco(void __iomem *io, unsigned long rate)
+{
+ unsigned long value;
+
+ value = ORION_DCO_CTL_OFFSET_0;
+ switch (rate) {
+ default:
+ case 44100:
+ value |= ORION_DCO_CTL_FREQ_11;
+ break;
+ case 48000:
+ value |= ORION_DCO_CTL_FREQ_12;
+ break;
+ case 96000:
+ value |= ORION_DCO_CTL_FREQ_24;
+ break;
+ }
+ writel(value, io + ORION_DCO_CTL);
+
+ /* wait for dco locked */
+ do {
+ cpu_relax();
+ value = readl(io + ORION_DCO_SPCR_STATUS);
+ value &= ORION_DCO_SPCR_STATUS;
+ } while (value == 0);
+}
+
+static int orion_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ unsigned int i2s_reg, reg;
+ unsigned long i2s_value, value;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ i2s_reg = ORION_I2S_PLAYCTL;
+ reg = ORION_PLAYCTL;
+ } else {
+ i2s_reg = ORION_I2S_RECCTL;
+ reg = ORION_RECCTL;
+ }
+
+ /* set dco conf */
+ orion_set_dco(priv->io, params_rate(params));
+
+ i2s_value = readl(priv->io+i2s_reg);
+ i2s_value &= ~ORION_I2S_CTL_SIZE_MASK;
+
+ value = readl(priv->io+reg);
+ value &= ~ORION_PLAYCTL_SIZE_MASK;
+
+ /*
+ * Size settings in play/rec i2s control regs and play/rec control
+ * regs must be the same.
+ */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ i2s_value |= ORION_I2S_CTL_SIZE_16;
+ value |= ORION_PLAYCTL_SIZE_16_C;
+ break;
+ /*
+ * doesn't work... S20_3LE != orion/kirkwood 20bit format ?
+ *
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ i2s_value |= ORION_I2S_CTL_SIZE_20;
+ value |= ORION_PLAYCTL_SIZE_20;
+ break;
+ */
+ case SNDRV_PCM_FORMAT_S24_LE:
+ i2s_value |= ORION_I2S_CTL_SIZE_24;
+ value |= ORION_PLAYCTL_SIZE_24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ i2s_value |= ORION_I2S_CTL_SIZE_32;
+ value |= ORION_PLAYCTL_SIZE_32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ writel(i2s_value, priv->io+i2s_reg);
+ writel(value, priv->io+reg);
+
+ return 0;
+}
+
+static int orion_i2s_play_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ unsigned long value;
+
+ /*
+ * specs says ORION_PLAYCTL must be read 2 times before
+ * changing it. So read 1 time here and 1 later.
+ */
+ value = readl(priv->io + ORION_PLAYCTL);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* stop audio, enable interrupts */
+ value = readl(priv->io + ORION_PLAYCTL);
+ value |= ORION_PLAYCTL_PAUSE;
+ writel(value, priv->io + ORION_PLAYCTL);
+
+ value = readl(priv->io + ORION_INT_MASK);
+ value |= ORION_INT_CAUSE_PLAY_BYTES;
+ writel(value, priv->io + ORION_INT_MASK);
+
+ /* configure audio & enable i2s playback */
+ value = readl(priv->io + ORION_PLAYCTL);
+ value &= ~ORION_PLAYCTL_BURST_MASK;
+ value &= ~(ORION_PLAYCTL_PAUSE|ORION_PLAYCTL_SPDIF_EN);
+
+ if (priv->burst == 32)
+ value |= ORION_PLAYCTL_BURST_32;
+ else
+ value |= ORION_PLAYCTL_BURST_128;
+ value |= ORION_PLAYCTL_I2S_EN;
+ writel(value, priv->io + ORION_PLAYCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* stop audio, disable interrupts */
+ value = readl(priv->io + ORION_PLAYCTL);
+ value |= ORION_PLAYCTL_PAUSE;
+ writel(value, priv->io + ORION_PLAYCTL);
+
+ value = readl(priv->io + ORION_INT_MASK);
+ value &= ~ORION_INT_CAUSE_PLAY_BYTES;
+ writel(value, priv->io + ORION_INT_MASK);
+
+ /* disable all playbacks */
+ value = readl(priv->io + ORION_PLAYCTL);
+ value &= ~(ORION_PLAYCTL_I2S_EN | ORION_PLAYCTL_SPDIF_EN);
+ writel(value, priv->io + ORION_PLAYCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ value = readl(priv->io + ORION_PLAYCTL);
+ value |= ORION_PLAYCTL_PAUSE;
+ writel(value, priv->io + ORION_PLAYCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ value = readl(priv->io + ORION_PLAYCTL);
+ value &= ~ORION_PLAYCTL_PAUSE;
+ writel(value, priv->io + ORION_PLAYCTL);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int orion_i2s_rec_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ unsigned long value;
+
+ value = readl(priv->io + ORION_RECCTL);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* stop audio, enable interrupts */
+ value = readl(priv->io + ORION_RECCTL);
+ value |= ORION_RECCTL_PAUSE;
+ writel(value, priv->io + ORION_RECCTL);
+
+ value = readl(priv->io + ORION_INT_MASK);
+ value |= ORION_INT_CAUSE_REC_BYTES;
+ writel(value, priv->io + ORION_INT_MASK);
+
+ /* configure audio & enable i2s record */
+ value = readl(priv->io + ORION_RECCTL);
+ value &= ~ORION_RECCTL_BURST_MASK;
+ value &= ~ORION_RECCTL_MONO;
+ value &= ~(ORION_RECCTL_PAUSE | ORION_RECCTL_SPDIF_EN);
+
+ if (priv->burst == 32)
+ value |= ORION_RECCTL_BURST_32;
+ else
+ value |= ORION_RECCTL_BURST_128;
+ value |= ORION_RECCTL_I2S_EN;
+
+ writel(value, priv->io + ORION_RECCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* stop audio, disable interrupts */
+ value = readl(priv->io + ORION_RECCTL);
+ value |= ORION_RECCTL_PAUSE;
+ writel(value, priv->io + ORION_RECCTL);
+
+ value = readl(priv->io + ORION_INT_MASK);
+ value &= ~ORION_INT_CAUSE_REC_BYTES;
+ writel(value, priv->io + ORION_INT_MASK);
+
+ /* disable all records */
+ value = readl(priv->io + ORION_RECCTL);
+ value &= ~(ORION_RECCTL_I2S_EN | ORION_RECCTL_SPDIF_EN);
+ writel(value, priv->io + ORION_RECCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ value = readl(priv->io + ORION_RECCTL);
+ value |= ORION_RECCTL_PAUSE;
+ writel(value, priv->io + ORION_RECCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ value = readl(priv->io + ORION_RECCTL);
+ value &= ~ORION_RECCTL_PAUSE;
+ writel(value, priv->io + ORION_RECCTL);
+ break;
+
+ default:
+ return -EINVAL;
+ break;
+ }
+
+ return 0;
+}
+
+static int orion_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return orion_i2s_play_trigger(substream, cmd, dai);
+ else
+ return orion_i2s_rec_trigger(substream, cmd, dai);
+
+ return 0;
+}
+
+static int orion_i2s_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ unsigned long value;
+ unsigned int reg_data;
+
+ /* put system in a "safe" state : */
+ /* disable audio interrupts */
+ writel(0xffffffff, priv->io + ORION_INT_CAUSE);
+ writel(0, priv->io + ORION_INT_MASK);
+
+ reg_data = readl(priv->io + 0x1200);
+ reg_data &= (~(0x333FF8));
+ reg_data |= 0x111D18;
+ writel(reg_data, priv->io + 0x1200);
+
+ msleep(500);
+
+ reg_data = readl(priv->io + 0x1200);
+ reg_data &= (~(0x333FF8));
+ reg_data |= 0x111D18;
+ writel(reg_data, priv->io + 0x1200);
+
+ /* disable playback/record */
+ value = readl(priv->io + ORION_PLAYCTL);
+ value &= ~(ORION_PLAYCTL_I2S_EN|ORION_PLAYCTL_SPDIF_EN);
+ writel(value, priv->io + ORION_PLAYCTL);
+
+ value = readl(priv->io + ORION_RECCTL);
+ value &= ~(ORION_RECCTL_I2S_EN | ORION_RECCTL_SPDIF_EN);
+ writel(value, priv->io + ORION_RECCTL);
+
+ return 0;
+
+}
+
+static void orion_i2s_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+}
+
+static struct snd_soc_dai_ops orion_i2s_dai_ops = {
+ .trigger = orion_i2s_trigger,
+ .hw_params = orion_i2s_hw_params,
+ .set_fmt = orion_i2s_set_fmt,
+};
+
+
+struct snd_soc_dai orion_i2s_dai = {
+ .name = DRV_NAME,
+ .id = 0,
+ .probe = orion_i2s_probe,
+ .remove = orion_i2s_remove,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = ORION_I2S_RATES,
+ .formats = ORION_I2S_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = ORION_I2S_RATES,
+ .formats = ORION_I2S_FORMATS,},
+ .ops = &orion_i2s_dai_ops,
+};
+EXPORT_SYMBOL_GPL(orion_i2s_dai);
+
+static __devinit int orion_i2s_dev_probe(struct platform_device *pdev)
+{
+ struct resource *mem;
+ struct orion_asoc_platform_data *data =
+ pdev->dev.platform_data;
+ int err;
+
+ priv = kzalloc(sizeof(struct orion_dma_data), GFP_KERNEL);
+ if (!priv) {
+ dev_err(&pdev->dev, "allocation failed\n");
+ err = -ENOMEM;
+ goto error;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "platform_get_resource failed\n");
+ err = -ENXIO;
+ goto err_alloc;
+ }
+
+ priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME);
+ if (!priv->mem) {
+ dev_err(&pdev->dev, "request_mem_region failed\n");
+ err = -EBUSY;
+ goto error;
+ }
+
+ priv->io = ioremap(priv->mem->start, SZ_16K);
+ if (!priv->io) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ err = -ENOMEM;
+ goto err_iomem;
+ }
+
+ priv->irq = platform_get_irq(pdev, 0);
+ if (priv->irq <= 0) {
+ dev_err(&pdev->dev, "platform_get_irq failed\n");
+ err = -ENXIO;
+ goto err_ioremap;
+ }
+
+ if (!data || !data->dram) {
+ dev_err(&pdev->dev, "no platform data ?!\n");
+ err = -EINVAL;
+ goto err_ioremap;
+ }
+
+ priv->dram = data->dram;
+ priv->burst = data->burst;
+
+ orion_i2s_dai.capture.dma_data = priv;
+ orion_i2s_dai.playback.dma_data = priv;
+
+ return snd_soc_register_dai(&orion_i2s_dai);
+
+err_ioremap:
+ iounmap(priv->io);
+err_iomem:
+ release_mem_region(priv->mem->start, SZ_16K);
+err_alloc:
+ kfree(priv);
+error:
+ return err;
+}
+
+static __devexit int orion_i2s_dev_remove(struct platform_device *pdev)
+{
+ if (priv) {
+ iounmap(priv->io);
+ release_mem_region(priv->mem->start, SZ_16K);
+ kfree(priv);
+ }
+ snd_soc_unregister_dai(&orion_i2s_dai);
+ return 0;
+}
+
+static struct platform_driver orion_i2s_driver = {
+ .probe = orion_i2s_dev_probe,
+ .remove = orion_i2s_dev_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init orion_i2s_init(void)
+{
+ return platform_driver_register(&orion_i2s_driver);
+}
+module_init(orion_i2s_init);
+
+static void __exit orion_i2s_exit(void)
+{
+ platform_driver_unregister(&orion_i2s_driver);
+}
+module_exit(orion_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard, <apatard@mandriva.com>");
+MODULE_DESCRIPTION("Orion/Kirkwood I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:orion-i2s");
Index: sound-2.6/sound/soc/orion/Makefile
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ sound-2.6/sound/soc/orion/Makefile 2010-05-25 10:31:24.633080406 +0200
@@ -0,0 +1,6 @@
+snd-soc-orion-objs := orion-dma.o
+snd-soc-orion-i2s-objs := orion-i2s.o
+
+obj-$(CONFIG_SND_ORION_SOC) += snd-soc-orion.o
+obj-$(CONFIG_SND_ORION_SOC_I2S) += snd-soc-orion-i2s.o
+
Index: sound-2.6/sound/soc/Makefile
===================================================================
--- sound-2.6.orig/sound/soc/Makefile 2010-05-24 23:50:35.880580638 +0200
+++ sound-2.6/sound/soc/Makefile 2010-05-24 23:51:07.072580370 +0200
@@ -10,6 +10,7 @@ obj-$(CONFIG_SND_SOC) += fsl/
obj-$(CONFIG_SND_SOC) += imx/
obj-$(CONFIG_SND_SOC) += nuc900/
obj-$(CONFIG_SND_SOC) += omap/
+obj-$(CONFIG_SND_SOC) += orion/
obj-$(CONFIG_SND_SOC) += pxa/
obj-$(CONFIG_SND_SOC) += s3c24xx/
obj-$(CONFIG_SND_SOC) += s6000/
Index: sound-2.6/sound/soc/orion/orion-dma.h
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ sound-2.6/sound/soc/orion/orion-dma.h 2010-05-24 23:51:07.080580373 +0200
@@ -0,0 +1,17 @@
+/*
+ * orion-dma.h
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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.
+ */
+
+#ifndef _ORION_DMA_H
+#define _ORION_DMA_H
+
+extern struct snd_soc_platform orion_soc_platform;
+
+#endif
Index: sound-2.6/sound/soc/orion/orion-i2s.h
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ sound-2.6/sound/soc/orion/orion-i2s.h 2010-05-24 23:51:07.088580320 +0200
@@ -0,0 +1,17 @@
+/*
+ * orion-i2s.h
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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.
+ */
+
+#ifndef _ORION_I2S_H
+#define _ORION_I2S_H
+
+extern struct snd_soc_dai orion_i2s_dai;
+
+#endif
Index: sound-2.6/sound/soc/Kconfig
===================================================================
--- sound-2.6.orig/sound/soc/Kconfig 2010-05-24 23:50:35.860580703 +0200
+++ sound-2.6/sound/soc/Kconfig 2010-05-24 23:51:07.096580366 +0200
@@ -32,6 +32,7 @@ source "sound/soc/fsl/Kconfig"
source "sound/soc/imx/Kconfig"
source "sound/soc/nuc900/Kconfig"
source "sound/soc/omap/Kconfig"
+source "sound/soc/orion/Kconfig"
source "sound/soc/pxa/Kconfig"
source "sound/soc/s3c24xx/Kconfig"
source "sound/soc/s6000/Kconfig"
^ permalink raw reply [flat|nested] 18+ messages in thread