[alsa-devel] [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
Foreword: Ralf suggested that it might be a good idea in order to allow for reasonable testing and to avoid build failures due to two-way dependencies in different parts of the kernel, that he applies all the patches once they have been acked by their respective maintainers, feeds them into -next and eventually sends the whole series to Linus. One exception will be the ASoC patches which will, due to major changes in the ASoC subsystem, go through the ASoC tree. So if you are a maintainer for one of the subsystem touched by this series and would rather see the patch going through your tree (given the patch is ok) please tell.
This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.
The JZ4740 has a mostly MIPS32 compatible core (no on cpu timers) and many on chip peripherals like RTC, NAND, MMC, OHCI, UDC, ADC, I2C, SPI, AC97, I2S, I2S Codec, UART and LCD controller.
The JZ4740 is mostly used in eBooks, PMPs and hand-held consoles. This series contains patches for the Qi Ben NanoNote clamshell device as the inital supported device.
Changes since v1: There have been some minor changes since v1, mostly code cleanup and some functional changes. One bigger change is that there is now a MFD driver for the ADC core which does IRQ demultiplexing for the ADC unit and synchronizes access to shared registers between the different users of the ADC core. The patch adding a defconfig for the Qi LB60 has been dropped. A detailed list of changes is present in each patch.
- Lars
Cc: Alessandro Zummo a.zummo@towertech.it Cc: Andrew Morton akpm@linux-foundation.org Cc: Anton Vorontsov cbouatmailru@gmail.com Cc: David Brownell dbrownell@users.sourceforge.net Cc: David Woodhouse dwmw2@infradead.org Cc: Greg Kroah-Hartman gregkh@suse.de Cc: Liam Girdwood lrg@slimlogic.co.uk Cc: Mark Brown broonie@opensource.wolfsonmicro.com Cc: Paul Gortmaker p_gortmaker@yahoo.com Cc: Samuel Ortiz sameo@linux.intel.com Cc: alsa-devel@alsa-project.org Cc: linux-fbdev@vger.kernel.org Cc: linux-mmc@vger.kernel.org Cc: linux-mtd@lists.infradead.org Cc: linux-usb@vger.kernel.org Cc: lm-sensors@lm-sensors.org Cc: rtc-linux@googlegroups.com
Lars-Peter Clausen (26): MIPS: Add base support for Ingenic JZ4740 System-on-a-Chip MIPS: jz4740: Add IRQ handler code MIPS: JZ4740: Add clock API support. MIPS: JZ4740: Add timer support MIPS: JZ4740: Add clocksource/clockevent support. MIPS: JZ4740: Add power-management and system reset support MIPS: JZ4740: Add setup code MIPS: JZ4740: Add gpio support MIPS: JZ4740: Add DMA support. MIPS: JZ4740: Add PWM support MIPS: JZ4740: Add serial support MIPS: JZ4740: Add prom support MIPS: JZ4740: Add platform devices MIPS: JZ4740: Add Kbuild files RTC: Add JZ4740 RTC driver fbdev: Add JZ4740 framebuffer driver MTD: Nand: Add JZ4740 NAND driver MMC: Add JZ4740 mmc driver USB: Add JZ4740 ohci support alsa: ASoC: Add JZ4740 codec driver alsa: ASoC: Add JZ4740 ASoC support MFD: Add JZ4740 ADC driver hwmon: Add JZ4740 ADC driver power: Add JZ4740 battery driver. MIPS: JZ4740: Add qi_lb60 board support alsa: ASoC: JZ4740: Add qi_lb60 board driver
arch/mips/Kbuild.platforms | 1 + arch/mips/Kconfig | 13 + arch/mips/include/asm/bootinfo.h | 6 + arch/mips/include/asm/cpu.h | 9 +- arch/mips/include/asm/mach-jz4740/base.h | 26 + arch/mips/include/asm/mach-jz4740/clock.h | 28 + arch/mips/include/asm/mach-jz4740/dma.h | 90 +++ arch/mips/include/asm/mach-jz4740/gpio.h | 398 +++++++++++ arch/mips/include/asm/mach-jz4740/irq.h | 57 ++ arch/mips/include/asm/mach-jz4740/platform.h | 36 + arch/mips/include/asm/mach-jz4740/timer.h | 22 + arch/mips/include/asm/mach-jz4740/war.h | 25 + arch/mips/jz4740/Kconfig | 12 + arch/mips/jz4740/Makefile | 20 + arch/mips/jz4740/Platform | 5 + arch/mips/jz4740/board-qi_lb60.c | 483 +++++++++++++ arch/mips/jz4740/clock-debugfs.c | 109 +++ arch/mips/jz4740/clock.c | 920 ++++++++++++++++++++++++ arch/mips/jz4740/clock.h | 76 ++ arch/mips/jz4740/dma.c | 289 ++++++++ arch/mips/jz4740/gpio.c | 601 ++++++++++++++++ arch/mips/jz4740/irq.c | 169 +++++ arch/mips/jz4740/irq.h | 21 + arch/mips/jz4740/platform.c | 284 ++++++++ arch/mips/jz4740/pm.c | 56 ++ arch/mips/jz4740/prom.c | 68 ++ arch/mips/jz4740/pwm.c | 169 +++++ arch/mips/jz4740/reset.c | 79 ++ arch/mips/jz4740/reset.h | 7 + arch/mips/jz4740/serial.c | 33 + arch/mips/jz4740/serial.h | 21 + arch/mips/jz4740/setup.c | 29 + arch/mips/jz4740/time.c | 144 ++++ arch/mips/jz4740/timer.c | 48 ++ arch/mips/jz4740/timer.h | 136 ++++ arch/mips/kernel/cpu-probe.c | 20 + arch/mips/mm/tlbex.c | 5 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/jz4740-hwmon.c | 206 ++++++ drivers/mfd/Kconfig | 8 + drivers/mfd/Makefile | 1 + drivers/mfd/jz4740-adc.c | 392 ++++++++++ drivers/mmc/host/Kconfig | 8 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/jz4740_mmc.c | 993 ++++++++++++++++++++++++++ drivers/mtd/nand/Kconfig | 6 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/jz4740_nand.c | 474 ++++++++++++ drivers/power/Kconfig | 11 + drivers/power/Makefile | 1 + drivers/power/jz4740-battery.c | 445 ++++++++++++ drivers/rtc/Kconfig | 11 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-jz4740.c | 341 +++++++++ drivers/usb/Kconfig | 1 + drivers/usb/host/ohci-hcd.c | 5 + drivers/usb/host/ohci-jz4740.c | 276 +++++++ drivers/video/Kconfig | 9 + drivers/video/Makefile | 1 + drivers/video/jz4740_fb.c | 817 +++++++++++++++++++++ include/linux/jz4740-adc.h | 32 + include/linux/jz4740_fb.h | 58 ++ include/linux/mmc/jz4740_mmc.h | 15 + include/linux/mtd/jz4740_nand.h | 34 + include/linux/power/jz4740-battery.h | 24 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/jz4740-codec.c | 514 +++++++++++++ sound/soc/codecs/jz4740-codec.h | 20 + sound/soc/jz4740/Kconfig | 23 + sound/soc/jz4740/Makefile | 13 + sound/soc/jz4740/jz4740-i2s.c | 540 ++++++++++++++ sound/soc/jz4740/jz4740-i2s.h | 18 + sound/soc/jz4740/jz4740-pcm.c | 373 ++++++++++ sound/soc/jz4740/jz4740-pcm.h | 22 + sound/soc/jz4740/qi_lb60.c | 167 +++++ 79 files changed, 10396 insertions(+), 1 deletions(-) create mode 100644 arch/mips/include/asm/mach-jz4740/base.h create mode 100644 arch/mips/include/asm/mach-jz4740/clock.h create mode 100644 arch/mips/include/asm/mach-jz4740/dma.h create mode 100644 arch/mips/include/asm/mach-jz4740/gpio.h create mode 100644 arch/mips/include/asm/mach-jz4740/irq.h create mode 100644 arch/mips/include/asm/mach-jz4740/platform.h create mode 100644 arch/mips/include/asm/mach-jz4740/timer.h create mode 100644 arch/mips/include/asm/mach-jz4740/war.h create mode 100644 arch/mips/jz4740/Kconfig create mode 100644 arch/mips/jz4740/Makefile create mode 100644 arch/mips/jz4740/Platform create mode 100644 arch/mips/jz4740/board-qi_lb60.c create mode 100644 arch/mips/jz4740/clock-debugfs.c create mode 100644 arch/mips/jz4740/clock.c create mode 100644 arch/mips/jz4740/clock.h create mode 100644 arch/mips/jz4740/dma.c create mode 100644 arch/mips/jz4740/gpio.c create mode 100644 arch/mips/jz4740/irq.c create mode 100644 arch/mips/jz4740/irq.h create mode 100644 arch/mips/jz4740/platform.c create mode 100644 arch/mips/jz4740/pm.c create mode 100644 arch/mips/jz4740/prom.c create mode 100644 arch/mips/jz4740/pwm.c create mode 100644 arch/mips/jz4740/reset.c create mode 100644 arch/mips/jz4740/reset.h create mode 100644 arch/mips/jz4740/serial.c create mode 100644 arch/mips/jz4740/serial.h create mode 100644 arch/mips/jz4740/setup.c create mode 100644 arch/mips/jz4740/time.c create mode 100644 arch/mips/jz4740/timer.c create mode 100644 arch/mips/jz4740/timer.h create mode 100644 drivers/hwmon/jz4740-hwmon.c create mode 100644 drivers/mfd/jz4740-adc.c create mode 100644 drivers/mmc/host/jz4740_mmc.c create mode 100644 drivers/mtd/nand/jz4740_nand.c create mode 100644 drivers/power/jz4740-battery.c create mode 100644 drivers/rtc/rtc-jz4740.c create mode 100644 drivers/usb/host/ohci-jz4740.c create mode 100644 drivers/video/jz4740_fb.c create mode 100644 include/linux/jz4740-adc.h create mode 100644 include/linux/jz4740_fb.h create mode 100644 include/linux/mmc/jz4740_mmc.h create mode 100644 include/linux/mtd/jz4740_nand.h create mode 100644 include/linux/power/jz4740-battery.h create mode 100644 sound/soc/codecs/jz4740-codec.c create mode 100644 sound/soc/codecs/jz4740-codec.h create mode 100644 sound/soc/jz4740/Kconfig create mode 100644 sound/soc/jz4740/Makefile create mode 100644 sound/soc/jz4740/jz4740-i2s.c create mode 100644 sound/soc/jz4740/jz4740-i2s.h create mode 100644 sound/soc/jz4740/jz4740-pcm.c create mode 100644 sound/soc/jz4740/jz4740-pcm.h create mode 100644 sound/soc/jz4740/qi_lb60.c
This patch adds support for the JZ4740 internal codec.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@opensource.wolfsonmicro.com Cc: Liam Girdwood lrg@slimlogic.co.uk Cc: alsa-devel@alsa-project.org
--- Changes since v1 - Put Kconfig entry in alphabetic order - Drop codec_set_fmt since the codec supports only one format - Rename "Capture Volume" control to "Master Capture Volume" - Drop unnecessary format checks - Add suspend/resume - Cleanup jz4740_codec_set_bias_level --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/jz4740-codec.c | 514 +++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/jz4740-codec.h | 20 ++ 4 files changed, 540 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/jz4740-codec.c create mode 100644 sound/soc/codecs/jz4740-codec.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c37c844..00d347d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -24,6 +24,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS42L51 if I2C select SND_SOC_CS4270 if I2C + select SND_SOC_JZ4740 if SOC_JZ4740 select SND_SOC_MAX9877 if I2C select SND_SOC_DA7210 if I2C select SND_SOC_PCM3008 @@ -142,6 +143,9 @@ config SND_SOC_CS4270_VD33_ERRATA config SND_SOC_CX20442 tristate
+config SND_SOC_JZ4740_CODEC + tristate + config SND_SOC_L3 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4a9c205..301b131 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -57,6 +57,7 @@ snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o +snd-soc-jz4740-codec-objs := jz4740-codec.o
# Amp snd-soc-max9877-objs := max9877.o @@ -80,6 +81,7 @@ 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 obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o +obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o diff --git a/sound/soc/codecs/jz4740-codec.c b/sound/soc/codecs/jz4740-codec.c new file mode 100644 index 0000000..35848b6 --- /dev/null +++ b/sound/soc/codecs/jz4740-codec.c @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc-dapm.h> +#include <sound/soc.h> + +#define JZ4740_REG_CODEC_1 0x0 +#define JZ4740_REG_CODEC_2 0x1 + +#define JZ4740_CODEC_1_LINE_ENABLE BIT(29) +#define JZ4740_CODEC_1_MIC_ENABLE BIT(28) +#define JZ4740_CODEC_1_SW1_ENABLE BIT(27) +#define JZ4740_CODEC_1_ADC_ENABLE BIT(26) +#define JZ4740_CODEC_1_SW2_ENABLE BIT(25) +#define JZ4740_CODEC_1_DAC_ENABLE BIT(24) +#define JZ4740_CODEC_1_VREF_DISABLE BIT(20) +#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19) +#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18) +#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17) +#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16) +#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14) +#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13) +#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12) +#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10)) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8) +#define JZ4740_CODEC_1_SUSPEND BIT(1) +#define JZ4740_CODEC_1_RESET BIT(0) + +#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29 +#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28 +#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27 +#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26 +#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25 +#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24 +#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14 +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8 + +#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000 +#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003 + +#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16 +#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0 + +static const uint32_t jz4740_codec_regs[] = { + 0x021b2302, 0x00170803, +}; + +struct jz4740_codec { + void __iomem *base; + struct resource *mem; + + uint32_t reg_cache[2]; + struct snd_soc_codec codec; +}; + +static inline struct jz4740_codec *codec_to_jz4740(struct snd_soc_codec *codec) +{ + return container_of(codec, struct jz4740_codec, codec); +} + +static unsigned int jz4740_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec); + return readl(jz4740_codec->base + (reg << 2)); +} + +static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec); + + jz4740_codec->reg_cache[reg] = val; + writel(val, jz4740_codec->base + (reg << 2)); + + return 0; +} + +static const struct snd_kcontrol_new jz4740_codec_controls[] = { + SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0), + SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0), + SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1), + SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0), +}; + +static const struct snd_kcontrol_new jz4740_codec_output_controls[] = { + SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_kcontrol_new jz4740_codec_input_controls[] = { + SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0), + SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0), + + SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1, + jz4740_codec_output_controls, + ARRAY_SIZE(jz4740_codec_output_controls)), + + SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0, + jz4740_codec_input_controls, + ARRAY_SIZE(jz4740_codec_input_controls)), + SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("LIN"), + SND_SOC_DAPM_INPUT("RIN"), +}; + +static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = { + {"Line Input", NULL, "LIN"}, + {"Line Input", NULL, "RIN"}, + + {"Input Mixer", "Line Capture Switch", "Line Input"}, + {"Input Mixer", "Mic Capture Switch", "MIC"}, + + {"ADC", NULL, "Input Mixer"}, + + {"Output Mixer", "Bypass Switch", "Input Mixer"}, + {"Output Mixer", "DAC Switch", "DAC"}, + + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, +}; + +static int jz4740_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + uint32_t val; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + switch (params_rate(params)) { + case 8000: + val = 0; + break; + case 11025: + val = 1; + break; + case 12000: + val = 2; + break; + case 16000: + val = 3; + break; + case 22050: + val = 4; + break; + case 24000: + val = 5; + break; + case 32000: + val = 6; + break; + case 44100: + val = 7; + break; + case 48000: + val = 8; + break; + default: + return -EINVAL; + } + + val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_SAMPLE_RATE_MASK, val); + + return 0; +} + +static struct snd_soc_dai_ops jz4740_codec_dai_ops = { + .hw_params = jz4740_codec_hw_params, +}; + +struct snd_soc_dai jz4740_codec_dai = { + .name = "jz4740", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .ops = &jz4740_codec_dai_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(jz4740_codec_dai); + +static void jz4740_codec_wakeup(struct snd_soc_codec *codec) +{ + int i; + uint32_t *cache = codec->reg_cache; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET); + udelay(2); + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0); + + for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i) + jz4740_codec_write(codec, i, cache[i]); +} + +static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + unsigned int mask; + unsigned int value; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = 0; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_STANDBY: + /* The only way to clear the suspend flag is to reset the codec */ + if (codec->bias_level == SND_SOC_BIAS_OFF) + jz4740_codec_wakeup(codec); + + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_OFF: + mask = JZ4740_CODEC_1_SUSPEND; + value = JZ4740_CODEC_1_SUSPEND; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + default: + break; + } + + codec->bias_level = level; + + return 0; +} + +static struct snd_soc_codec *jz4740_codec_codec; + +static int jz4740_codec_dev_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = jz4740_codec_codec; + + BUG_ON(!codec); + + socdev->card->codec = codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret) { + dev_err(&pdev->dev, "Failed to create pcms: %d\n", ret); + return ret; + } + + snd_soc_add_controls(codec, jz4740_codec_controls, + ARRAY_SIZE(jz4740_codec_controls)); + + snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets, + ARRAY_SIZE(jz4740_codec_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes, + ARRAY_SIZE(jz4740_codec_dapm_routes)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int jz4740_codec_dev_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_dev_jz4740_codec = { + .probe = jz4740_codec_dev_probe, + .remove = jz4740_codec_dev_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_jz4740_codec); + +#ifdef CONFIG_PM_SLEEP + +static int jz4740_codec_suspend(struct device *dev) +{ + struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev); + return jz4740_codec_set_bias_level(&jz4740_codec->codec, + SND_SOC_BIAS_OFF); +} + +static int jz4740_codec_resume(struct device *dev) +{ + struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev); + return jz4740_codec_set_bias_level(&jz4740_codec->codec, + SND_SOC_BIAS_STANDBY); +} + +static const struct dev_pm_ops jz4740_pm_ops = { + .suspend = jz4740_codec_suspend, + .resume = jz4740_codec_resume, +}; + +#define JZ4740_CODEC_PM_OPS (&jz4740_pm_ops) + +#else +#define JZ4740_CODEC_PM_OPS NULL +#endif + +static int __devinit jz4740_codec_probe(struct platform_device *pdev) +{ + int ret; + struct jz4740_codec *jz4740_codec; + struct snd_soc_codec *codec; + struct resource *mem; + + jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL); + if (!jz4740_codec) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "Failed to get mmio memory resource\n"); + ret = -ENOENT; + goto err_free_codec; + } + + mem = request_mem_region(mem->start, resource_size(mem), pdev->name); + if (!mem) { + dev_err(&pdev->dev, "Failed to request mmio memory region\n"); + ret = -EBUSY; + goto err_free_codec; + } + + jz4740_codec->base = ioremap(mem->start, resource_size(mem)); + if (!jz4740_codec->base) { + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); + ret = -EBUSY; + goto err_release_mem_region; + } + jz4740_codec->mem = mem; + + jz4740_codec_dai.dev = &pdev->dev; + + codec = &jz4740_codec->codec; + + codec->dev = &pdev->dev; + codec->name = "jz4740"; + codec->owner = THIS_MODULE; + + codec->read = jz4740_codec_read; + codec->write = jz4740_codec_write; + codec->set_bias_level = jz4740_codec_set_bias_level; + codec->bias_level = SND_SOC_BIAS_OFF; + + codec->dai = &jz4740_codec_dai; + codec->num_dai = 1; + + codec->reg_cache = jz4740_codec->reg_cache; + codec->reg_cache_size = 2; + memcpy(codec->reg_cache, jz4740_codec_regs, sizeof(jz4740_codec_regs)); + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + jz4740_codec_codec = codec; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE); + + platform_set_drvdata(pdev, jz4740_codec); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec\n"); + goto err_iounmap; + } + + ret = snd_soc_register_dai(&jz4740_codec_dai); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec dai\n"); + goto err_unregister_codec; + } + + jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; + +err_unregister_codec: + snd_soc_unregister_codec(codec); +err_iounmap: + iounmap(jz4740_codec->base); +err_release_mem_region: + release_mem_region(mem->start, resource_size(mem)); +err_free_codec: + kfree(jz4740_codec); + + return ret; +} + +static int __devexit jz4740_codec_remove(struct platform_device *pdev) +{ + struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev); + struct resource *mem = jz4740_codec->mem; + + snd_soc_unregister_dai(&jz4740_codec_dai); + snd_soc_unregister_codec(&jz4740_codec->codec); + + iounmap(jz4740_codec->base); + release_mem_region(mem->start, resource_size(mem)); + + platform_set_drvdata(pdev, NULL); + kfree(jz4740_codec); + + return 0; +} + +static struct platform_driver jz4740_codec_driver = { + .probe = jz4740_codec_probe, + .remove = __devexit_p(jz4740_codec_remove), + .driver = { + .name = "jz4740-codec", + .owner = THIS_MODULE, + .pm = JZ4740_CODEC_PM_OPS, + }, +}; + +static int __init jz4740_codec_init(void) +{ + return platform_driver_register(&jz4740_codec_driver); +} +module_init(jz4740_codec_init); + +static void __exit jz4740_codec_exit(void) +{ + platform_driver_unregister(&jz4740_codec_driver); +} +module_exit(jz4740_codec_exit); + +MODULE_DESCRIPTION("JZ4740 SoC internal codec driver"); +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:jz4740-codec"); diff --git a/sound/soc/codecs/jz4740-codec.h b/sound/soc/codecs/jz4740-codec.h new file mode 100644 index 0000000..b5a0691 --- /dev/null +++ b/sound/soc/codecs/jz4740-codec.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __SND_SOC_CODECS_JZ4740_CODEC_H__ +#define __SND_SOC_CODECS_JZ4740_CODEC_H__ + +extern struct snd_soc_dai jz4740_codec_dai; +extern struct snd_soc_codec_device soc_codec_dev_jz4740_codec; + +#endif
This patch adds support for the JZ4740 internal codec.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@opensource.wolfsonmicro.com Cc: Liam Girdwood lrg@slimlogic.co.uk Cc: alsa-devel@alsa-project.org
-- Changes since v1 - Put Kconfig entry in alphabetic order - Drop codec_set_fmt since the codec supports only one format - Rename "Capture Volume" control to "Master Capture Volume" - Drop unnecessary format checks - Add suspend/resume - Cleanup jz4740_codec_set_bias_level
Changes since v2 - Drop codec prefix from the filename. Note that I keeped it for the object name, because otherwise when build as a module it will clash with the ASoC JZ4740 platform support. --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/jz4740.c | 514 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/jz4740.h | 20 ++ 4 files changed, 540 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/jz4740.c create mode 100644 sound/soc/codecs/jz4740.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c37c844..00d347d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -24,6 +24,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS42L51 if I2C select SND_SOC_CS4270 if I2C + select SND_SOC_JZ4740 if SOC_JZ4740 select SND_SOC_MAX9877 if I2C select SND_SOC_DA7210 if I2C select SND_SOC_PCM3008 @@ -142,6 +143,9 @@ config SND_SOC_CS4270_VD33_ERRATA config SND_SOC_CX20442 tristate
+config SND_SOC_JZ4740_CODEC + tristate + config SND_SOC_L3 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4a9c205..d8d9eeb 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -57,6 +57,7 @@ snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o +snd-soc-jz4740-codec-objs := jz4740.o
# Amp snd-soc-max9877-objs := max9877.o @@ -80,6 +81,7 @@ 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 obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o +obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c new file mode 100644 index 0000000..35848b6 --- /dev/null +++ b/sound/soc/codecs/jz4740.c @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc-dapm.h> +#include <sound/soc.h> + +#define JZ4740_REG_CODEC_1 0x0 +#define JZ4740_REG_CODEC_2 0x1 + +#define JZ4740_CODEC_1_LINE_ENABLE BIT(29) +#define JZ4740_CODEC_1_MIC_ENABLE BIT(28) +#define JZ4740_CODEC_1_SW1_ENABLE BIT(27) +#define JZ4740_CODEC_1_ADC_ENABLE BIT(26) +#define JZ4740_CODEC_1_SW2_ENABLE BIT(25) +#define JZ4740_CODEC_1_DAC_ENABLE BIT(24) +#define JZ4740_CODEC_1_VREF_DISABLE BIT(20) +#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19) +#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18) +#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17) +#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16) +#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14) +#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13) +#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12) +#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10)) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8) +#define JZ4740_CODEC_1_SUSPEND BIT(1) +#define JZ4740_CODEC_1_RESET BIT(0) + +#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29 +#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28 +#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27 +#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26 +#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25 +#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24 +#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14 +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8 + +#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000 +#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003 + +#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16 +#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0 + +static const uint32_t jz4740_codec_regs[] = { + 0x021b2302, 0x00170803, +}; + +struct jz4740_codec { + void __iomem *base; + struct resource *mem; + + uint32_t reg_cache[2]; + struct snd_soc_codec codec; +}; + +static inline struct jz4740_codec *codec_to_jz4740(struct snd_soc_codec *codec) +{ + return container_of(codec, struct jz4740_codec, codec); +} + +static unsigned int jz4740_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec); + return readl(jz4740_codec->base + (reg << 2)); +} + +static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec); + + jz4740_codec->reg_cache[reg] = val; + writel(val, jz4740_codec->base + (reg << 2)); + + return 0; +} + +static const struct snd_kcontrol_new jz4740_codec_controls[] = { + SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0), + SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0), + SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1), + SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0), +}; + +static const struct snd_kcontrol_new jz4740_codec_output_controls[] = { + SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_kcontrol_new jz4740_codec_input_controls[] = { + SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0), + SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0), + + SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1, + jz4740_codec_output_controls, + ARRAY_SIZE(jz4740_codec_output_controls)), + + SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0, + jz4740_codec_input_controls, + ARRAY_SIZE(jz4740_codec_input_controls)), + SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("LIN"), + SND_SOC_DAPM_INPUT("RIN"), +}; + +static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = { + {"Line Input", NULL, "LIN"}, + {"Line Input", NULL, "RIN"}, + + {"Input Mixer", "Line Capture Switch", "Line Input"}, + {"Input Mixer", "Mic Capture Switch", "MIC"}, + + {"ADC", NULL, "Input Mixer"}, + + {"Output Mixer", "Bypass Switch", "Input Mixer"}, + {"Output Mixer", "DAC Switch", "DAC"}, + + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, +}; + +static int jz4740_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + uint32_t val; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + switch (params_rate(params)) { + case 8000: + val = 0; + break; + case 11025: + val = 1; + break; + case 12000: + val = 2; + break; + case 16000: + val = 3; + break; + case 22050: + val = 4; + break; + case 24000: + val = 5; + break; + case 32000: + val = 6; + break; + case 44100: + val = 7; + break; + case 48000: + val = 8; + break; + default: + return -EINVAL; + } + + val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_SAMPLE_RATE_MASK, val); + + return 0; +} + +static struct snd_soc_dai_ops jz4740_codec_dai_ops = { + .hw_params = jz4740_codec_hw_params, +}; + +struct snd_soc_dai jz4740_codec_dai = { + .name = "jz4740", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .ops = &jz4740_codec_dai_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(jz4740_codec_dai); + +static void jz4740_codec_wakeup(struct snd_soc_codec *codec) +{ + int i; + uint32_t *cache = codec->reg_cache; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET); + udelay(2); + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0); + + for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i) + jz4740_codec_write(codec, i, cache[i]); +} + +static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + unsigned int mask; + unsigned int value; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = 0; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_STANDBY: + /* The only way to clear the suspend flag is to reset the codec */ + if (codec->bias_level == SND_SOC_BIAS_OFF) + jz4740_codec_wakeup(codec); + + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_OFF: + mask = JZ4740_CODEC_1_SUSPEND; + value = JZ4740_CODEC_1_SUSPEND; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + default: + break; + } + + codec->bias_level = level; + + return 0; +} + +static struct snd_soc_codec *jz4740_codec_codec; + +static int jz4740_codec_dev_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = jz4740_codec_codec; + + BUG_ON(!codec); + + socdev->card->codec = codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret) { + dev_err(&pdev->dev, "Failed to create pcms: %d\n", ret); + return ret; + } + + snd_soc_add_controls(codec, jz4740_codec_controls, + ARRAY_SIZE(jz4740_codec_controls)); + + snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets, + ARRAY_SIZE(jz4740_codec_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes, + ARRAY_SIZE(jz4740_codec_dapm_routes)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int jz4740_codec_dev_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_dev_jz4740_codec = { + .probe = jz4740_codec_dev_probe, + .remove = jz4740_codec_dev_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_jz4740_codec); + +#ifdef CONFIG_PM_SLEEP + +static int jz4740_codec_suspend(struct device *dev) +{ + struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev); + return jz4740_codec_set_bias_level(&jz4740_codec->codec, + SND_SOC_BIAS_OFF); +} + +static int jz4740_codec_resume(struct device *dev) +{ + struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev); + return jz4740_codec_set_bias_level(&jz4740_codec->codec, + SND_SOC_BIAS_STANDBY); +} + +static const struct dev_pm_ops jz4740_pm_ops = { + .suspend = jz4740_codec_suspend, + .resume = jz4740_codec_resume, +}; + +#define JZ4740_CODEC_PM_OPS (&jz4740_pm_ops) + +#else +#define JZ4740_CODEC_PM_OPS NULL +#endif + +static int __devinit jz4740_codec_probe(struct platform_device *pdev) +{ + int ret; + struct jz4740_codec *jz4740_codec; + struct snd_soc_codec *codec; + struct resource *mem; + + jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL); + if (!jz4740_codec) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "Failed to get mmio memory resource\n"); + ret = -ENOENT; + goto err_free_codec; + } + + mem = request_mem_region(mem->start, resource_size(mem), pdev->name); + if (!mem) { + dev_err(&pdev->dev, "Failed to request mmio memory region\n"); + ret = -EBUSY; + goto err_free_codec; + } + + jz4740_codec->base = ioremap(mem->start, resource_size(mem)); + if (!jz4740_codec->base) { + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); + ret = -EBUSY; + goto err_release_mem_region; + } + jz4740_codec->mem = mem; + + jz4740_codec_dai.dev = &pdev->dev; + + codec = &jz4740_codec->codec; + + codec->dev = &pdev->dev; + codec->name = "jz4740"; + codec->owner = THIS_MODULE; + + codec->read = jz4740_codec_read; + codec->write = jz4740_codec_write; + codec->set_bias_level = jz4740_codec_set_bias_level; + codec->bias_level = SND_SOC_BIAS_OFF; + + codec->dai = &jz4740_codec_dai; + codec->num_dai = 1; + + codec->reg_cache = jz4740_codec->reg_cache; + codec->reg_cache_size = 2; + memcpy(codec->reg_cache, jz4740_codec_regs, sizeof(jz4740_codec_regs)); + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + jz4740_codec_codec = codec; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE); + + platform_set_drvdata(pdev, jz4740_codec); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec\n"); + goto err_iounmap; + } + + ret = snd_soc_register_dai(&jz4740_codec_dai); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec dai\n"); + goto err_unregister_codec; + } + + jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; + +err_unregister_codec: + snd_soc_unregister_codec(codec); +err_iounmap: + iounmap(jz4740_codec->base); +err_release_mem_region: + release_mem_region(mem->start, resource_size(mem)); +err_free_codec: + kfree(jz4740_codec); + + return ret; +} + +static int __devexit jz4740_codec_remove(struct platform_device *pdev) +{ + struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev); + struct resource *mem = jz4740_codec->mem; + + snd_soc_unregister_dai(&jz4740_codec_dai); + snd_soc_unregister_codec(&jz4740_codec->codec); + + iounmap(jz4740_codec->base); + release_mem_region(mem->start, resource_size(mem)); + + platform_set_drvdata(pdev, NULL); + kfree(jz4740_codec); + + return 0; +} + +static struct platform_driver jz4740_codec_driver = { + .probe = jz4740_codec_probe, + .remove = __devexit_p(jz4740_codec_remove), + .driver = { + .name = "jz4740-codec", + .owner = THIS_MODULE, + .pm = JZ4740_CODEC_PM_OPS, + }, +}; + +static int __init jz4740_codec_init(void) +{ + return platform_driver_register(&jz4740_codec_driver); +} +module_init(jz4740_codec_init); + +static void __exit jz4740_codec_exit(void) +{ + platform_driver_unregister(&jz4740_codec_driver); +} +module_exit(jz4740_codec_exit); + +MODULE_DESCRIPTION("JZ4740 SoC internal codec driver"); +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:jz4740-codec"); diff --git a/sound/soc/codecs/jz4740.h b/sound/soc/codecs/jz4740.h new file mode 100644 index 0000000..b5a0691 --- /dev/null +++ b/sound/soc/codecs/jz4740.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __SND_SOC_CODECS_JZ4740_CODEC_H__ +#define __SND_SOC_CODECS_JZ4740_CODEC_H__ + +extern struct snd_soc_dai jz4740_codec_dai; +extern struct snd_soc_codec_device soc_codec_dev_jz4740_codec; + +#endif
On Sat, Jun 19, 2010 at 04:49:53PM +0200, Lars-Peter Clausen wrote:
This looks good, just one thing:
+#ifdef CONFIG_PM_SLEEP
+static int jz4740_codec_suspend(struct device *dev) +{
- struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev);
- return jz4740_codec_set_bias_level(&jz4740_codec->codec,
SND_SOC_BIAS_OFF);
+}
You've got these set up on the CODEC platform device rather than the ASoC CODEC. This means that the suspend and resume will happen out of sequence with the rest of the ASoC suspend and resume which could result in poor performance or bugs if the device is suspended while the core still thinks it's active. For example, ASoC will use DAPM to shut down the CODEC and it's possible that the CODEC could be suspended (and generate an audible noise) while an external amplifier is still powered, worsening the problem.
This patch adds support for the JZ4740 internal codec.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@opensource.wolfsonmicro.com Cc: Liam Girdwood lrg@slimlogic.co.uk Cc: alsa-devel@alsa-project.org
--- Changes since v1 - Put Kconfig entry in alphabetic order - Drop codec_set_fmt since the codec supports only one format - Rename "Capture Volume" control to "Master Capture Volume" - Drop unnecessary format checks - Add suspend/resume - Cleanup jz4740_codec_set_bias_level
Changes since v2 - Drop codec prefix from the filename. Note that I keeped it for the object name, because otherwise when build as a module it will clash with the ASoC JZ4740 platform support.
Changes since v3 - Hook up suspend/resume callbacks to the codec instead of the platform device --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/jz4740.c | 511 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/jz4740.h | 20 ++ 4 files changed, 537 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/jz4740.c create mode 100644 sound/soc/codecs/jz4740.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c37c844..00d347d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -24,6 +24,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS42L51 if I2C select SND_SOC_CS4270 if I2C + select SND_SOC_JZ4740 if SOC_JZ4740 select SND_SOC_MAX9877 if I2C select SND_SOC_DA7210 if I2C select SND_SOC_PCM3008 @@ -142,6 +143,9 @@ config SND_SOC_CS4270_VD33_ERRATA config SND_SOC_CX20442 tristate
+config SND_SOC_JZ4740_CODEC + tristate + config SND_SOC_L3 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4a9c205..d8d9eeb 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -57,6 +57,7 @@ snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o +snd-soc-jz4740-codec-objs := jz4740.o
# Amp snd-soc-max9877-objs := max9877.o @@ -80,6 +81,7 @@ 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 obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o +obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c new file mode 100644 index 0000000..66557de --- /dev/null +++ b/sound/soc/codecs/jz4740.c @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc-dapm.h> +#include <sound/soc.h> + +#define JZ4740_REG_CODEC_1 0x0 +#define JZ4740_REG_CODEC_2 0x1 + +#define JZ4740_CODEC_1_LINE_ENABLE BIT(29) +#define JZ4740_CODEC_1_MIC_ENABLE BIT(28) +#define JZ4740_CODEC_1_SW1_ENABLE BIT(27) +#define JZ4740_CODEC_1_ADC_ENABLE BIT(26) +#define JZ4740_CODEC_1_SW2_ENABLE BIT(25) +#define JZ4740_CODEC_1_DAC_ENABLE BIT(24) +#define JZ4740_CODEC_1_VREF_DISABLE BIT(20) +#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19) +#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18) +#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17) +#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16) +#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14) +#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13) +#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12) +#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10)) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8) +#define JZ4740_CODEC_1_SUSPEND BIT(1) +#define JZ4740_CODEC_1_RESET BIT(0) + +#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29 +#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28 +#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27 +#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26 +#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25 +#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24 +#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14 +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8 + +#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000 +#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003 + +#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16 +#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0 + +static const uint32_t jz4740_codec_regs[] = { + 0x021b2302, 0x00170803, +}; + +struct jz4740_codec { + void __iomem *base; + struct resource *mem; + + uint32_t reg_cache[2]; + struct snd_soc_codec codec; +}; + +static inline struct jz4740_codec *codec_to_jz4740(struct snd_soc_codec *codec) +{ + return container_of(codec, struct jz4740_codec, codec); +} + +static unsigned int jz4740_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec); + return readl(jz4740_codec->base + (reg << 2)); +} + +static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec); + + jz4740_codec->reg_cache[reg] = val; + writel(val, jz4740_codec->base + (reg << 2)); + + return 0; +} + +static const struct snd_kcontrol_new jz4740_codec_controls[] = { + SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0), + SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0), + SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1), + SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0), +}; + +static const struct snd_kcontrol_new jz4740_codec_output_controls[] = { + SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_kcontrol_new jz4740_codec_input_controls[] = { + SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0), + SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0), + + SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1, + jz4740_codec_output_controls, + ARRAY_SIZE(jz4740_codec_output_controls)), + + SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0, + jz4740_codec_input_controls, + ARRAY_SIZE(jz4740_codec_input_controls)), + SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("LIN"), + SND_SOC_DAPM_INPUT("RIN"), +}; + +static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = { + {"Line Input", NULL, "LIN"}, + {"Line Input", NULL, "RIN"}, + + {"Input Mixer", "Line Capture Switch", "Line Input"}, + {"Input Mixer", "Mic Capture Switch", "MIC"}, + + {"ADC", NULL, "Input Mixer"}, + + {"Output Mixer", "Bypass Switch", "Input Mixer"}, + {"Output Mixer", "DAC Switch", "DAC"}, + + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, +}; + +static int jz4740_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + uint32_t val; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + switch (params_rate(params)) { + case 8000: + val = 0; + break; + case 11025: + val = 1; + break; + case 12000: + val = 2; + break; + case 16000: + val = 3; + break; + case 22050: + val = 4; + break; + case 24000: + val = 5; + break; + case 32000: + val = 6; + break; + case 44100: + val = 7; + break; + case 48000: + val = 8; + break; + default: + return -EINVAL; + } + + val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_SAMPLE_RATE_MASK, val); + + return 0; +} + +static struct snd_soc_dai_ops jz4740_codec_dai_ops = { + .hw_params = jz4740_codec_hw_params, +}; + +struct snd_soc_dai jz4740_codec_dai = { + .name = "jz4740", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .ops = &jz4740_codec_dai_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(jz4740_codec_dai); + +static void jz4740_codec_wakeup(struct snd_soc_codec *codec) +{ + int i; + uint32_t *cache = codec->reg_cache; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET); + udelay(2); + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0); + + for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i) + jz4740_codec_write(codec, i, cache[i]); +} + +static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + unsigned int mask; + unsigned int value; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = 0; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_STANDBY: + /* The only way to clear the suspend flag is to reset the codec */ + if (codec->bias_level == SND_SOC_BIAS_OFF) + jz4740_codec_wakeup(codec); + + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_OFF: + mask = JZ4740_CODEC_1_SUSPEND; + value = JZ4740_CODEC_1_SUSPEND; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + default: + break; + } + + codec->bias_level = level; + + return 0; +} + +static struct snd_soc_codec *jz4740_codec_codec; + +static int jz4740_codec_dev_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = jz4740_codec_codec; + + BUG_ON(!codec); + + socdev->card->codec = codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret) { + dev_err(&pdev->dev, "Failed to create pcms: %d\n", ret); + return ret; + } + + snd_soc_add_controls(codec, jz4740_codec_controls, + ARRAY_SIZE(jz4740_codec_controls)); + + snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets, + ARRAY_SIZE(jz4740_codec_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes, + ARRAY_SIZE(jz4740_codec_dapm_routes)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int jz4740_codec_dev_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; +} + +#ifdef CONFIG_PM_SLEEP + +static int jz4740_codec_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int jz4740_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); +} + +#else +#define jz4740_codec_suspend NULL +#define jz4740_codec_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_jz4740_codec = { + .probe = jz4740_codec_dev_probe, + .remove = jz4740_codec_dev_remove, + .suspend = jz4740_codec_suspend, + .resume = jz4740_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_jz4740_codec); + +static int __devinit jz4740_codec_probe(struct platform_device *pdev) +{ + int ret; + struct jz4740_codec *jz4740_codec; + struct snd_soc_codec *codec; + struct resource *mem; + + jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL); + if (!jz4740_codec) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "Failed to get mmio memory resource\n"); + ret = -ENOENT; + goto err_free_codec; + } + + mem = request_mem_region(mem->start, resource_size(mem), pdev->name); + if (!mem) { + dev_err(&pdev->dev, "Failed to request mmio memory region\n"); + ret = -EBUSY; + goto err_free_codec; + } + + jz4740_codec->base = ioremap(mem->start, resource_size(mem)); + if (!jz4740_codec->base) { + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); + ret = -EBUSY; + goto err_release_mem_region; + } + jz4740_codec->mem = mem; + + jz4740_codec_dai.dev = &pdev->dev; + + codec = &jz4740_codec->codec; + + codec->dev = &pdev->dev; + codec->name = "jz4740"; + codec->owner = THIS_MODULE; + + codec->read = jz4740_codec_read; + codec->write = jz4740_codec_write; + codec->set_bias_level = jz4740_codec_set_bias_level; + codec->bias_level = SND_SOC_BIAS_OFF; + + codec->dai = &jz4740_codec_dai; + codec->num_dai = 1; + + codec->reg_cache = jz4740_codec->reg_cache; + codec->reg_cache_size = 2; + memcpy(codec->reg_cache, jz4740_codec_regs, sizeof(jz4740_codec_regs)); + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + jz4740_codec_codec = codec; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE); + + platform_set_drvdata(pdev, jz4740_codec); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec\n"); + goto err_iounmap; + } + + ret = snd_soc_register_dai(&jz4740_codec_dai); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec dai\n"); + goto err_unregister_codec; + } + + jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; + +err_unregister_codec: + snd_soc_unregister_codec(codec); +err_iounmap: + iounmap(jz4740_codec->base); +err_release_mem_region: + release_mem_region(mem->start, resource_size(mem)); +err_free_codec: + kfree(jz4740_codec); + + return ret; +} + +static int __devexit jz4740_codec_remove(struct platform_device *pdev) +{ + struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev); + struct resource *mem = jz4740_codec->mem; + + snd_soc_unregister_dai(&jz4740_codec_dai); + snd_soc_unregister_codec(&jz4740_codec->codec); + + iounmap(jz4740_codec->base); + release_mem_region(mem->start, resource_size(mem)); + + platform_set_drvdata(pdev, NULL); + kfree(jz4740_codec); + + return 0; +} + +static struct platform_driver jz4740_codec_driver = { + .probe = jz4740_codec_probe, + .remove = __devexit_p(jz4740_codec_remove), + .driver = { + .name = "jz4740-codec", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_codec_init(void) +{ + return platform_driver_register(&jz4740_codec_driver); +} +module_init(jz4740_codec_init); + +static void __exit jz4740_codec_exit(void) +{ + platform_driver_unregister(&jz4740_codec_driver); +} +module_exit(jz4740_codec_exit); + +MODULE_DESCRIPTION("JZ4740 SoC internal codec driver"); +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:jz4740-codec"); diff --git a/sound/soc/codecs/jz4740.h b/sound/soc/codecs/jz4740.h new file mode 100644 index 0000000..b5a0691 --- /dev/null +++ b/sound/soc/codecs/jz4740.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __SND_SOC_CODECS_JZ4740_CODEC_H__ +#define __SND_SOC_CODECS_JZ4740_CODEC_H__ + +extern struct snd_soc_dai jz4740_codec_dai; +extern struct snd_soc_codec_device soc_codec_dev_jz4740_codec; + +#endif
On Tue, 2010-06-22 at 00:46 +0200, Lars-Peter Clausen wrote:
This patch adds support for the JZ4740 internal codec.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@opensource.wolfsonmicro.com Cc: Liam Girdwood lrg@slimlogic.co.uk Cc: alsa-devel@alsa-project.org
Changes since v1
- Put Kconfig entry in alphabetic order
- Drop codec_set_fmt since the codec supports only one format
- Rename "Capture Volume" control to "Master Capture Volume"
- Drop unnecessary format checks
- Add suspend/resume
- Cleanup jz4740_codec_set_bias_level
Changes since v2
- Drop codec prefix from the filename. Note that I keeped it for the object name, because otherwise when build as a module it will clash with the ASoC JZ4740 platform support.
Changes since v3
- Hook up suspend/resume callbacks to the codec instead of the platform device
Acked-by: Liam Girdwood lrg@slimlogic.co.uk
This patch adds ASoC support for JZ4740 SoCs I2S module.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@opensource.wolfsonmicro.com Cc: Liam Girdwood lrg@slimlogic.co.uk Cc: alsa-devel@alsa-project.org
--- Changes since v1 - i2s: Properly free and disable clocks in case of an error in probe - i2s: Drop unnecessary format checks which are already done in the core - i2s: Drop set_clkdiv - pcm: Refactor dma buffer position handling to be better comprehensible - Cleanup and fix Kconfig --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/jz4740/Kconfig | 14 + sound/soc/jz4740/Makefile | 9 + sound/soc/jz4740/jz4740-i2s.c | 540 +++++++++++++++++++++++++++++++++++++++++ sound/soc/jz4740/jz4740-i2s.h | 18 ++ sound/soc/jz4740/jz4740-pcm.c | 352 +++++++++++++++++++++++++++ sound/soc/jz4740/jz4740-pcm.h | 22 ++ 8 files changed, 957 insertions(+), 0 deletions(-) create mode 100644 sound/soc/jz4740/Kconfig create mode 100644 sound/soc/jz4740/Makefile create mode 100644 sound/soc/jz4740/jz4740-i2s.c create mode 100644 sound/soc/jz4740/jz4740-i2s.h create mode 100644 sound/soc/jz4740/jz4740-pcm.c create mode 100644 sound/soc/jz4740/jz4740-pcm.h
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index d35f848..7137a9a 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -39,6 +39,7 @@ source "sound/soc/s3c24xx/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/txx9/Kconfig" +source "sound/soc/jz4740/Kconfig"
# Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 97661b7..d131999 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += txx9/ +obj-$(CONFIG_SND_SOC) += jz4740/ diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig new file mode 100644 index 0000000..27480f2 --- /dev/null +++ b/sound/soc/jz4740/Kconfig @@ -0,0 +1,14 @@ +config SND_JZ4740_SOC + tristate "SoC Audio for Ingenic JZ4740 SoC" + depends on MACH_JZ4740 && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the JZ4740 I2S interface. You will also need to select the audio + interfaces to support below. + +config SND_JZ4740_SOC_I2S + depends on SND_JZ4740_SOC + tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC" + help + Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740 + based boards. diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile new file mode 100644 index 0000000..1be8d19 --- /dev/null +++ b/sound/soc/jz4740/Makefile @@ -0,0 +1,9 @@ +# +# Jz4740 Platform Support +# +snd-soc-jz4740-objs := jz4740-pcm.o +snd-soc-jz4740-i2s-objs := jz4740-i2s.o + +obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o +obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o + diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c new file mode 100644 index 0000000..eb518f0 --- /dev/null +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -0,0 +1,540 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/clk.h> +#include <linux/delay.h> + +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "jz4740-i2s.h" +#include "jz4740-pcm.h" + +#define JZ_REG_AIC_CONF 0x00 +#define JZ_REG_AIC_CTRL 0x04 +#define JZ_REG_AIC_I2S_FMT 0x10 +#define JZ_REG_AIC_FIFO_STATUS 0x14 +#define JZ_REG_AIC_I2S_STATUS 0x1c +#define JZ_REG_AIC_CLK_DIV 0x30 +#define JZ_REG_AIC_FIFO 0x34 + +#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_MASK (0xf << 12) +#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_MASK (0xf << 8) +#define JZ_AIC_CONF_OVERFLOW_PLAY_LAST BIT(6) +#define JZ_AIC_CONF_INTERNAL_CODEC BIT(5) +#define JZ_AIC_CONF_I2S BIT(4) +#define JZ_AIC_CONF_RESET BIT(3) +#define JZ_AIC_CONF_BIT_CLK_MASTER BIT(2) +#define JZ_AIC_CONF_SYNC_CLK_MASTER BIT(1) +#define JZ_AIC_CONF_ENABLE BIT(0) + +#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12 +#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8 + +#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19) +#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16) +#define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15) +#define JZ_AIC_CTRL_ENABLE_TX_DMA BIT(14) +#define JZ_AIC_CTRL_MONO_TO_STEREO BIT(11) +#define JZ_AIC_CTRL_SWITCH_ENDIANNESS BIT(10) +#define JZ_AIC_CTRL_SIGNED_TO_UNSIGNED BIT(9) +#define JZ_AIC_CTRL_FLUSH BIT(8) +#define JZ_AIC_CTRL_ENABLE_ROR_INT BIT(6) +#define JZ_AIC_CTRL_ENABLE_TUR_INT BIT(5) +#define JZ_AIC_CTRL_ENABLE_RFS_INT BIT(4) +#define JZ_AIC_CTRL_ENABLE_TFS_INT BIT(3) +#define JZ_AIC_CTRL_ENABLE_LOOPBACK BIT(2) +#define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1) +#define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0) + +#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19 +#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16 + +#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12) +#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4) +#define JZ_AIC_I2S_FMT_MSB BIT(0) + +#define JZ_AIC_I2S_STATUS_BUSY BIT(2) + +#define JZ_AIC_CLK_DIV_MASK 0xf + +struct jz4740_i2s { + struct resource *mem; + void __iomem *base; + dma_addr_t phys_base; + + struct clk *clk_aic; + struct clk *clk_i2s; + + struct jz4740_pcm_config pcm_config_playback; + struct jz4740_pcm_config pcm_config_capture; +}; + +static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s, + unsigned int reg) +{ + return readl(i2s->base + reg); +} + +static inline void jz4740_i2s_write(const struct jz4740_i2s *i2s, + unsigned int reg, uint32_t value) +{ + writel(value, i2s->base + reg); +} + +static inline struct jz4740_i2s *jz4740_dai_to_i2s(struct snd_soc_dai *dai) +{ + return dai->private_data; +} + +static int jz4740_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf, ctrl; + + if (dai->active) + return 0; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + ctrl |= JZ_AIC_CTRL_FLUSH; + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + clk_enable(i2s->clk_i2s); + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf |= JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + return 0; +} + +static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + if (!dai->active) + return; + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf &= ~JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + clk_disable(i2s->clk_i2s); +} + +static int jz4740_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + + uint32_t ctrl; + uint32_t mask; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mask = JZ_AIC_CTRL_ENABLE_PLAYBACK | JZ_AIC_CTRL_ENABLE_TX_DMA; + else + mask = JZ_AIC_CTRL_ENABLE_CAPTURE | JZ_AIC_CTRL_ENABLE_RX_DMA; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ctrl |= mask; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ctrl &= ~mask; + break; + default: + return -EINVAL; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + return 0; +} + +static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + + uint32_t format = 0; + uint32_t conf; + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + + conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + conf |= JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER; + format |= JZ_AIC_I2S_FMT_ENABLE_SYS_CLK; + break; + case SND_SOC_DAIFMT_CBM_CFS: + conf |= JZ_AIC_CONF_SYNC_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: + conf |= JZ_AIC_CONF_BIT_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_MSB: + format |= JZ_AIC_I2S_FMT_MSB; + break; + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + jz4740_i2s_write(i2s, JZ_REG_AIC_I2S_FMT, format); + + return 0; +} + +static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + enum jz4740_dma_width dma_width; + struct jz4740_pcm_config *pcm_config; + unsigned int sample_size; + uint32_t ctrl; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + sample_size = 0; + dma_width = JZ4740_DMA_WIDTH_8BIT; + break; + case SNDRV_PCM_FORMAT_S16: + sample_size = 1; + dma_width = JZ4740_DMA_WIDTH_16BIT; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ctrl &= ~JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK; + ctrl |= sample_size << JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET; + if (params_channels(params) == 1) + ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO; + else + ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO; + + pcm_config = &i2s->pcm_config_playback; + pcm_config->dma_config.dst_width = dma_width; + + } else { + ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK; + ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET; + + pcm_config = &i2s->pcm_config_capture; + pcm_config->dma_config.src_width = dma_width; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + snd_soc_dai_set_dma_data(dai, substream, pcm_config); + + return 0; +} + +static int jz4740_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + struct clk *parent; + int ret = 0; + + switch (clk_id) { + case JZ4740_I2S_CLKSRC_EXT: + parent = clk_get(NULL, "ext"); + clk_set_parent(i2s->clk_i2s, parent); + break; + case JZ4740_I2S_CLKSRC_PLL: + parent = clk_get(NULL, "pll half"); + clk_set_parent(i2s->clk_i2s, parent); + ret = clk_set_rate(i2s->clk_i2s, freq); + break; + default: + return -EINVAL; + } + clk_put(parent); + + return ret; +} + +static int jz4740_i2s_suspend(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + if (dai->active) { + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf &= ~JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + clk_disable(i2s->clk_i2s); + } + + clk_disable(i2s->clk_aic); + + return 0; +} + +static int jz4740_i2s_resume(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + clk_enable(i2s->clk_aic); + + if (dai->active) { + clk_enable(i2s->clk_i2s); + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf |= JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + } + + return 0; +} + +static int jz4740_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) | + (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) | + JZ_AIC_CONF_OVERFLOW_PLAY_LAST | + JZ_AIC_CONF_I2S | + JZ_AIC_CONF_INTERNAL_CODEC; + + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET); + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + return 0; +} + +static struct snd_soc_dai_ops jz4740_i2s_dai_ops = { + .startup = jz4740_i2s_startup, + .shutdown = jz4740_i2s_shutdown, + .trigger = jz4740_i2s_trigger, + .hw_params = jz4740_i2s_hw_params, + .set_fmt = jz4740_i2s_set_fmt, + .set_sysclk = jz4740_i2s_set_sysclk, +}; + +#define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE) + +struct snd_soc_dai jz4740_i2s_dai = { + .name = "jz4740-i2s", + .probe = jz4740_i2s_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .symmetric_rates = 1, + .ops = &jz4740_i2s_dai_ops, + .suspend = jz4740_i2s_suspend, + .resume = jz4740_i2s_resume, +}; +EXPORT_SYMBOL_GPL(jz4740_i2s_dai); + +static void __devinit jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s) +{ + struct jz4740_dma_config *dma_config; + + /* Playback */ + dma_config = &i2s->pcm_config_playback.dma_config; + dma_config->src_width = JZ4740_DMA_WIDTH_32BIT, + dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE; + dma_config->request_type = JZ4740_DMA_TYPE_AIC_TRANSMIT; + dma_config->flags = JZ4740_DMA_SRC_AUTOINC; + dma_config->mode = JZ4740_DMA_MODE_SINGLE; + i2s->pcm_config_playback.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO; + + /* Capture */ + dma_config = &i2s->pcm_config_capture.dma_config; + dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT, + dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE; + dma_config->request_type = JZ4740_DMA_TYPE_AIC_RECEIVE; + dma_config->flags = JZ4740_DMA_DST_AUTOINC; + dma_config->mode = JZ4740_DMA_MODE_SINGLE; + i2s->pcm_config_capture.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO; +} + +static int __devinit jz4740_i2s_dev_probe(struct platform_device *pdev) +{ + struct jz4740_i2s *i2s; + int ret; + + i2s = kzalloc(sizeof(*i2s), GFP_KERNEL); + + if (!i2s) + return -ENOMEM; + + i2s->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!i2s->mem) { + ret = -ENOENT; + goto err_free; + } + + i2s->mem = request_mem_region(i2s->mem->start, resource_size(i2s->mem), + pdev->name); + if (!i2s->mem) { + ret = -EBUSY; + goto err_free; + } + + i2s->base = ioremap_nocache(i2s->mem->start, resource_size(i2s->mem)); + if (!i2s->base) { + ret = -EBUSY; + goto err_release_mem_region; + } + + i2s->phys_base = i2s->mem->start; + + i2s->clk_aic = clk_get(&pdev->dev, "aic"); + if (IS_ERR(i2s->clk_aic)) { + ret = PTR_ERR(i2s->clk_aic); + goto err_iounmap; + } + + i2s->clk_i2s = clk_get(&pdev->dev, "i2s"); + if (IS_ERR(i2s->clk_i2s)) { + ret = PTR_ERR(i2s->clk_i2s); + goto err_clk_put_aic; + } + + clk_enable(i2s->clk_aic); + + jz4740_i2c_init_pcm_config(i2s); + + jz4740_i2s_dai.private_data = i2s; + ret = snd_soc_register_dai(&jz4740_i2s_dai); + + if (ret) { + dev_err(&pdev->dev, "Failed to register DAI\n"); + goto err_clk_put_i2s; + } + + platform_set_drvdata(pdev, i2s); + + return 0; + +err_clk_put_i2s: + clk_disable(i2s->clk_aic); + clk_put(i2s->clk_i2s); +err_clk_put_aic: + clk_put(i2s->clk_aic); +err_iounmap: + iounmap(i2s->base); +err_release_mem_region: + release_mem_region(i2s->mem->start, resource_size(i2s->mem)); +err_free: + kfree(i2s); + + return ret; +} + +static int __devexit jz4740_i2s_dev_remove(struct platform_device *pdev) +{ + struct jz4740_i2s *i2s = platform_get_drvdata(pdev); + + snd_soc_unregister_dai(&jz4740_i2s_dai); + + clk_disable(i2s->clk_aic); + clk_put(i2s->clk_i2s); + clk_put(i2s->clk_aic); + + iounmap(i2s->base); + release_mem_region(i2s->mem->start, resource_size(i2s->mem)); + + platform_set_drvdata(pdev, NULL); + kfree(i2s); + + return 0; +} + +static struct platform_driver jz4740_i2s_driver = { + .probe = jz4740_i2s_dev_probe, + .remove = __devexit_p(jz4740_i2s_dev_remove), + .driver = { + .name = "jz4740-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_i2s_init(void) +{ + return platform_driver_register(&jz4740_i2s_driver); +} +module_init(jz4740_i2s_init); + +static void __exit jz4740_i2s_exit(void) +{ + platform_driver_unregister(&jz4740_i2s_driver); +} +module_exit(jz4740_i2s_exit); + +MODULE_AUTHOR("Lars-Peter Clausen, lars@metafoo.de"); +MODULE_DESCRIPTION("Ingenic JZ4740 SoC I2S driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:jz4740-i2s"); diff --git a/sound/soc/jz4740/jz4740-i2s.h b/sound/soc/jz4740/jz4740-i2s.h new file mode 100644 index 0000000..da22ed8 --- /dev/null +++ b/sound/soc/jz4740/jz4740-i2s.h @@ -0,0 +1,18 @@ +/* + * 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 _JZ4740_I2S_H +#define _JZ4740_I2S_H + +/* I2S clock source */ +#define JZ4740_I2S_CLKSRC_EXT 0 +#define JZ4740_I2S_CLKSRC_PLL 1 + +#define JZ4740_I2S_BIT_CLK 0 + +extern struct snd_soc_dai jz4740_i2s_dai; + +#endif diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c new file mode 100644 index 0000000..67b6cf2 --- /dev/null +++ b/sound/soc/jz4740/jz4740-pcm.c @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/mach-jz4740/dma.h> +#include "jz4740-pcm.h" + +struct jz4740_runtime_data { + unsigned long dma_period; + dma_addr_t dma_start; + dma_addr_t dma_pos; + dma_addr_t dma_end; + + struct jz4740_dma_chan *dma; + + dma_addr_t fifo_addr; +}; + +/* identify hardware playback capabilities */ +static const struct snd_pcm_hardware jz4740_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + + .rates = SNDRV_PCM_RATE_8000_48000, + .channels_min = 1, + .channels_max = 2, + .period_bytes_min = 16, + .period_bytes_max = 2 * PAGE_SIZE, + .periods_min = 2, + .periods_max = 128, + .buffer_bytes_max = 128 * 2 * PAGE_SIZE, + .fifo_size = 32, +}; + +static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd, + struct snd_pcm_substream *substream) +{ + unsigned long count; + + if (prtd->dma_pos == prtd->dma_end) + prtd->dma_pos = prtd->dma_start; + + if (prtd->dma_pos + prtd->dma_period > prtd->dma_end) + count = prtd->dma_end - prtd->dma_pos; + else + count = prtd->dma_period; + + jz4740_dma_disable(prtd->dma); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos); + jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr); + } else { + jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr); + jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos); + } + + jz4740_dma_set_transfer_count(prtd->dma, count); + + prtd->dma_pos += count; + + jz4740_dma_enable(prtd->dma); +} + +static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err, + void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + + snd_pcm_period_elapsed(substream); + + jz4740_pcm_start_transfer(prtd, substream); +} + +static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct jz4740_pcm_config *config; + + config = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); + + if (!config) + return 0; + + if (!prtd->dma) { + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->dma = jz4740_dma_request(substream, "PCM Capture"); + else + prtd->dma = jz4740_dma_request(substream, "PCM Playback"); + } + + if (!prtd->dma) + return -EBUSY; + + jz4740_dma_configure(prtd->dma, &config->dma_config); + prtd->fifo_addr = config->fifo_addr; + + jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + prtd->dma_period = params_period_bytes(params); + prtd->dma_start = runtime->dma_addr; + prtd->dma_pos = prtd->dma_start; + prtd->dma_end = prtd->dma_start + runtime->dma_bytes; + + return 0; +} + +static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct jz4740_runtime_data *prtd = substream->runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, NULL); + if (prtd->dma) { + jz4740_dma_free(prtd->dma); + prtd->dma = NULL; + } + + return 0; +} + +static int jz4740_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct jz4740_runtime_data *prtd = substream->runtime->private_data; + + if (!prtd->dma) + return -EBUSY; + + prtd->dma_pos = prtd->dma_start; + + return 0; +} + +static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + jz4740_pcm_start_transfer(prtd, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + jz4740_dma_disable(prtd->dma); + break; + default: + break; + } + + return 0; +} + +static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + unsigned long byte_offset; + snd_pcm_uframes_t offset; + struct jz4740_dma_chan *dma = prtd->dma; + + /* prtd->dma_pos points to the end of the current transfer. So by + * subtracting prdt->dma_start we get the offset to the end of the + * current period in bytes. By subtracting the residue of the transfer + * we get the current offset in bytes. */ + byte_offset = prtd->dma_pos - prtd->dma_start; + byte_offset -= jz4740_dma_get_residue(dma); + + offset = bytes_to_frames(runtime, byte_offset); + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +static int jz4740_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware); + + runtime->private_data = prtd; + + return 0; +} + +static int jz4740_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + + kfree(prtd); + + return 0; +} + +static int jz4740_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static struct snd_pcm_ops jz4740_pcm_ops = { + .open = jz4740_pcm_open, + .close = jz4740_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = jz4740_pcm_hw_params, + .hw_free = jz4740_pcm_hw_free, + .prepare = jz4740_pcm_prepare, + .trigger = jz4740_pcm_trigger, + .pointer = jz4740_pcm_pointer, + .mmap = jz4740_pcm_mmap, +}; + +static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = jz4740_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + buf->area = dma_alloc_noncoherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + + return 0; +} + +static void jz4740_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area, + buf->addr); + buf->area = NULL; + } +} + +static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32); + +int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &jz4740_pcm_dmamask; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (dai->playback.channels_min) { + ret = jz4740_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto err; + } + + if (dai->capture.channels_min) { + ret = jz4740_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto err; + } + +err: + return ret; +} + +struct snd_soc_platform jz4740_soc_platform = { + .name = "jz4740-pcm", + .pcm_ops = &jz4740_pcm_ops, + .pcm_new = jz4740_pcm_new, + .pcm_free = jz4740_pcm_free, +}; +EXPORT_SYMBOL_GPL(jz4740_soc_platform); + +static int __init jz4740_soc_platform_init(void) +{ + return snd_soc_register_platform(&jz4740_soc_platform); +} +module_init(jz4740_soc_platform_init); + +static void __exit jz4740_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&jz4740_soc_platform); +} +module_exit(jz4740_soc_platform_exit); + +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/jz4740/jz4740-pcm.h b/sound/soc/jz4740/jz4740-pcm.h new file mode 100644 index 0000000..e3f221e --- /dev/null +++ b/sound/soc/jz4740/jz4740-pcm.h @@ -0,0 +1,22 @@ +/* + * + * 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 _JZ4740_PCM_H +#define _JZ4740_PCM_H + +#include <linux/dma-mapping.h> +#include <asm/mach-jz4740/dma.h> + +/* platform data */ +extern struct snd_soc_platform jz4740_soc_platform; + +struct jz4740_pcm_config { + struct jz4740_dma_config dma_config; + phys_addr_t fifo_addr; +}; + +#endif
This patch adds ASoC support for JZ4740 SoCs I2S module.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@opensource.wolfsonmicro.com Cc: Liam Girdwood lrg@slimlogic.co.uk Cc: alsa-devel@alsa-project.org
alsa: ASoC: jz4740-i2s: Properly free and disable clocks in case of an error
-- Changes since v1 - i2s: Properly free and disable clocks in case of an error in probe - i2s: Drop unnecessary format checks which are already done in the core - i2s: Drop set_clkdiv - pcm: Refactor dma buffer position handling to be better comprehensible - Cleanup and fix Kconfig
Changes since v2 - Use a platform device to register the snd_soc_platform --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/jz4740/Kconfig | 14 + sound/soc/jz4740/Makefile | 9 + sound/soc/jz4740/jz4740-i2s.c | 540 +++++++++++++++++++++++++++++++++++++++++ sound/soc/jz4740/jz4740-i2s.h | 18 ++ sound/soc/jz4740/jz4740-pcm.c | 373 ++++++++++++++++++++++++++++ sound/soc/jz4740/jz4740-pcm.h | 22 ++ 8 files changed, 978 insertions(+), 0 deletions(-) create mode 100644 sound/soc/jz4740/Kconfig create mode 100644 sound/soc/jz4740/Makefile create mode 100644 sound/soc/jz4740/jz4740-i2s.c create mode 100644 sound/soc/jz4740/jz4740-i2s.h create mode 100644 sound/soc/jz4740/jz4740-pcm.c create mode 100644 sound/soc/jz4740/jz4740-pcm.h
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index d35f848..7137a9a 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -39,6 +39,7 @@ source "sound/soc/s3c24xx/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/txx9/Kconfig" +source "sound/soc/jz4740/Kconfig"
# Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 97661b7..d131999 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += txx9/ +obj-$(CONFIG_SND_SOC) += jz4740/ diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig new file mode 100644 index 0000000..27480f2 --- /dev/null +++ b/sound/soc/jz4740/Kconfig @@ -0,0 +1,14 @@ +config SND_JZ4740_SOC + tristate "SoC Audio for Ingenic JZ4740 SoC" + depends on MACH_JZ4740 && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the JZ4740 I2S interface. You will also need to select the audio + interfaces to support below. + +config SND_JZ4740_SOC_I2S + depends on SND_JZ4740_SOC + tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC" + help + Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740 + based boards. diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile new file mode 100644 index 0000000..1be8d19 --- /dev/null +++ b/sound/soc/jz4740/Makefile @@ -0,0 +1,9 @@ +# +# Jz4740 Platform Support +# +snd-soc-jz4740-objs := jz4740-pcm.o +snd-soc-jz4740-i2s-objs := jz4740-i2s.o + +obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o +obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o + diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c new file mode 100644 index 0000000..eb518f0 --- /dev/null +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -0,0 +1,540 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/clk.h> +#include <linux/delay.h> + +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "jz4740-i2s.h" +#include "jz4740-pcm.h" + +#define JZ_REG_AIC_CONF 0x00 +#define JZ_REG_AIC_CTRL 0x04 +#define JZ_REG_AIC_I2S_FMT 0x10 +#define JZ_REG_AIC_FIFO_STATUS 0x14 +#define JZ_REG_AIC_I2S_STATUS 0x1c +#define JZ_REG_AIC_CLK_DIV 0x30 +#define JZ_REG_AIC_FIFO 0x34 + +#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_MASK (0xf << 12) +#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_MASK (0xf << 8) +#define JZ_AIC_CONF_OVERFLOW_PLAY_LAST BIT(6) +#define JZ_AIC_CONF_INTERNAL_CODEC BIT(5) +#define JZ_AIC_CONF_I2S BIT(4) +#define JZ_AIC_CONF_RESET BIT(3) +#define JZ_AIC_CONF_BIT_CLK_MASTER BIT(2) +#define JZ_AIC_CONF_SYNC_CLK_MASTER BIT(1) +#define JZ_AIC_CONF_ENABLE BIT(0) + +#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12 +#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8 + +#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19) +#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16) +#define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15) +#define JZ_AIC_CTRL_ENABLE_TX_DMA BIT(14) +#define JZ_AIC_CTRL_MONO_TO_STEREO BIT(11) +#define JZ_AIC_CTRL_SWITCH_ENDIANNESS BIT(10) +#define JZ_AIC_CTRL_SIGNED_TO_UNSIGNED BIT(9) +#define JZ_AIC_CTRL_FLUSH BIT(8) +#define JZ_AIC_CTRL_ENABLE_ROR_INT BIT(6) +#define JZ_AIC_CTRL_ENABLE_TUR_INT BIT(5) +#define JZ_AIC_CTRL_ENABLE_RFS_INT BIT(4) +#define JZ_AIC_CTRL_ENABLE_TFS_INT BIT(3) +#define JZ_AIC_CTRL_ENABLE_LOOPBACK BIT(2) +#define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1) +#define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0) + +#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19 +#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16 + +#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12) +#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4) +#define JZ_AIC_I2S_FMT_MSB BIT(0) + +#define JZ_AIC_I2S_STATUS_BUSY BIT(2) + +#define JZ_AIC_CLK_DIV_MASK 0xf + +struct jz4740_i2s { + struct resource *mem; + void __iomem *base; + dma_addr_t phys_base; + + struct clk *clk_aic; + struct clk *clk_i2s; + + struct jz4740_pcm_config pcm_config_playback; + struct jz4740_pcm_config pcm_config_capture; +}; + +static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s, + unsigned int reg) +{ + return readl(i2s->base + reg); +} + +static inline void jz4740_i2s_write(const struct jz4740_i2s *i2s, + unsigned int reg, uint32_t value) +{ + writel(value, i2s->base + reg); +} + +static inline struct jz4740_i2s *jz4740_dai_to_i2s(struct snd_soc_dai *dai) +{ + return dai->private_data; +} + +static int jz4740_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf, ctrl; + + if (dai->active) + return 0; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + ctrl |= JZ_AIC_CTRL_FLUSH; + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + clk_enable(i2s->clk_i2s); + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf |= JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + return 0; +} + +static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + if (!dai->active) + return; + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf &= ~JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + clk_disable(i2s->clk_i2s); +} + +static int jz4740_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + + uint32_t ctrl; + uint32_t mask; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mask = JZ_AIC_CTRL_ENABLE_PLAYBACK | JZ_AIC_CTRL_ENABLE_TX_DMA; + else + mask = JZ_AIC_CTRL_ENABLE_CAPTURE | JZ_AIC_CTRL_ENABLE_RX_DMA; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ctrl |= mask; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ctrl &= ~mask; + break; + default: + return -EINVAL; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + return 0; +} + +static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + + uint32_t format = 0; + uint32_t conf; + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + + conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + conf |= JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER; + format |= JZ_AIC_I2S_FMT_ENABLE_SYS_CLK; + break; + case SND_SOC_DAIFMT_CBM_CFS: + conf |= JZ_AIC_CONF_SYNC_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: + conf |= JZ_AIC_CONF_BIT_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_MSB: + format |= JZ_AIC_I2S_FMT_MSB; + break; + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + jz4740_i2s_write(i2s, JZ_REG_AIC_I2S_FMT, format); + + return 0; +} + +static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + enum jz4740_dma_width dma_width; + struct jz4740_pcm_config *pcm_config; + unsigned int sample_size; + uint32_t ctrl; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + sample_size = 0; + dma_width = JZ4740_DMA_WIDTH_8BIT; + break; + case SNDRV_PCM_FORMAT_S16: + sample_size = 1; + dma_width = JZ4740_DMA_WIDTH_16BIT; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ctrl &= ~JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK; + ctrl |= sample_size << JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET; + if (params_channels(params) == 1) + ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO; + else + ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO; + + pcm_config = &i2s->pcm_config_playback; + pcm_config->dma_config.dst_width = dma_width; + + } else { + ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK; + ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET; + + pcm_config = &i2s->pcm_config_capture; + pcm_config->dma_config.src_width = dma_width; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + snd_soc_dai_set_dma_data(dai, substream, pcm_config); + + return 0; +} + +static int jz4740_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + struct clk *parent; + int ret = 0; + + switch (clk_id) { + case JZ4740_I2S_CLKSRC_EXT: + parent = clk_get(NULL, "ext"); + clk_set_parent(i2s->clk_i2s, parent); + break; + case JZ4740_I2S_CLKSRC_PLL: + parent = clk_get(NULL, "pll half"); + clk_set_parent(i2s->clk_i2s, parent); + ret = clk_set_rate(i2s->clk_i2s, freq); + break; + default: + return -EINVAL; + } + clk_put(parent); + + return ret; +} + +static int jz4740_i2s_suspend(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + if (dai->active) { + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf &= ~JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + clk_disable(i2s->clk_i2s); + } + + clk_disable(i2s->clk_aic); + + return 0; +} + +static int jz4740_i2s_resume(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + clk_enable(i2s->clk_aic); + + if (dai->active) { + clk_enable(i2s->clk_i2s); + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf |= JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + } + + return 0; +} + +static int jz4740_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) | + (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) | + JZ_AIC_CONF_OVERFLOW_PLAY_LAST | + JZ_AIC_CONF_I2S | + JZ_AIC_CONF_INTERNAL_CODEC; + + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET); + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + return 0; +} + +static struct snd_soc_dai_ops jz4740_i2s_dai_ops = { + .startup = jz4740_i2s_startup, + .shutdown = jz4740_i2s_shutdown, + .trigger = jz4740_i2s_trigger, + .hw_params = jz4740_i2s_hw_params, + .set_fmt = jz4740_i2s_set_fmt, + .set_sysclk = jz4740_i2s_set_sysclk, +}; + +#define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE) + +struct snd_soc_dai jz4740_i2s_dai = { + .name = "jz4740-i2s", + .probe = jz4740_i2s_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .symmetric_rates = 1, + .ops = &jz4740_i2s_dai_ops, + .suspend = jz4740_i2s_suspend, + .resume = jz4740_i2s_resume, +}; +EXPORT_SYMBOL_GPL(jz4740_i2s_dai); + +static void __devinit jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s) +{ + struct jz4740_dma_config *dma_config; + + /* Playback */ + dma_config = &i2s->pcm_config_playback.dma_config; + dma_config->src_width = JZ4740_DMA_WIDTH_32BIT, + dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE; + dma_config->request_type = JZ4740_DMA_TYPE_AIC_TRANSMIT; + dma_config->flags = JZ4740_DMA_SRC_AUTOINC; + dma_config->mode = JZ4740_DMA_MODE_SINGLE; + i2s->pcm_config_playback.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO; + + /* Capture */ + dma_config = &i2s->pcm_config_capture.dma_config; + dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT, + dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE; + dma_config->request_type = JZ4740_DMA_TYPE_AIC_RECEIVE; + dma_config->flags = JZ4740_DMA_DST_AUTOINC; + dma_config->mode = JZ4740_DMA_MODE_SINGLE; + i2s->pcm_config_capture.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO; +} + +static int __devinit jz4740_i2s_dev_probe(struct platform_device *pdev) +{ + struct jz4740_i2s *i2s; + int ret; + + i2s = kzalloc(sizeof(*i2s), GFP_KERNEL); + + if (!i2s) + return -ENOMEM; + + i2s->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!i2s->mem) { + ret = -ENOENT; + goto err_free; + } + + i2s->mem = request_mem_region(i2s->mem->start, resource_size(i2s->mem), + pdev->name); + if (!i2s->mem) { + ret = -EBUSY; + goto err_free; + } + + i2s->base = ioremap_nocache(i2s->mem->start, resource_size(i2s->mem)); + if (!i2s->base) { + ret = -EBUSY; + goto err_release_mem_region; + } + + i2s->phys_base = i2s->mem->start; + + i2s->clk_aic = clk_get(&pdev->dev, "aic"); + if (IS_ERR(i2s->clk_aic)) { + ret = PTR_ERR(i2s->clk_aic); + goto err_iounmap; + } + + i2s->clk_i2s = clk_get(&pdev->dev, "i2s"); + if (IS_ERR(i2s->clk_i2s)) { + ret = PTR_ERR(i2s->clk_i2s); + goto err_clk_put_aic; + } + + clk_enable(i2s->clk_aic); + + jz4740_i2c_init_pcm_config(i2s); + + jz4740_i2s_dai.private_data = i2s; + ret = snd_soc_register_dai(&jz4740_i2s_dai); + + if (ret) { + dev_err(&pdev->dev, "Failed to register DAI\n"); + goto err_clk_put_i2s; + } + + platform_set_drvdata(pdev, i2s); + + return 0; + +err_clk_put_i2s: + clk_disable(i2s->clk_aic); + clk_put(i2s->clk_i2s); +err_clk_put_aic: + clk_put(i2s->clk_aic); +err_iounmap: + iounmap(i2s->base); +err_release_mem_region: + release_mem_region(i2s->mem->start, resource_size(i2s->mem)); +err_free: + kfree(i2s); + + return ret; +} + +static int __devexit jz4740_i2s_dev_remove(struct platform_device *pdev) +{ + struct jz4740_i2s *i2s = platform_get_drvdata(pdev); + + snd_soc_unregister_dai(&jz4740_i2s_dai); + + clk_disable(i2s->clk_aic); + clk_put(i2s->clk_i2s); + clk_put(i2s->clk_aic); + + iounmap(i2s->base); + release_mem_region(i2s->mem->start, resource_size(i2s->mem)); + + platform_set_drvdata(pdev, NULL); + kfree(i2s); + + return 0; +} + +static struct platform_driver jz4740_i2s_driver = { + .probe = jz4740_i2s_dev_probe, + .remove = __devexit_p(jz4740_i2s_dev_remove), + .driver = { + .name = "jz4740-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_i2s_init(void) +{ + return platform_driver_register(&jz4740_i2s_driver); +} +module_init(jz4740_i2s_init); + +static void __exit jz4740_i2s_exit(void) +{ + platform_driver_unregister(&jz4740_i2s_driver); +} +module_exit(jz4740_i2s_exit); + +MODULE_AUTHOR("Lars-Peter Clausen, lars@metafoo.de"); +MODULE_DESCRIPTION("Ingenic JZ4740 SoC I2S driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:jz4740-i2s"); diff --git a/sound/soc/jz4740/jz4740-i2s.h b/sound/soc/jz4740/jz4740-i2s.h new file mode 100644 index 0000000..da22ed8 --- /dev/null +++ b/sound/soc/jz4740/jz4740-i2s.h @@ -0,0 +1,18 @@ +/* + * 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 _JZ4740_I2S_H +#define _JZ4740_I2S_H + +/* I2S clock source */ +#define JZ4740_I2S_CLKSRC_EXT 0 +#define JZ4740_I2S_CLKSRC_PLL 1 + +#define JZ4740_I2S_BIT_CLK 0 + +extern struct snd_soc_dai jz4740_i2s_dai; + +#endif diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c new file mode 100644 index 0000000..ee68d85 --- /dev/null +++ b/sound/soc/jz4740/jz4740-pcm.c @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/mach-jz4740/dma.h> +#include "jz4740-pcm.h" + +struct jz4740_runtime_data { + unsigned long dma_period; + dma_addr_t dma_start; + dma_addr_t dma_pos; + dma_addr_t dma_end; + + struct jz4740_dma_chan *dma; + + dma_addr_t fifo_addr; +}; + +/* identify hardware playback capabilities */ +static const struct snd_pcm_hardware jz4740_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + + .rates = SNDRV_PCM_RATE_8000_48000, + .channels_min = 1, + .channels_max = 2, + .period_bytes_min = 16, + .period_bytes_max = 2 * PAGE_SIZE, + .periods_min = 2, + .periods_max = 128, + .buffer_bytes_max = 128 * 2 * PAGE_SIZE, + .fifo_size = 32, +}; + +static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd, + struct snd_pcm_substream *substream) +{ + unsigned long count; + + if (prtd->dma_pos == prtd->dma_end) + prtd->dma_pos = prtd->dma_start; + + if (prtd->dma_pos + prtd->dma_period > prtd->dma_end) + count = prtd->dma_end - prtd->dma_pos; + else + count = prtd->dma_period; + + jz4740_dma_disable(prtd->dma); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos); + jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr); + } else { + jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr); + jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos); + } + + jz4740_dma_set_transfer_count(prtd->dma, count); + + prtd->dma_pos += count; + + jz4740_dma_enable(prtd->dma); +} + +static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err, + void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + + snd_pcm_period_elapsed(substream); + + jz4740_pcm_start_transfer(prtd, substream); +} + +static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct jz4740_pcm_config *config; + + config = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); + + if (!config) + return 0; + + if (!prtd->dma) { + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->dma = jz4740_dma_request(substream, "PCM Capture"); + else + prtd->dma = jz4740_dma_request(substream, "PCM Playback"); + } + + if (!prtd->dma) + return -EBUSY; + + jz4740_dma_configure(prtd->dma, &config->dma_config); + prtd->fifo_addr = config->fifo_addr; + + jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + prtd->dma_period = params_period_bytes(params); + prtd->dma_start = runtime->dma_addr; + prtd->dma_pos = prtd->dma_start; + prtd->dma_end = prtd->dma_start + runtime->dma_bytes; + + return 0; +} + +static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct jz4740_runtime_data *prtd = substream->runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, NULL); + if (prtd->dma) { + jz4740_dma_free(prtd->dma); + prtd->dma = NULL; + } + + return 0; +} + +static int jz4740_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct jz4740_runtime_data *prtd = substream->runtime->private_data; + + if (!prtd->dma) + return -EBUSY; + + prtd->dma_pos = prtd->dma_start; + + return 0; +} + +static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + jz4740_pcm_start_transfer(prtd, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + jz4740_dma_disable(prtd->dma); + break; + default: + break; + } + + return 0; +} + +static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + unsigned long byte_offset; + snd_pcm_uframes_t offset; + struct jz4740_dma_chan *dma = prtd->dma; + + /* prtd->dma_pos points to the end of the current transfer. So by + * subtracting prdt->dma_start we get the offset to the end of the + * current period in bytes. By subtracting the residue of the transfer + * we get the current offset in bytes. */ + byte_offset = prtd->dma_pos - prtd->dma_start; + byte_offset -= jz4740_dma_get_residue(dma); + + offset = bytes_to_frames(runtime, byte_offset); + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +static int jz4740_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware); + + runtime->private_data = prtd; + + return 0; +} + +static int jz4740_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + + kfree(prtd); + + return 0; +} + +static int jz4740_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static struct snd_pcm_ops jz4740_pcm_ops = { + .open = jz4740_pcm_open, + .close = jz4740_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = jz4740_pcm_hw_params, + .hw_free = jz4740_pcm_hw_free, + .prepare = jz4740_pcm_prepare, + .trigger = jz4740_pcm_trigger, + .pointer = jz4740_pcm_pointer, + .mmap = jz4740_pcm_mmap, +}; + +static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = jz4740_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + buf->area = dma_alloc_noncoherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + + return 0; +} + +static void jz4740_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area, + buf->addr); + buf->area = NULL; + } +} + +static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32); + +int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &jz4740_pcm_dmamask; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (dai->playback.channels_min) { + ret = jz4740_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto err; + } + + if (dai->capture.channels_min) { + ret = jz4740_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto err; + } + +err: + return ret; +} + +struct snd_soc_platform jz4740_soc_platform = { + .name = "jz4740-pcm", + .pcm_ops = &jz4740_pcm_ops, + .pcm_new = jz4740_pcm_new, + .pcm_free = jz4740_pcm_free, +}; +EXPORT_SYMBOL_GPL(jz4740_soc_platform); + +static int __devinit jz4740_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&jz4740_soc_platform); +} + +static int __devexit jz4740_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&jz4740_soc_platform); + return 0; +} + +static struct platform_driver jz4740_pcm_driver = { + .probe = jz4740_pcm_probe, + .remove = __devexit_p(jz4740_pcm_remove), + .driver = { + .name = "jz4740-pcm", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_soc_platform_init(void) +{ + return platform_driver_register(&jz4740_pcm_driver); +} +module_init(jz4740_soc_platform_init); + +static void __exit jz4740_soc_platform_exit(void) +{ + return platform_driver_unregister(&jz4740_pcm_driver); +} +module_exit(jz4740_soc_platform_exit); + +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/jz4740/jz4740-pcm.h b/sound/soc/jz4740/jz4740-pcm.h new file mode 100644 index 0000000..e3f221e --- /dev/null +++ b/sound/soc/jz4740/jz4740-pcm.h @@ -0,0 +1,22 @@ +/* + * + * 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 _JZ4740_PCM_H +#define _JZ4740_PCM_H + +#include <linux/dma-mapping.h> +#include <asm/mach-jz4740/dma.h> + +/* platform data */ +extern struct snd_soc_platform jz4740_soc_platform; + +struct jz4740_pcm_config { + struct jz4740_dma_config dma_config; + phys_addr_t fifo_addr; +}; + +#endif
This patch adds ASoC support for the qi_lb60 board.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@opensource.wolfsonmicro.com Cc: Liam Girdwood lrg@slimlogic.co.uk Cc: alsa-devel@alsa-project.org
--- Changes since v1 - Refer to AMP gpios always by their define - Do not try to set codecs format, since the set_fmt callback for the codec was dropped. --- sound/soc/jz4740/Kconfig | 9 ++ sound/soc/jz4740/Makefile | 4 + sound/soc/jz4740/jz4740-pcm.c | 25 ++++++- sound/soc/jz4740/qi_lb60.c | 167 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 sound/soc/jz4740/qi_lb60.c
diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig index 27480f2..5351cba 100644 --- a/sound/soc/jz4740/Kconfig +++ b/sound/soc/jz4740/Kconfig @@ -12,3 +12,12 @@ config SND_JZ4740_SOC_I2S help Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740 based boards. + +config SND_JZ4740_SOC_QI_LB60 + tristate "SoC Audio support for Qi LB60" + depends on SND_JZ4740_SOC && JZ4740_QI_LB60 + select SND_JZ4740_SOC_I2S + select SND_SOC_JZ4740_CODEC + help + Say Y if you want to add support for ASoC audio on the Qi LB60 board + a.k.a Qi Ben NanoNote. diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile index 1be8d19..be873c1 100644 --- a/sound/soc/jz4740/Makefile +++ b/sound/soc/jz4740/Makefile @@ -7,3 +7,7 @@ snd-soc-jz4740-i2s-objs := jz4740-i2s.o obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o
+# Jz4740 Machine Support +snd-soc-qi-lb60-objs := qi_lb60.o + +obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c index 67b6cf2..ee68d85 100644 --- a/sound/soc/jz4740/jz4740-pcm.c +++ b/sound/soc/jz4740/jz4740-pcm.c @@ -16,6 +16,7 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/platform_device.h> #include <linux/slab.h>
#include <linux/dma-mapping.h> @@ -335,15 +336,35 @@ struct snd_soc_platform jz4740_soc_platform = { }; EXPORT_SYMBOL_GPL(jz4740_soc_platform);
-static int __init jz4740_soc_platform_init(void) +static int __devinit jz4740_pcm_probe(struct platform_device *pdev) { return snd_soc_register_platform(&jz4740_soc_platform); } + +static int __devexit jz4740_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&jz4740_soc_platform); + return 0; +} + +static struct platform_driver jz4740_pcm_driver = { + .probe = jz4740_pcm_probe, + .remove = __devexit_p(jz4740_pcm_remove), + .driver = { + .name = "jz4740-pcm", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_soc_platform_init(void) +{ + return platform_driver_register(&jz4740_pcm_driver); +} module_init(jz4740_soc_platform_init);
static void __exit jz4740_soc_platform_exit(void) { - snd_soc_unregister_platform(&jz4740_soc_platform); + return platform_driver_unregister(&jz4740_pcm_driver); } module_exit(jz4740_soc_platform_exit);
diff --git a/sound/soc/jz4740/qi_lb60.c b/sound/soc/jz4740/qi_lb60.c new file mode 100644 index 0000000..829bc45 --- /dev/null +++ b/sound/soc/jz4740/qi_lb60.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2009, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/gpio.h> + +#include "../codecs/jz4740-codec.h" +#include "jz4740-pcm.h" +#include "jz4740-i2s.h" + + +#define QI_LB60_SND_GPIO JZ_GPIO_PORTB(29) +#define QI_LB60_AMP_GPIO JZ_GPIO_PORTD(4) + +static int qi_lb60_spk_event(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *ctrl, int event) +{ + int on = 0; + if (event & SND_SOC_DAPM_POST_PMU) + on = 1; + else if (event & SND_SOC_DAPM_PRE_PMD) + on = 0; + + gpio_set_value(QI_LB60_SND_GPIO, on); + gpio_set_value(QI_LB60_AMP_GPIO, on); + + return 0; +} + +static const struct snd_soc_dapm_widget qi_lb60_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", qi_lb60_spk_event), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route qi_lb60_routes[] = { + {"Mic", NULL, "MIC"}, + {"Speaker", NULL, "LOUT"}, + {"Speaker", NULL, "ROUT"}, +}; + +#define QI_LB60_DAIFMT (SND_SOC_DAIFMT_I2S | \ + SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBM_CFM) + +static int qi_lb60_codec_init(struct snd_soc_codec *codec) +{ + int ret; + struct snd_soc_dai *cpu_dai = codec->socdev->card->dai_link->cpu_dai; + struct snd_soc_dai *codec_dai = codec->socdev->card->dai_link->codec_dai; + + snd_soc_dapm_nc_pin(codec, "LIN"); + snd_soc_dapm_nc_pin(codec, "RIN"); + + ret = snd_soc_dai_set_fmt(cpu_dai, QI_LB60_DAIFMT); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cpu dai format: %d\n", ret); + return ret; + } + + snd_soc_dapm_new_controls(codec, qi_lb60_widgets, ARRAY_SIZE(qi_lb60_widgets)); + snd_soc_dapm_add_routes(codec, qi_lb60_routes, ARRAY_SIZE(qi_lb60_routes)); + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link qi_lb60_dai = { + .name = "jz4740", + .stream_name = "jz4740", + .cpu_dai = &jz4740_i2s_dai, + .codec_dai = &jz4740_codec_dai, + .init = qi_lb60_codec_init, +}; + +static struct snd_soc_card qi_lb60 = { + .name = "QI LB60", + .dai_link = &qi_lb60_dai, + .num_links = 1, + .platform = &jz4740_soc_platform, +}; + +static struct snd_soc_device qi_lb60_snd_devdata = { + .card = &qi_lb60, + .codec_dev = &soc_codec_dev_jz4740_codec, +}; + +static struct platform_device *qi_lb60_snd_device; + +static int __init qi_lb60_init(void) +{ + int ret; + + qi_lb60_snd_device = platform_device_alloc("soc-audio", -1); + + if (!qi_lb60_snd_device) + return -ENOMEM; + + ret = gpio_request(QI_LB60_SND_GPIO, "SND"); + if (ret) { + pr_err("qi_lb60 snd: Failed to request SND GPIO(%d): %d\n", + QI_LB60_SND_GPIO, ret); + goto err_device_put; + } + + ret = gpio_request(QI_LB60_AMP_GPIO, "AMP"); + if (ret) { + pr_err("qi_lb60 snd: Failed to request AMP GPIO(%d): %d\n", + QI_LB60_AMP_GPIO, ret); + goto err_gpio_free_snd; + } + + gpio_direction_output(QI_LB60_SND_GPIO, 0); + gpio_direction_output(QI_LB60_AMP_GPIO, 0); + + platform_set_drvdata(qi_lb60_snd_device, &qi_lb60_snd_devdata); + qi_lb60_snd_devdata.dev = &qi_lb60_snd_device->dev; + + ret = platform_device_add(qi_lb60_snd_device); + if (ret) { + pr_err("qi_lb60 snd: Failed to add snd soc device: %d\n", ret); + goto err_unset_pdata; + } + + return 0; + +err_unset_pdata: + platform_set_drvdata(qi_lb60_snd_device, NULL); +/*err_gpio_free_amp:*/ + gpio_free(QI_LB60_AMP_GPIO); +err_gpio_free_snd: + gpio_free(QI_LB60_SND_GPIO); +err_device_put: + platform_device_put(qi_lb60_snd_device); + + return ret; +} +module_init(qi_lb60_init); + +static void __exit qi_lb60_exit(void) +{ + gpio_free(QI_LB60_AMP_GPIO); + gpio_free(QI_LB60_SND_GPIO); + platform_device_unregister(qi_lb60_snd_device); +} +module_exit(qi_lb60_exit); + +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support"); +MODULE_LICENSE("GPL v2");
This patch adds ASoC support for the qi_lb60 board.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@opensource.wolfsonmicro.com Cc: Liam Girdwood lrg@slimlogic.co.uk Cc: alsa-devel@alsa-project.org
-- Changes since v1 - Refer to AMP gpios always by their define - Do not try to set codecs format, since the set_fmt callback for the codec was dropped.
Changes since v2 - The codec prefix was removed from jz4740 codec include file - This patch accidentally contained a few bits meant for ASoC JZ4740 platform support patch. Removed them. --- sound/soc/jz4740/Kconfig | 9 +++ sound/soc/jz4740/Makefile | 4 + sound/soc/jz4740/qi_lb60.c | 166 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 0 deletions(-) create mode 100644 sound/soc/jz4740/qi_lb60.c
diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig index 27480f2..5351cba 100644 --- a/sound/soc/jz4740/Kconfig +++ b/sound/soc/jz4740/Kconfig @@ -12,3 +12,12 @@ config SND_JZ4740_SOC_I2S help Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740 based boards. + +config SND_JZ4740_SOC_QI_LB60 + tristate "SoC Audio support for Qi LB60" + depends on SND_JZ4740_SOC && JZ4740_QI_LB60 + select SND_JZ4740_SOC_I2S + select SND_SOC_JZ4740_CODEC + help + Say Y if you want to add support for ASoC audio on the Qi LB60 board + a.k.a Qi Ben NanoNote. diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile index 1be8d19..be873c1 100644 --- a/sound/soc/jz4740/Makefile +++ b/sound/soc/jz4740/Makefile @@ -7,3 +7,7 @@ snd-soc-jz4740-i2s-objs := jz4740-i2s.o obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o
+# Jz4740 Machine Support +snd-soc-qi-lb60-objs := qi_lb60.o + +obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o diff --git a/sound/soc/jz4740/qi_lb60.c b/sound/soc/jz4740/qi_lb60.c new file mode 100644 index 0000000..f15f491 --- /dev/null +++ b/sound/soc/jz4740/qi_lb60.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2009, Lars-Peter Clausen lars@metafoo.de + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/gpio.h> + +#include "../codecs/jz4740.h" +#include "jz4740-pcm.h" +#include "jz4740-i2s.h" + + +#define QI_LB60_SND_GPIO JZ_GPIO_PORTB(29) +#define QI_LB60_AMP_GPIO JZ_GPIO_PORTD(4) + +static int qi_lb60_spk_event(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *ctrl, int event) +{ + int on = 0; + if (event & SND_SOC_DAPM_POST_PMU) + on = 1; + else if (event & SND_SOC_DAPM_PRE_PMD) + on = 0; + + gpio_set_value(QI_LB60_SND_GPIO, on); + gpio_set_value(QI_LB60_AMP_GPIO, on); + + return 0; +} + +static const struct snd_soc_dapm_widget qi_lb60_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", qi_lb60_spk_event), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route qi_lb60_routes[] = { + {"Mic", NULL, "MIC"}, + {"Speaker", NULL, "LOUT"}, + {"Speaker", NULL, "ROUT"}, +}; + +#define QI_LB60_DAIFMT (SND_SOC_DAIFMT_I2S | \ + SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBM_CFM) + +static int qi_lb60_codec_init(struct snd_soc_codec *codec) +{ + int ret; + struct snd_soc_dai *cpu_dai = codec->socdev->card->dai_link->cpu_dai; + + snd_soc_dapm_nc_pin(codec, "LIN"); + snd_soc_dapm_nc_pin(codec, "RIN"); + + ret = snd_soc_dai_set_fmt(cpu_dai, QI_LB60_DAIFMT); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cpu dai format: %d\n", ret); + return ret; + } + + snd_soc_dapm_new_controls(codec, qi_lb60_widgets, ARRAY_SIZE(qi_lb60_widgets)); + snd_soc_dapm_add_routes(codec, qi_lb60_routes, ARRAY_SIZE(qi_lb60_routes)); + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link qi_lb60_dai = { + .name = "jz4740", + .stream_name = "jz4740", + .cpu_dai = &jz4740_i2s_dai, + .codec_dai = &jz4740_codec_dai, + .init = qi_lb60_codec_init, +}; + +static struct snd_soc_card qi_lb60 = { + .name = "QI LB60", + .dai_link = &qi_lb60_dai, + .num_links = 1, + .platform = &jz4740_soc_platform, +}; + +static struct snd_soc_device qi_lb60_snd_devdata = { + .card = &qi_lb60, + .codec_dev = &soc_codec_dev_jz4740_codec, +}; + +static struct platform_device *qi_lb60_snd_device; + +static int __init qi_lb60_init(void) +{ + int ret; + + qi_lb60_snd_device = platform_device_alloc("soc-audio", -1); + + if (!qi_lb60_snd_device) + return -ENOMEM; + + ret = gpio_request(QI_LB60_SND_GPIO, "SND"); + if (ret) { + pr_err("qi_lb60 snd: Failed to request SND GPIO(%d): %d\n", + QI_LB60_SND_GPIO, ret); + goto err_device_put; + } + + ret = gpio_request(QI_LB60_AMP_GPIO, "AMP"); + if (ret) { + pr_err("qi_lb60 snd: Failed to request AMP GPIO(%d): %d\n", + QI_LB60_AMP_GPIO, ret); + goto err_gpio_free_snd; + } + + gpio_direction_output(QI_LB60_SND_GPIO, 0); + gpio_direction_output(QI_LB60_AMP_GPIO, 0); + + platform_set_drvdata(qi_lb60_snd_device, &qi_lb60_snd_devdata); + qi_lb60_snd_devdata.dev = &qi_lb60_snd_device->dev; + + ret = platform_device_add(qi_lb60_snd_device); + if (ret) { + pr_err("qi_lb60 snd: Failed to add snd soc device: %d\n", ret); + goto err_unset_pdata; + } + + return 0; + +err_unset_pdata: + platform_set_drvdata(qi_lb60_snd_device, NULL); +/*err_gpio_free_amp:*/ + gpio_free(QI_LB60_AMP_GPIO); +err_gpio_free_snd: + gpio_free(QI_LB60_SND_GPIO); +err_device_put: + platform_device_put(qi_lb60_snd_device); + + return ret; +} +module_init(qi_lb60_init); + +static void __exit qi_lb60_exit(void) +{ + gpio_free(QI_LB60_AMP_GPIO); + gpio_free(QI_LB60_SND_GPIO); + platform_device_unregister(qi_lb60_snd_device); +} +module_exit(qi_lb60_exit); + +MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); +MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support"); +MODULE_LICENSE("GPL v2");
On Sat, Jun 19, 2010 at 07:08:05AM +0200, Lars-Peter Clausen wrote:
This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.
great stuff. I have a JZ4730 based netbook, for which I started magling the provided sources quite some time ago, but I didn't reach the point of submitting patches... there are a lot of common stuff between JZ4730 and JZ4740 so IMHO it would be a good thing not to nail everthing to JZ4740 namewise. It might also a good idea to select something like arch/mips/jzrisc as base directory, put the factored out code there and add JZ4730/JZ4740 in either seperate files or directories.
Thomas.
On 06/20/2010 05:26 PM, Thomas Bogendoerfer wrote:
great stuff. I have a JZ4730 based netbook, for which I started magling the provided sources quite some time ago, but I didn't reach the point of submitting patches... there are a lot of common stuff between JZ4730 and JZ4740 so IMHO it would be a good thing not to nail everthing to JZ4740 namewise. It might also a good idea to select something like arch/mips/jzrisc as base directory, put the
Hi Thomas
I would advice "xburst" instead jzrisc. because the Ingenic call their cpu "XBurst" series. like: XBurst JZ4740, XBurst JZ4750 ...
factored out code there and add JZ4730/JZ4740 in either seperate files or directories.
participants (5)
-
Lars-Peter Clausen
-
Liam Girdwood
-
Mark Brown
-
Thomas Bogendoerfer
-
Xiangfu Liu