2010-11-20 13:28:19

by Dmitry Artamonow

[permalink] [raw]
Subject: [PATCH 0/3] ASoC: add support for audio on iPaq hx4700

Here's three patches adding support for audio on iPaq hx4700 PDA.
AK4641 codec driver was originally written by Harald Welte for
gnufiish project [1], then Philipp Zabel adapted it to use on hx4700.
I picked Philipp's patches from his git repository[2], updated them to
current kernel and also fixed all the issues I've met while
using these patches on my hx4700 device. So now I would like to submit
these patches for review in hope they could be included in 2.6.38.

This patchset is based on linux-next-20101119

[1] http://gnufiish.org
[2] http://git.linuxtogo.org/?p=ph5/kernel.git;a=summary


Dmitry Artamonow (2):
ASoC: Asahi Kasei AK4641 codec driver
ASoC: add iPAQ hx4700 machine driver

Philipp Zabel (1):
pxa/hx4700: add I2C board info for AK4641 codec

arch/arm/mach-pxa/hx4700.c | 18 +
arch/arm/mach-pxa/include/mach/hx4700.h | 4 +-
include/sound/ak4641.h | 26 ++
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/ak4641.c | 628 +++++++++++++++++++++++++++++++
sound/soc/codecs/ak4641.h | 47 +++
sound/soc/pxa/Kconfig | 9 +
sound/soc/pxa/Makefile | 2 +
sound/soc/pxa/hx4700.c | 253 +++++++++++++
10 files changed, 991 insertions(+), 2 deletions(-)
create mode 100644 include/sound/ak4641.h
create mode 100644 sound/soc/codecs/ak4641.c
create mode 100644 sound/soc/codecs/ak4641.h
create mode 100644 sound/soc/pxa/hx4700.c


2010-11-20 13:03:07

by Dmitry Artamonow

[permalink] [raw]
Subject: [PATCH 1/3] ASoC: Asahi Kasei AK4641 codec driver

A driver for the AK4641 codec used in iPAQ hx4700 and Glofiish M800
among others.

Signed-off-by: Harald Welte <[email protected]>
Signed-off-by: Philipp Zabel <[email protected]>
Signed-off-by: Dmitry Artamonow <[email protected]>
---
include/sound/ak4641.h | 26 ++
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/ak4641.c | 628 +++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/ak4641.h | 47 ++++
5 files changed, 707 insertions(+), 0 deletions(-)
create mode 100644 include/sound/ak4641.h
create mode 100644 sound/soc/codecs/ak4641.c
create mode 100644 sound/soc/codecs/ak4641.h

diff --git a/include/sound/ak4641.h b/include/sound/ak4641.h
new file mode 100644
index 0000000..96d1991
--- /dev/null
+++ b/include/sound/ak4641.h
@@ -0,0 +1,26 @@
+/*
+ * AK4641 ALSA SoC Codec driver
+ *
+ * Copyright 2009 Philipp Zabel
+ *
+ * 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 __AK4641_H
+#define __AK4641_H
+
+/**
+ * struct ak4641_platform_data - platform specific AK4641 configuration
+ * @gpio_power: GPIO to control external power to AK4641
+ * @gpio_npdn: GPIO connected to AK4641 nPDN pin
+ *
+ * Both GPIO parameters are optional.
+ */
+struct ak4641_platform_data {
+ int gpio_power;
+ int gpio_npdn;
+};
+
+#endif /* __AK4641_H */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 6ebd3a6..7c266eb 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -20,6 +20,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AD73311 if I2C
select SND_SOC_AK4104 if SPI_MASTER
select SND_SOC_AK4535 if I2C
+ select SND_SOC_AK4641 if I2C
select SND_SOC_AK4642 if I2C
select SND_SOC_AK4671 if I2C
select SND_SOC_ALC5623 if I2C
@@ -126,6 +127,9 @@ config SND_SOC_AK4104
config SND_SOC_AK4535
tristate

+config SND_SOC_AK4641
+ tristate
+
config SND_SOC_AK4642
tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 42f185d..e13bd20 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -7,6 +7,7 @@ snd-soc-ad73311-objs := ad73311.o
snd-soc-ads117x-objs := ads117x.o
snd-soc-ak4104-objs := ak4104.o
snd-soc-ak4535-objs := ak4535.o
+snd-soc-ak4641-objs := ak4641.o
snd-soc-ak4642-objs := ak4642.o
snd-soc-ak4671-objs := ak4671.o
snd-soc-cq93vc-objs := cq93vc.o
@@ -83,6 +84,7 @@ obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o
obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
+obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o
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
diff --git a/sound/soc/codecs/ak4641.c b/sound/soc/codecs/ak4641.c
new file mode 100644
index 0000000..d45e144
--- /dev/null
+++ b/sound/soc/codecs/ak4641.c
@@ -0,0 +1,628 @@
+/*
+ * ak4641.c -- AK4641 ALSA Soc Audio driver
+ *
+ * Copyright (C) 2008 Harald Welte <[email protected]>
+ *
+ * Based on ak4535.c by Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/ak4641.h>
+
+#include "ak4641.h"
+
+/* codec private data */
+struct ak4641_priv {
+ struct snd_soc_codec *codec;
+ u8 reg_cache[AK4641_CACHEREGNUM];
+ unsigned int sysclk;
+};
+
+/*
+ * ak4641 register cache
+ */
+static const u8 ak4641_reg[AK4641_CACHEREGNUM] = {
+ 0x00, 0x80, 0x00, 0x80,
+ 0x02, 0x00, 0x11, 0x05,
+ 0x00, 0x00, 0x36, 0x10,
+ 0x00, 0x00, 0x57, 0x00,
+ 0x88, 0x88, 0x08, 0x08
+};
+
+
+static int ak4641_sync(struct snd_soc_codec *codec)
+{
+ u8 *cache = codec->reg_cache;
+ int i, r = 0;
+
+ for (i = 0; i < AK4641_CACHEREGNUM; i++)
+ r |= snd_soc_write(codec, i, cache[i]);
+
+ return r;
+};
+
+
+static const char *ak4641_mono_gain[] = {"+6dB", "-17dB"};
+static const char *ak4641_mono_out[] = {"(L + R)/2", "Hi-Z"};
+static const char *ak4641_hp_out[] = {"Stereo", "Mono"};
+static const char *ak4641_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"};
+static const char *ak4641_mic_select[] = {"Internal", "External"};
+static const char *ak4641_mic_or_dac[] = {"Microphone", "Voice DAC"};
+
+static const struct soc_enum ak4641_enum[] = {
+ SOC_ENUM_SINGLE(AK4641_SIG1, 7, 2, ak4641_mono_gain),
+ SOC_ENUM_SINGLE(AK4641_SIG1, 6, 2, ak4641_mono_out),
+ SOC_ENUM_SINGLE(AK4641_MODE2, 2, 2, ak4641_hp_out),
+ SOC_ENUM_SINGLE(AK4641_DAC, 0, 4, ak4641_deemp),
+ SOC_ENUM_SINGLE(AK4641_MIC, 1, 2, ak4641_mic_select),
+ SOC_ENUM_SINGLE(AK4641_BTIF, 4, 2, ak4641_mic_or_dac),
+};
+
+static const struct snd_kcontrol_new ak4641_snd_controls[] = {
+ SOC_ENUM("Mono 1 Output", ak4641_enum[1]),
+ SOC_ENUM("Mono 1 Gain", ak4641_enum[0]),
+ SOC_ENUM("Headphone Output", ak4641_enum[2]),
+ SOC_ENUM("Playback Deemphasis", ak4641_enum[3]),
+
+ SOC_SINGLE("Mic Boost (+20dB) Switch", AK4641_MIC, 0, 1, 0),
+
+ SOC_SINGLE("ALC Operation Time", AK4641_TIMER, 0, 3, 0),
+ SOC_SINGLE("ALC Recovery Time", AK4641_TIMER, 2, 3, 0),
+ SOC_SINGLE("ALC ZC Time", AK4641_TIMER, 4, 3, 0),
+
+ SOC_SINGLE("ALC 1 Switch", AK4641_ALC1, 5, 1, 0),
+
+ SOC_SINGLE("ALC Volume", AK4641_ALC2, 0, 127, 0),
+
+ SOC_SINGLE("Capture Volume", AK4641_PGA, 0, 127, 0),
+
+ SOC_DOUBLE_R("Master Playback Volume", AK4641_LATT,
+ AK4641_RATT, 0, 255, 1),
+
+ SOC_SINGLE("AUX In Volume", AK4641_VOL, 0, 15, 0),
+ SOC_SINGLE("Mic In Volume", AK4641_VOL, 4, 7, 0),
+ SOC_SINGLE("Mic In -4dB", AK4641_VOL, 7, 1, 0),
+
+ SOC_SINGLE("Equalizer", AK4641_DAC, 2, 1, 0),
+ SOC_SINGLE("EQ1 100 Hz", AK4641_EQLO, 0, 15, 1),
+ SOC_SINGLE("EQ2 250 Hz", AK4641_EQLO, 4, 15, 1),
+ SOC_SINGLE("EQ3 1 kHz", AK4641_EQMID, 0, 15, 1),
+ SOC_SINGLE("EQ4 3.5 kHz", AK4641_EQMID, 4, 15, 1),
+ SOC_SINGLE("EQ5 10 kHz", AK4641_EQHI, 0, 15, 1),
+};
+
+/* Mono 1 Mixer */
+static const struct snd_kcontrol_new ak4641_mono1_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Mono Sidetone Switch", AK4641_SIG1, 4, 1, 0),
+ SOC_DAPM_SINGLE("Mono Playback Switch", AK4641_SIG1, 5, 1, 0),
+};
+
+/* Stereo Mixer */
+static const struct snd_kcontrol_new ak4641_stereo_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4641_SIG2, 4, 1, 0),
+ SOC_DAPM_SINGLE("Playback Switch", AK4641_SIG2, 7, 1, 0),
+ SOC_DAPM_SINGLE("Aux Bypass Switch", AK4641_SIG2, 5, 1, 0),
+};
+
+/* Input Mixer */
+static const struct snd_kcontrol_new ak4641_input_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Capture Switch", AK4641_MIC, 2, 1, 0),
+ SOC_DAPM_SINGLE("Aux Capture Switch", AK4641_MIC, 5, 1, 0),
+};
+
+/* Mic mux */
+static const struct snd_kcontrol_new ak4641_mic_mux_control =
+ SOC_DAPM_ENUM("Mic Select", ak4641_enum[4]);
+
+/* Input mux */
+static const struct snd_kcontrol_new ak4641_input_mux_control =
+ SOC_DAPM_ENUM("Input Select", ak4641_enum[5]);
+
+/* HP L switch */
+static const struct snd_kcontrol_new ak4641_hpl_control =
+ SOC_DAPM_SINGLE("Switch", AK4641_SIG2, 1, 1, 0);
+
+/* HP R switch */
+static const struct snd_kcontrol_new ak4641_hpr_control =
+ SOC_DAPM_SINGLE("Switch", AK4641_SIG2, 0, 1, 0);
+
+/* mono 2 switch */
+static const struct snd_kcontrol_new ak4641_mono2_control =
+ SOC_DAPM_SINGLE("Switch", AK4641_SIG1, 0, 1, 0);
+
+/* ak4641 dapm widgets */
+static const struct snd_soc_dapm_widget ak4641_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4641_stereo_mixer_controls[0],
+ ARRAY_SIZE(ak4641_stereo_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4641_mono1_mixer_controls[0],
+ ARRAY_SIZE(ak4641_mono1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4641_input_mixer_controls[0],
+ ARRAY_SIZE(ak4641_input_mixer_controls)),
+ SND_SOC_DAPM_MUX("Mic Mux", SND_SOC_NOPM, 0, 0,
+ &ak4641_mic_mux_control),
+ SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+ &ak4641_input_mux_control),
+ SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0,
+ &ak4641_mono2_control),
+ /* speaker powersave bit */
+ SND_SOC_DAPM_SWITCH("Left Out Enable", SND_SOC_NOPM, 0, 0,
+ &ak4641_hpl_control),
+ SND_SOC_DAPM_SWITCH("Right Out Enable", SND_SOC_NOPM, 0, 0,
+ &ak4641_hpr_control),
+
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+ SND_SOC_DAPM_OUTPUT("MOUT1"),
+ SND_SOC_DAPM_OUTPUT("MOUT2"),
+ SND_SOC_DAPM_OUTPUT("MICOUT"),
+
+ SND_SOC_DAPM_ADC("ADC", "HiFi Capture", AK4641_PM1, 0, 0),
+ SND_SOC_DAPM_PGA("Mic", AK4641_PM1, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("AUX In", AK4641_PM1, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono Out", AK4641_PM1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Line Out", AK4641_PM1, 4, 0, NULL, 0),
+
+ SND_SOC_DAPM_DAC("DAC", "HiFi Playback", AK4641_PM2, 0, 0),
+ SND_SOC_DAPM_PGA("Mono Out 2", AK4641_PM2, 3, 0, NULL, 0),
+
+ SND_SOC_DAPM_ADC("Voice ADC", "Voice Capture", AK4641_BTIF, 0, 0),
+ SND_SOC_DAPM_ADC("Voice DAC", "Voice Playback", AK4641_BTIF, 1, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4641_MIC, 3, 0),
+ SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4641_MIC, 4, 0),
+
+ SND_SOC_DAPM_INPUT("MICIN"),
+ SND_SOC_DAPM_INPUT("MICEXT"),
+ SND_SOC_DAPM_INPUT("AUX"),
+ SND_SOC_DAPM_INPUT("AIN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Stereo Mixer */
+ {"Stereo Mixer", "Playback Switch", "DAC"},
+ {"Stereo Mixer", "Mic Sidetone Switch", "Input Mux"},
+ {"Stereo Mixer", "Aux Bypass Switch", "AUX In"},
+
+ /* Mono 1 Mixer */
+ {"Mono1 Mixer", "Mic Mono Sidetone Switch", "Input Mux"},
+ {"Mono1 Mixer", "Mono Playback Switch", "DAC"},
+
+ /* Mic */
+ {"Mic", NULL, "AIN"},
+ {"Mic Mux", "Internal", "Mic Int Bias"},
+ {"Mic Mux", "External", "Mic Ext Bias"},
+ {"Mic Int Bias", NULL, "MICIN"},
+ {"Mic Ext Bias", NULL, "MICEXT"},
+ {"MICOUT", NULL, "Mic Mux"},
+
+ /* Input Mux */
+ {"Input Mux", "Microphone", "Mic"},
+ {"Input Mux", "Voice DAC", "Voice DAC"},
+
+ /* Line Out */
+ {"LOUT", NULL, "Left Out Enable"},
+ {"ROUT", NULL, "Right Out Enable"},
+ {"Left Out Enable", "Switch", "Line Out"},
+ {"Right Out Enable", "Switch", "Line Out"},
+ {"Line Out", NULL, "Stereo Mixer"},
+
+ /* Mono 1 Out */
+ {"MOUT1", NULL, "Mono Out"},
+ {"Mono Out", NULL, "Mono1 Mixer"},
+
+ /* Mono 2 Out */
+ {"MOUT2", NULL, "Mono 2 Enable"},
+ {"Mono 2 Enable", "Switch", "Mono Out 2"},
+ {"Mono Out 2", NULL, "Stereo Mixer"},
+
+ {"Voice ADC", NULL, "Mono 2 Enable"},
+
+ /* Aux In */
+ {"AUX In", NULL, "AUX"},
+
+ /* ADC */
+ {"ADC", NULL, "Input Mixer"},
+ {"Input Mixer", "Mic Capture Switch", "Mic"},
+ {"Input Mixer", "Aux Capture Switch", "AUX In"},
+};
+
+static int ak4641_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ snd_soc_dapm_new_controls(dapm, ak4641_dapm_widgets,
+ ARRAY_SIZE(ak4641_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(dapm);
+ return 0;
+}
+
+static int ak4641_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 ak4641_priv *ak4641 = snd_soc_codec_get_drvdata(codec);
+
+ ak4641->sysclk = freq;
+ return 0;
+}
+
+static int ak4641_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ /* FIXME */
+
+ return 0;
+}
+
+static int ak4641_i2s_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_codec *codec = rtd->codec;
+ struct ak4641_priv *ak4641 = snd_soc_codec_get_drvdata(codec);
+ u8 mode2 = snd_soc_read(codec, AK4641_MODE2) & ~(0x3 << 5);
+ int rate = params_rate(params), fs = 256;
+
+ if (rate)
+ fs = ak4641->sysclk / rate;
+
+ /* set fs */
+ switch (fs) {
+ case 1024:
+ mode2 |= (0x2 << 5);
+ break;
+ case 512:
+ mode2 |= (0x1 << 5);
+ break;
+ case 256:
+ break;
+ }
+
+ /* set rate */
+ snd_soc_write(codec, AK4641_MODE2, mode2);
+ return 0;
+}
+
+static int ak4641_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 btif = snd_soc_read(codec, AK4641_BTIF) & ~(0x3 << 5);
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ btif |= (0x3 << 5);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ btif |= (0x2 << 5);
+ break;
+ case SND_SOC_DAIFMT_DSP_A: /* MSB after FRM */
+ btif |= (0x0 << 5);
+ break;
+ case SND_SOC_DAIFMT_DSP_B: /* MSB during FRM */
+ btif |= (0x1 << 5);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4641_BTIF, btif);
+ return 0;
+}
+
+static int ak4641_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 mode1 = 0;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ mode1 = 0x0002;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ mode1 = 0x0001;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4641_MODE1, mode1);
+ return 0;
+}
+
+static int ak4641_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, AK4641_DAC) & 0xffdf;
+ if (!mute)
+ snd_soc_write(codec, AK4641_DAC, mute_reg);
+ else
+ snd_soc_write(codec, AK4641_DAC, mute_reg | 0x20);
+ return 0;
+}
+
+static int ak4641_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 i, mute_reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ mute_reg = snd_soc_read(codec, AK4641_DAC) & 0xffdf;
+ snd_soc_write(codec, AK4641_DAC, mute_reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ mute_reg = snd_soc_read(codec, AK4641_DAC) & 0xffdf;
+ snd_soc_write(codec, AK4641_DAC, mute_reg | 0x20);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ i = snd_soc_read(codec, AK4641_PM1);
+ snd_soc_write(codec, AK4641_PM1, i | 0x80);
+ i = snd_soc_read(codec, AK4641_PM2);
+ snd_soc_write(codec, AK4641_PM2, i & (~0x80));
+ break;
+ case SND_SOC_BIAS_OFF:
+ i = snd_soc_read(codec, AK4641_PM1);
+ snd_soc_write(codec, AK4641_PM1, i & (~0x80));
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define AK4641_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+#define AK4641_RATES_BT (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000)
+#define AK4641_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
+
+static struct snd_soc_dai_ops ak4641_i2s_dai_ops = {
+ .hw_params = ak4641_i2s_hw_params,
+ .set_fmt = ak4641_i2s_set_dai_fmt,
+ .digital_mute = ak4641_mute,
+ .set_sysclk = ak4641_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_ops ak4641_pcm_dai_ops = {
+ .hw_params = ak4641_pcm_hw_params,
+ .set_fmt = ak4641_pcm_set_dai_fmt,
+ .digital_mute = ak4641_mute,
+ .set_sysclk = ak4641_set_dai_sysclk,
+};
+
+struct snd_soc_dai_driver ak4641_dai[] = {
+{
+ .name = "ak4641-hifi",
+ .id = 1,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4641_RATES,
+ .formats = AK4641_FORMATS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4641_RATES,
+ .formats = AK4641_FORMATS,
+ },
+ .ops = &ak4641_i2s_dai_ops,
+},
+{
+ .name = "ak4641-voice",
+ .id = 1,
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = AK4641_RATES_BT,
+ .formats = AK4641_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Voice Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = AK4641_RATES_BT,
+ .formats = AK4641_FORMATS,
+ },
+ .ops = &ak4641_pcm_dai_ops,
+},
+};
+
+static int ak4641_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ ak4641_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ak4641_resume(struct snd_soc_codec *codec)
+{
+ ak4641_sync(codec);
+ ak4641_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+
+static int ak4641_probe(struct snd_soc_codec *codec)
+{
+ struct ak4641_platform_data *pdata = codec->dev->platform_data;
+ int ret;
+
+
+ if (pdata) {
+ if (gpio_is_valid(pdata->gpio_power)) {
+ ret = gpio_request(pdata->gpio_power, "ak4641 power");
+ if (ret)
+ goto err_out;
+ }
+ if (gpio_is_valid(pdata->gpio_npdn)) {
+ ret = gpio_request(pdata->gpio_npdn, "ak4641 npdn");
+ if (ret)
+ goto err_gpio;
+ }
+
+ if (gpio_is_valid(pdata->gpio_power))
+ gpio_direction_output(pdata->gpio_power, 1);
+ if (gpio_is_valid(pdata->gpio_npdn)) {
+ gpio_direction_output(pdata->gpio_npdn, 0);
+ udelay(1); /* > 150 ns */
+ gpio_set_value(pdata->gpio_npdn, 1);
+ }
+ }
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ goto err_register;
+ }
+
+ /* power on device */
+ ak4641_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ snd_soc_add_controls(codec, ak4641_snd_controls,
+ ARRAY_SIZE(ak4641_snd_controls));
+ ak4641_add_widgets(codec);
+
+ return 0;
+
+err_register:
+ if (pdata) {
+ if (gpio_is_valid(pdata->gpio_power))
+ gpio_set_value(pdata->gpio_power, 0);
+ if (gpio_is_valid(pdata->gpio_npdn))
+ gpio_free(pdata->gpio_npdn);
+ }
+err_gpio:
+ if (pdata && gpio_is_valid(pdata->gpio_power))
+ gpio_free(pdata->gpio_power);
+err_out:
+ return ret;
+}
+
+static int ak4641_remove(struct snd_soc_codec *codec)
+{
+ struct ak4641_platform_data *pdata = codec->dev->platform_data;
+
+ ak4641_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ if (pdata) {
+ if (gpio_is_valid(pdata->gpio_power)) {
+ gpio_set_value(pdata->gpio_power, 0);
+ gpio_free(pdata->gpio_power);
+ }
+ if (gpio_is_valid(pdata->gpio_npdn))
+ gpio_free(pdata->gpio_npdn);
+ }
+ return 0;
+}
+
+
+static struct snd_soc_codec_driver soc_codec_dev_ak4641 = {
+ .probe = ak4641_probe,
+ .remove = ak4641_remove,
+ .suspend = ak4641_suspend,
+ .resume = ak4641_resume,
+ .set_bias_level = ak4641_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(ak4641_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = ak4641_reg,
+ .reg_cache_step = 1,
+};
+
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int __devinit ak4641_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct ak4641_priv *ak4641;
+ int ret;
+
+ ak4641 = kzalloc(sizeof(struct ak4641_priv), GFP_KERNEL);
+ if (!ak4641)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, ak4641);
+
+ ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_ak4641,
+ ak4641_dai, ARRAY_SIZE(ak4641_dai));
+ if (ret < 0)
+ kfree(ak4641);
+
+ return ret;
+}
+
+static int __devexit ak4641_i2c_remove(struct i2c_client *i2c)
+{
+ snd_soc_unregister_codec(&i2c->dev);
+ kfree(i2c_get_clientdata(i2c));
+ return 0;
+}
+
+static const struct i2c_device_id ak4641_i2c_id[] = {
+ { "ak4641", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4641_i2c_id);
+
+static struct i2c_driver ak4641_i2c_driver = {
+ .driver = {
+ .name = "ak4641-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4641_i2c_probe,
+ .remove = __devexit_p(ak4641_i2c_remove),
+ .id_table = ak4641_i2c_id,
+};
+#endif
+
+static int __init ak4641_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&ak4641_i2c_driver);
+ if (ret != 0)
+ pr_err("Failed to register AK4641 I2C driver: %d\n", ret);
+#endif
+ return ret;
+}
+module_init(ak4641_modinit);
+
+static void __exit ak4641_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&ak4641_i2c_driver);
+#endif
+}
+module_exit(ak4641_exit);
+
+MODULE_DESCRIPTION("SoC AK4641 driver");
+MODULE_AUTHOR("Harald Welte <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4641.h b/sound/soc/codecs/ak4641.h
new file mode 100644
index 0000000..4a26324
--- /dev/null
+++ b/sound/soc/codecs/ak4641.h
@@ -0,0 +1,47 @@
+/*
+ * ak4641.h -- AK4641 SoC Audio driver
+ *
+ * Copyright 2008 Harald Welte <[email protected]>
+ *
+ * Based on ak4535.h
+ *
+ * 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 _AK4641_H
+#define _AK4641_H
+
+/* AK4641 register space */
+
+#define AK4641_PM1 0x00
+#define AK4641_PM2 0x01
+#define AK4641_SIG1 0x02
+#define AK4641_SIG2 0x03
+#define AK4641_MODE1 0x04
+#define AK4641_MODE2 0x05
+#define AK4641_DAC 0x06
+#define AK4641_MIC 0x07
+#define AK4641_TIMER 0x08
+#define AK4641_ALC1 0x09
+#define AK4641_ALC2 0x0a
+#define AK4641_PGA 0x0b
+#define AK4641_LATT 0x0c
+#define AK4641_RATT 0x0d
+#define AK4641_VOL 0x0e
+#define AK4641_STATUS 0x0f
+#define AK4641_EQLO 0x10
+#define AK4641_EQMID 0x11
+#define AK4641_EQHI 0x12
+#define AK4641_BTIF 0x13
+
+#define AK4641_CACHEREGNUM 0x14
+
+
+
+#define AK4641_DAI_HIFI 0
+#define AK4641_DAI_VOICE 1
+
+
+#endif
--
1.7.0.4

2010-11-20 13:05:41

by Dmitry Artamonow

[permalink] [raw]
Subject: [PATCH 2/3] ASoC: add iPAQ hx4700 machine driver

AK4641 connected via I2S and I2C, jack detection via GPIO.

Signed-off-by: Philipp Zabel <[email protected]>
Signed-off-by: Dmitry Artamonow <[email protected]>
---
sound/soc/pxa/Kconfig | 9 ++
sound/soc/pxa/Makefile | 2 +
sound/soc/pxa/hx4700.c | 253 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 264 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/pxa/hx4700.c

diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
index 37f191b..1d7e7a5 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -154,6 +154,15 @@ config SND_SOC_RAUMFELD
help
Say Y if you want to add support for SoC audio on Raumfeld devices

+config SND_PXA2XX_SOC_HX4700
+ tristate "SoC Audio support for HP iPAQ hx4700"
+ depends on SND_PXA2XX_SOC && MACH_H4700
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_AK4641
+ help
+ Say Y if you want to add support for SoC audio on the
+ HP iPAQ hx4700.
+
config SND_PXA2XX_SOC_MAGICIAN
tristate "SoC Audio support for HTC Magician"
depends on SND_PXA2XX_SOC && MACH_MAGICIAN
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
index 0766016..af35762 100644
--- a/sound/soc/pxa/Makefile
+++ b/sound/soc/pxa/Makefile
@@ -22,6 +22,7 @@ snd-soc-palm27x-objs := palm27x.o
snd-soc-saarb-objs := saarb.o
snd-soc-tavorevb3-objs := tavorevb3.o
snd-soc-zylonite-objs := zylonite.o
+snd-soc-hx4700-objs := hx4700.o
snd-soc-magician-objs := magician.o
snd-soc-mioa701-objs := mioa701_wm9713.o
snd-soc-z2-objs := z2.o
@@ -37,6 +38,7 @@ obj-$(CONFIG_SND_PXA2XX_SOC_E800) += snd-soc-e800.o
obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o
obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o
obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o
+obj-$(CONFIG_SND_PXA2XX_SOC_HX4700) += snd-soc-hx4700.o
obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o
obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o
obj-$(CONFIG_SND_PXA2XX_SOC_Z2) += snd-soc-z2.o
diff --git a/sound/soc/pxa/hx4700.c b/sound/soc/pxa/hx4700.c
new file mode 100644
index 0000000..e629a88
--- /dev/null
+++ b/sound/soc/pxa/hx4700.c
@@ -0,0 +1,253 @@
+/*
+ * SoC audio for HP iPAQ hx4700
+ *
+ * Copyright (c) 2009 Philipp Zabel
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/hx4700.h>
+#include <asm/mach-types.h>
+#include "pxa2xx-i2s.h"
+
+#include "../codecs/ak4641.h"
+
+static struct snd_soc_jack hs_jack;
+
+/* Headphones jack detection DAPM pin */
+static struct snd_soc_jack_pin hs_jack_pin[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Speaker",
+ /* disable speaker when hp jack is inserted */
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1,
+ },
+};
+
+/* Headphones jack detection GPIO */
+static struct snd_soc_jack_gpio hs_jack_gpio = {
+ .gpio = GPIO75_HX4700_EARPHONE_nDET,
+ .invert = true,
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 200,
+};
+
+/*
+ * iPAQ hx4700 uses I2S for capture and playback.
+ */
+static int hx4700_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 = 0;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as output */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops hx4700_ops = {
+ .hw_params = hx4700_hw_params,
+};
+
+static int hx4700_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(GPIO107_HX4700_SPK_nSD, !!SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+static int hx4700_hp_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(GPIO92_HX4700_HP_DRIVER, !!SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+/* hx4700 machine dapm widgets */
+static const struct snd_soc_dapm_widget ak4641_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", hx4700_hp_power),
+ SND_SOC_DAPM_SPK("Speaker", hx4700_spk_power),
+ SND_SOC_DAPM_MIC("Built-in Microphone", NULL),
+};
+
+/* hx4700 machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* Headphone connected to LOUT, ROUT */
+ {"Headphone Jack", NULL, "LOUT"},
+ {"Headphone Jack", NULL, "ROUT"},
+
+ /* Speaker connected to MOUT2 */
+ {"Speaker", NULL, "MOUT2"},
+
+ /* Microphone connected to MICIN */
+ {"MICIN", NULL, "Built-in Microphone"},
+ {"AIN", NULL, "MICOUT"},
+};
+
+static struct snd_soc_card snd_soc_card_hx4700;
+
+/*
+ * Logic for a ak4641 as connected on a HP iPAQ hx4700
+ */
+static int hx4700_ak4641_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* NC codec pins */
+ /* FIXME: is anything connected here? */
+ snd_soc_dapm_nc_pin(dapm, "MOUT1");
+ snd_soc_dapm_nc_pin(dapm, "MICEXT");
+ snd_soc_dapm_nc_pin(dapm, "AUX");
+
+ /* Add hx4700 specific widgets */
+ snd_soc_dapm_new_controls(dapm, ak4641_dapm_widgets,
+ ARRAY_SIZE(ak4641_dapm_widgets));
+
+ /* Set up hx4700 specific audio path interconnects */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(dapm);
+
+ /* Jack detection API stuff */
+ err = snd_soc_jack_new(codec, "Headphone Jack",
+ SND_JACK_HEADPHONE, &hs_jack);
+ if (err)
+ return err;
+
+ err = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pin),
+ hs_jack_pin);
+ if (err)
+ return err;
+
+ err = snd_soc_jack_add_gpios(&hs_jack, 1, &hs_jack_gpio);
+
+ return err;
+}
+
+/* hx4700 digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link hx4700_dai[] = {
+{
+ .name = "ak4641",
+ .stream_name = "AK4641",
+ .cpu_dai_name = "pxa2xx-i2s",
+ .codec_dai_name = "ak4641-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "ak4641-codec.0-0012",
+ .init = hx4700_ak4641_init,
+ .ops = &hx4700_ops,
+},
+};
+
+/* hx4700 audio machine driver */
+static struct snd_soc_card snd_soc_card_hx4700 = {
+ .name = "iPAQ hx4700",
+ .dai_link = hx4700_dai,
+ .num_links = ARRAY_SIZE(hx4700_dai),
+};
+
+static struct platform_device *hx4700_snd_device;
+
+static int __init hx4700_init(void)
+{
+ int ret;
+
+ if (!machine_is_h4700())
+ return -ENODEV;
+
+ ret = gpio_request(GPIO107_HX4700_SPK_nSD, "SPK_POWER");
+ if (ret)
+ goto err_request_spk;
+ ret = gpio_request(GPIO92_HX4700_HP_DRIVER, "EP_POWER");
+ if (ret)
+ goto err_request_ep;
+
+ gpio_direction_output(GPIO107_HX4700_SPK_nSD, 1);
+ gpio_direction_output(GPIO92_HX4700_HP_DRIVER, 0);
+
+ hx4700_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!hx4700_snd_device) {
+ ret = -ENOMEM;
+ goto err_pdev;
+ }
+
+ platform_set_drvdata(hx4700_snd_device, &snd_soc_card_hx4700);
+ ret = platform_device_add(hx4700_snd_device);
+ if (ret) {
+ platform_device_put(hx4700_snd_device);
+ goto err_pdev;
+ }
+
+ return 0;
+
+err_pdev:
+ gpio_free(GPIO92_HX4700_HP_DRIVER);
+err_request_ep:
+ gpio_free(GPIO107_HX4700_SPK_nSD);
+err_request_spk:
+ return ret;
+}
+
+static void __exit hx4700_exit(void)
+{
+ snd_soc_jack_free_gpios(&hs_jack, 1, &hs_jack_gpio);
+ platform_device_unregister(hx4700_snd_device);
+
+ gpio_set_value(GPIO92_HX4700_HP_DRIVER, 0);
+ gpio_set_value(GPIO107_HX4700_SPK_nSD, 0);
+
+ gpio_free(GPIO92_HX4700_HP_DRIVER);
+ gpio_free(GPIO107_HX4700_SPK_nSD);
+}
+
+module_init(hx4700_init);
+module_exit(hx4700_exit);
+
+MODULE_AUTHOR("Philipp Zabel");
+MODULE_DESCRIPTION("ALSA SoC iPAQ hx4700");
+MODULE_LICENSE("GPL");
--
1.7.0.4

2010-11-20 13:29:01

by Dmitry Artamonow

[permalink] [raw]
Subject: [PATCH 3/3] pxa/hx4700: add I2C board info for AK4641 codec

From: Philipp Zabel <[email protected]>

From: Philipp Zabel <[email protected]>

Also rename AK4641 power GPIOs to document the codec chip.

Signed-off-by: Philipp Zabel <[email protected]>
---
arch/arm/mach-pxa/hx4700.c | 18 ++++++++++++++++++
arch/arm/mach-pxa/include/mach/hx4700.h | 4 ++--
2 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/arch/arm/mach-pxa/hx4700.c b/arch/arm/mach-pxa/hx4700.c
index 76d93a2..da9ebd1 100644
--- a/arch/arm/mach-pxa/hx4700.c
+++ b/arch/arm/mach-pxa/hx4700.c
@@ -45,6 +45,7 @@
#include <mach/irda.h>
#include <mach/pxa2xx_spi.h>

+#include <sound/ak4641.h>
#include <video/platform_lcd.h>
#include <video/w100fb.h>

@@ -801,6 +802,22 @@ static struct i2c_board_info __initdata pi2c_board_info[] = {
};

/*
+ * Asahi Kasei AK4641 on I2C
+ */
+
+static struct ak4641_platform_data ak4641_info = {
+ .gpio_power = GPIO27_HX4700_AK4641_POWER,
+ .gpio_npdn = GPIO109_HX4700_AK4641_nPDN,
+};
+
+static struct i2c_board_info i2c_board_info[] __initdata = {
+ {
+ I2C_BOARD_INFO("ak4641", 0x12),
+ .platform_data = &ak4641_info,
+ },
+};
+
+/*
* PCMCIA
*/

@@ -859,6 +876,7 @@ static void __init hx4700_init(void)
pxa_set_ficp_info(&ficp_info);
pxa27x_set_i2c_power_info(NULL);
pxa_set_i2c_info(NULL);
+ i2c_register_board_info(0, ARRAY_AND_SIZE(i2c_board_info));
i2c_register_board_info(1, ARRAY_AND_SIZE(pi2c_board_info));
pxa2xx_set_spi_info(2, &pxa_ssp2_master_info);
spi_register_board_info(ARRAY_AND_SIZE(tsc2046_board_info));
diff --git a/arch/arm/mach-pxa/include/mach/hx4700.h b/arch/arm/mach-pxa/include/mach/hx4700.h
index 3740844..73cd586 100644
--- a/arch/arm/mach-pxa/include/mach/hx4700.h
+++ b/arch/arm/mach-pxa/include/mach/hx4700.h
@@ -29,7 +29,7 @@
#define GPIO14_HX4700_nWLAN_IRQ 14
#define GPIO18_HX4700_RDY 18
#define GPIO22_HX4700_LCD_RL 22
-#define GPIO27_HX4700_CODEC_ON 27
+#define GPIO27_HX4700_AK4641_POWER 27
#define GPIO32_HX4700_RS232_ON 32
#define GPIO52_HX4700_CPU_nBATT_FAULT 52
#define GPIO58_HX4700_TSC2046_nPENIRQ 58
@@ -67,7 +67,7 @@
#define GPIO105_HX4700_nIR_ON 105
#define GPIO106_HX4700_CPU_BT_nRESET 106
#define GPIO107_HX4700_SPK_nSD 107
-#define GPIO109_HX4700_CODEC_nPDN 109
+#define GPIO109_HX4700_AK4641_nPDN 109
#define GPIO110_HX4700_LCD_LVDD_3V3_ON 110
#define GPIO111_HX4700_LCD_AVDD_3V3_ON 111
#define GPIO112_HX4700_LCD_N2V7_7V3_ON 112
--
1.7.0.4

2010-11-21 12:04:48

by Harald Welte

[permalink] [raw]
Subject: Re: [PATCH 0/3] ASoC: add support for audio on iPaq hx4700

Dmitry,

great to see that the code I once wrote still has some useful purpose.

I'm happy to see Philipp picking it up and you submitting it mainline.

Maybe, one of these days, I will also submit the gnufiish stuff ;)

--
- Harald Welte <[email protected]> http://laforge.gnumonks.org/
============================================================================
"Privacy in residential applications is a desirable marketing option."
(ETSI EN 300 175-7 Ch. A6)

2010-11-22 11:41:43

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 1/3] ASoC: Asahi Kasei AK4641 codec driver

On Sat, Nov 20, 2010 at 03:59:22PM +0300, Dmitry Artamonow wrote:
> A driver for the AK4641 codec used in iPAQ hx4700 and Glofiish M800
> among others.

Remember to CC maintainers on patches, as documented in SubmittingPatches.
This ensures that they see your patches and they don't get lost in the
mailing list.

> +/* codec private data */
> +struct ak4641_priv {
> + struct snd_soc_codec *codec;
> + u8 reg_cache[AK4641_CACHEREGNUM];

Shouldn't need the register cache any more - the core should be able to
take care of allocating and managing it for you.

> +static const char *ak4641_mono_gain[] = {"+6dB", "-17dB"};

Gain controls should be exported to userspace as volume controls with
TLV information.

> +static const char *ak4641_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"};

It is better to expose deemphasis controls as as on/off switch then
automatically select the best match for sample rate when enabled.

> +static const char *ak4641_mic_select[] = {"Internal", "External"};
> +static const char *ak4641_mic_or_dac[] = {"Microphone", "Voice DAC"};
> +
> +static const struct soc_enum ak4641_enum[] = {
> + SOC_ENUM_SINGLE(AK4641_SIG1, 7, 2, ak4641_mono_gain),

Don't do this, declare individual variables for each enum. Referencing
into the array by element number is really bad for legibility and as a
result maintainability - it's easy to be off by one in your count and
very hard to notice it when reading the code.

> + SOC_SINGLE("Mic Boost (+20dB) Switch", AK4641_MIC, 0, 1, 0),

This ought to be a volume control too.

> + SOC_SINGLE("ALC Volume", AK4641_ALC2, 0, 127, 0),
> + SOC_SINGLE("Capture Volume", AK4641_PGA, 0, 127, 0),
> + SOC_DOUBLE_R("Master Playback Volume", AK4641_LATT,
> + AK4641_RATT, 0, 255, 1),
> + SOC_SINGLE("AUX In Volume", AK4641_VOL, 0, 15, 0),
> + SOC_SINGLE("Mic In Volume", AK4641_VOL, 4, 7, 0),

It'd be nice to supply TLV information for these.

> + SOC_SINGLE("Mic In -4dB", AK4641_VOL, 7, 1, 0),

This should also be a Volume control.

> + SOC_SINGLE("Equalizer", AK4641_DAC, 2, 1, 0),

Should be "Equalizer Switch".

> +static const struct snd_kcontrol_new ak4641_hpl_control =
> + SOC_DAPM_SINGLE("Switch", AK4641_SIG2, 1, 1, 0);
> +
> +/* HP R switch */
> +static const struct snd_kcontrol_new ak4641_hpr_control =
> + SOC_DAPM_SINGLE("Switch", AK4641_SIG2, 0, 1, 0);

I'm not sure these should be DAPM controls - it'd be more normal to do
these as just regular controls - but it does depend if there's any way
of controlling the inputs individually. If there isn't then this makes
sense.

> +static int ak4641_pcm_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *dai)
> +{
> + /* FIXME */
> +
> + return 0;
> +}

Either fix this or remove it - no point in having the empty function.
If removing then just set the capabilities in the DAI defintion to be
whatever the hardware defaults are.

> +
> +static int ak4641_i2s_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_codec *codec = rtd->codec;
> + struct ak4641_priv *ak4641 = snd_soc_codec_get_drvdata(codec);
> + u8 mode2 = snd_soc_read(codec, AK4641_MODE2) & ~(0x3 << 5);
> + int rate = params_rate(params), fs = 256;
> +
> + if (rate)
> + fs = ak4641->sysclk / rate;

If there's no rate then return an error; that should never happen.

> +
> + /* set fs */
> + switch (fs) {
> + case 1024:
> + mode2 |= (0x2 << 5);
> + break;
> + case 512:
> + mode2 |= (0x1 << 5);
> + break;
> + case 256:
> + break;
> + }

I'd expect to see some reporting/handling of erronious setups here - if
you've got a clock set up for 44.1kHz and someone's trying to play 48kHz
for example. It'd also be nice if the driver were able to tell
userspace what the constraints imposed by sysclk are if sysclk has been
configured when opening the PCM, see wm8988 for an example of doing this.
This ensures that userspace won't try to do something unsupported.

> +#define AK4641_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
> + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
> + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)

SNDRV_PCM_RATE_8000_48000

> + if (gpio_is_valid(pdata->gpio_power))
> + gpio_direction_output(pdata->gpio_power, 1);
> + if (gpio_is_valid(pdata->gpio_npdn)) {
> + gpio_direction_output(pdata->gpio_npdn, 0);
> + udelay(1); /* > 150 ns */
> + gpio_set_value(pdata->gpio_npdn, 1);
> + }

Would it not make sense to manage the power GPIOs as part of the bias
management? That'd seem logical and would mean that when the driver
goes to _OFF you'd be using this feature also, which would presumably
reduce the power consumption still further. As things stand I don't see
anything that puts the GPIOs into a powersave mode.

> +static struct i2c_driver ak4641_i2c_driver = {
> + .driver = {
> + .name = "ak4641-codec",

Remove the -codec from the name.

> +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
> + ret = i2c_add_driver(&ak4641_i2c_driver);
> + if (ret != 0)
> + pr_err("Failed to register AK4641 I2C driver: %d\n", ret);
> +#endif

No need to make this conditional on I2C if the driver only supports I2C.

2010-11-22 11:42:50

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 2/3] ASoC: add iPAQ hx4700 machine driver

On Sat, Nov 20, 2010 at 03:59:23PM +0300, Dmitry Artamonow wrote:
> AK4641 connected via I2S and I2C, jack detection via GPIO.
>
> Signed-off-by: Philipp Zabel <[email protected]>
> Signed-off-by: Dmitry Artamonow <[email protected]>

This looks OK but depends on the CODEC driver.