Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

397
sound/soc/samsung/Kconfig Normal file
View file

@ -0,0 +1,397 @@
config SND_SOC_SAMSUNG
tristate "ASoC support for Samsung"
depends on SOC_SAMSUNG
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M if you want to add support for codecs attached to
the Samsung SoCs' Audio interfaces. You will also need to
select the audio interfaces to support below.
config SND_S3C24XX_I2S
tristate
config SND_S3C_I2SV2_SOC
tristate
config SND_S3C2412_SOC_I2S
tristate
select SND_S3C_I2SV2_SOC
config SND_SAMSUNG_PCM
tristate
config SND_SAMSUNG_AC97
tristate
select SND_SOC_AC97_BUS
config SND_SAMSUNG_SPDIF
tristate
select SND_SOC_SPDIF
config SND_SAMSUNG_I2S
tristate
config SND_SAMSUNG_COMPR
tristate
config SND_SAMSUNG_AUDSS
tristate
config SND_SAMSUNG_XYREF
tristate
config SND_ESA_SA_EFFECT
tristate "Use Sound Alive Effect"
depends on SND_SAMSUNG_COMPR
default y
help
Say Y if you want to add support for Sound Alive.
config SND_SAMSUNG_IOMMU
tristate "IOMMU support for Samsung Audio sub system"
depends on SND_SOC_SAMSUNG && SND_SAMSUNG_AUDSS
help
Say Y if you want to add support for iommu.
config SND_SOC_SAMSUNG_NEO1973_WM8753
tristate "Audio support for Openmoko Neo1973 Smartphones (GTA02)"
depends on SND_SOC_SAMSUNG && MACH_NEO1973_GTA02
select SND_S3C24XX_I2S
select SND_SOC_WM8753
select SND_SOC_BT_SCO
help
Say Y here to enable audio support for the Openmoko Neo1973
Smartphones.
config SND_SOC_SAMSUNG_JIVE_WM8750
tristate "SoC I2S Audio support for Jive"
depends on SND_SOC_SAMSUNG && MACH_JIVE && I2C
select SND_SOC_WM8750
select SND_S3C2412_SOC_I2S
help
Say Y if you want to add support for SoC audio on the Jive.
config SND_SOC_SAMSUNG_SMDK_WM8580
tristate "SoC I2S Audio support for WM8580 on SMDK"
depends on SND_SOC_SAMSUNG && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110)
depends on REGMAP_I2C
select SND_SOC_WM8580
select SND_SAMSUNG_I2S
help
Say Y if you want to add support for SoC audio on the SMDKs.
config SND_SOC_SAMSUNG_SMDK_WM8994
tristate "SoC I2S Audio support for WM8994 on SMDK"
depends on SND_SOC_SAMSUNG
depends on I2C=y
select MFD_WM8994
select SND_SOC_WM8994
select SND_SAMSUNG_I2S
help
Say Y if you want to add support for SoC audio on the SMDKs.
config SND_SOC_SAMSUNG_SMDK2443_WM9710
tristate "SoC AC97 Audio support for SMDK2443 - WM9710"
depends on SND_SOC_SAMSUNG && MACH_SMDK2443
select AC97_BUS
select SND_SOC_AC97_CODEC
select SND_SAMSUNG_AC97
help
Say Y if you want to add support for SoC audio on smdk2443
with the WM9710.
config SND_SOC_SAMSUNG_LN2440SBC_ALC650
tristate "SoC AC97 Audio support for LN2440SBC - ALC650"
depends on SND_SOC_SAMSUNG && ARCH_S3C24XX
select AC97_BUS
select SND_SOC_AC97_CODEC
select SND_SAMSUNG_AC97
help
Say Y if you want to add support for SoC audio on ln2440sbc
with the ALC650.
config SND_SOC_SAMSUNG_S3C24XX_UDA134X
tristate "SoC I2S Audio support UDA134X wired to a S3C24XX"
depends on SND_SOC_SAMSUNG && ARCH_S3C24XX
select SND_S3C24XX_I2S
select SND_SOC_L3
select SND_SOC_UDA134X
config SND_SOC_SAMSUNG_SIMTEC
tristate
help
Internal node for common S3C24XX/Simtec suppor
config SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23
tristate "SoC I2S Audio support for TLV320AIC23 on Simtec boards"
depends on SND_SOC_SAMSUNG && ARCH_S3C24XX && I2C
select SND_S3C24XX_I2S
select SND_SOC_TLV320AIC23_I2C
select SND_SOC_SAMSUNG_SIMTEC
config SND_SOC_SAMSUNG_SIMTEC_HERMES
tristate "SoC I2S Audio support for Simtec Hermes board"
depends on SND_SOC_SAMSUNG && ARCH_S3C24XX && I2C
select SND_S3C24XX_I2S
select SND_SOC_TLV320AIC3X
select SND_SOC_SAMSUNG_SIMTEC
config SND_SOC_SAMSUNG_H1940_UDA1380
tristate "Audio support for the HP iPAQ H1940"
depends on SND_SOC_SAMSUNG && ARCH_H1940 && I2C
select SND_S3C24XX_I2S
select SND_SOC_UDA1380
help
This driver provides audio support for HP iPAQ h1940 PDA.
config SND_SOC_SAMSUNG_RX1950_UDA1380
tristate "Audio support for the HP iPAQ RX1950"
depends on SND_SOC_SAMSUNG && MACH_RX1950 && I2C
select SND_S3C24XX_I2S
select SND_SOC_UDA1380
help
This driver provides audio support for HP iPAQ RX1950 PDA.
config SND_SOC_SAMSUNG_SMDK_WM9713
tristate "SoC AC97 Audio support for SMDK with WM9713"
depends on SND_SOC_SAMSUNG && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110)
select SND_SOC_WM9713
select SND_SAMSUNG_AC97
help
Say Y if you want to add support for SoC audio on the SMDK.
config SND_SOC_SMARTQ
tristate "SoC I2S Audio support for SmartQ board"
depends on SND_SOC_SAMSUNG && MACH_SMARTQ && I2C
select SND_SAMSUNG_I2S
select SND_SOC_WM8750
config SND_SOC_GONI_AQUILA_WM8994
tristate "SoC I2S Audio support for AQUILA/GONI - WM8994"
depends on SND_SOC_SAMSUNG && (MACH_GONI || MACH_AQUILA)
depends on I2C=y
select SND_SAMSUNG_I2S
select MFD_WM8994
select SND_SOC_WM8994
help
Say Y if you want to add support for SoC audio on goni or aquila
with the WM8994.
config SND_SOC_SAMSUNG_SMDK_SPDIF
tristate "SoC S/PDIF Audio support for SMDK"
depends on SND_SOC_SAMSUNG
select SND_SAMSUNG_SPDIF
help
Say Y if you want to add support for SoC S/PDIF audio on the SMDK.
config SND_SOC_SMDK_WM8580_PCM
tristate "SoC PCM Audio support for WM8580 on SMDK"
depends on SND_SOC_SAMSUNG && (MACH_SMDKV210 || MACH_SMDKC110)
depends on REGMAP_I2C
select SND_SOC_WM8580
select SND_SAMSUNG_PCM
help
Say Y if you want to add support for SoC audio on the SMDK.
config SND_SOC_SMDK_WM8994_PCM
tristate "SoC PCM Audio support for WM8994 on SMDK"
depends on SND_SOC_SAMSUNG
depends on I2C=y
select MFD_WM8994
select SND_SOC_WM8994
select SND_SAMSUNG_PCM
help
Say Y if you want to add support for SoC audio on the SMDK
config SND_SOC_SPEYSIDE
tristate "Audio support for Wolfson Speyside"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
select SND_SAMSUNG_I2S
select SND_SOC_WM8996
select SND_SOC_WM9081
select SND_SOC_WM0010
select SND_SOC_WM1250_EV1
config SND_SOC_TOBERMORY
tristate "Audio support for Wolfson Tobermory"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410 && INPUT && I2C
select SND_SAMSUNG_I2S
select SND_SOC_WM8962
config SND_SOC_BELLS
tristate "Audio support for Wolfson Bells"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410 && MFD_ARIZONA
select SND_SAMSUNG_I2S
select SND_SOC_WM5102
select SND_SOC_WM5110
select SND_SOC_WM9081
select SND_SOC_WM0010
select SND_SOC_WM1250_EV1
config SND_SOC_LOWLAND
tristate "Audio support for Wolfson Lowland"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410 && I2C
select SND_SAMSUNG_I2S
select SND_SOC_WM5100
select SND_SOC_WM9081
config SND_SOC_LITTLEMILL
tristate "Audio support for Wolfson Littlemill"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
select SND_SAMSUNG_I2S
select MFD_WM8994
select SND_SOC_WM8994
config SND_SOC_SNOW
tristate "Audio support for Google Snow boards"
depends on SND_SOC_SAMSUNG && I2C
select SND_SOC_MAX98090
select SND_SOC_MAX98095
select SND_SAMSUNG_I2S
help
Say Y if you want to add audio support for various Snow
boards based on Exynos5 series of SoCs.
config SND_SOC_ODROIDX2
tristate "Audio support for Odroid-X2 and Odroid-U3"
depends on SND_SOC_SAMSUNG
select SND_SOC_MAX98090
select SND_SAMSUNG_I2S
help
Say Y here to enable audio support for the Odroid-X2/U3.
config SND_SOC_SAMSUNG_ESPRESSO8890_WM5110
tristate "SoC I2S Audio support for WM5110 on ESPRESSO8890"
depends on SND_SOC_SAMSUNG
select SND_SAMSUNG_I2S
select SND_SAMSUNG_AUDSS
select SND_SAMSUNG_IOMMU
select SND_SOC_DUMMY_CODEC
select SND_SOC_WM5110
select MFD_WM5110
select MFD_ARIZONA
select MFD_ARIZONA_SPI
select REGULATOR_ARIZONA
select GPIO_ARIZONA
select SWITCH
select SWITCH_ARIZONA
help
Say Y if you want to add support for SoC audio on the ESPRESSO8890.
config SND_SOC_SAMSUNG_UNIVERSAL8890_DUMMY
tristate "SoC I2S Audio support for DUMMY on UNIVERSAL8890"
depends on SND_SOC_SAMSUNG
select SND_SAMSUNG_I2S
select SND_SAMSUNG_AUDSS
select SND_SAMSUNG_IOMMU
select SND_SOC_DUMMY_CODEC
help
Say Y if you want to add support for SoC audio on the UNIVERSAL8890.
config SND_SOC_SAMSUNG_DUMMY_CODEC
tristate "SoC I2S Audio support for DUMMY-CODEC"
depends on SND_SOC_SAMSUNG
select SND_SOC_DUMMY_CODEC
select SND_SAMSUNG_I2S
select SND_SAMSUNG_AUDSS
help
Say Y if you want to enable SAMSUNG dummy codec
config SND_SOC_SAMSUNG_SMDK7870_COD3026X
tristate "SoC I2S Audio support for COD3026X on SMDK7870"
depends on SND_SOC_SAMSUNG
select SND_SAMSUNG_I2S
select SND_SAMSUNG_AUDSS
select SND_SOC_DUMMY_CODEC
select SND_SOC_COD3026X
select SND_SOC_EXYNOS_AUDMIXER
help
Say Y if you want to add support for SoC audio on the smdk7870 board.
config SND_SOC_SAMSUNG_UNIVERSAL7270_LARGO
tristate "SoC I2S Audio support for LARGO on UNIVERSAL7270"
depends on SND_SOC_SAMSUNG
select SND_SAMSUNG_I2S
select SND_SAMSUNG_AUDSS
select SND_SOC_DUMMY_CODEC
select SND_SOC_LARGO
select SND_SOC_EXYNOS_AUDMIXER
help
Say Y if you want to add support for SoC audio on the universal7270 board.
config SND_SOC_SAMSUNG_SMDK7570_COD3026X
tristate "SoC I2S Audio support for COD3026X on SMDK7570"
depends on SND_SOC_SAMSUNG
select SND_SAMSUNG_I2S
select SND_SAMSUNG_AUDSS
select SND_SOC_DUMMY_CODEC
select SND_SOC_COD3026X
select SND_SOC_EXYNOS_AUDMIXER
help
Say Y if you want to add support for SoC audio on the smdk7570 board.
config SND_SOC_SAMSUNG_SMDK7570_COD9002X
tristate "SoC I2S Audio support for COD9002X on SMDK7570"
depends on SND_SOC_SAMSUNG
select SND_SAMSUNG_I2S
select SND_SAMSUNG_AUDSS
select SND_SOC_DUMMY_CODEC
select SND_SOC_COD9002X
select SND_SOC_EXYNOS_AUDMIXER
help
Say Y if you want to add support for SoC audio on the smdk7570 board.
config SND_SOC_SAMSUNG_UNIVERSAL7570_COD9002X
tristate "SoC I2S Audio support for COD9002X on UNIVERSAL7570"
depends on SND_SOC_SAMSUNG
select SND_SAMSUNG_I2S
select SND_SAMSUNG_AUDSS
select SND_SOC_DUMMY_CODEC
select SND_SOC_COD9002X
select SND_SOC_EXYNOS_AUDMIXER
help
Say Y if you want to add support for SoC audio on the universal7570 board.
config SND_SOC_SAMSUNG_UNIVERSAL7870_COD3026X
tristate "SoC I2S Audio support for COD3026X on UNIVERSAL7870"
depends on SND_SOC_SAMSUNG
select SND_SAMSUNG_I2S
select SND_SAMSUNG_AUDSS
select SND_SOC_DUMMY_CODEC
select SND_SOC_COD3026X
select SND_SOC_EXYNOS_AUDMIXER
help
Say Y if you want to add support for SoC audio on the universal7870 board.
config SND_SOC_SAMSUNG_VERBOSE_DEBUG
tristate "Enable verbose debugging of Audio on Samsung Platforms"
config SND_SOC_I2S_TXSLOT_NUMBER
int "Number of tx slots including audio sample"
depends on SND_SAMSUNG_I2S && SND_SAMSUNG_AUDSS
default 2
help
An audio sample is included in a slot in the I2S. Thus, slot number will be 2
in the stereo audio transmission, so it's default number. In the TDM mode,
you have to change number of slot for transmitting more slots.
config SND_SOC_I2S_RXSLOT_NUMBER
int "Number of rx slots including audio sample"
depends on SND_SAMSUNG_I2S && SND_SAMSUNG_AUDSS
default 2
help
An audio sample is included in a slot in the I2S. Thus, slot number will be 2
in the stereo audio transmission, so it's default number. In the TDM mode,
you have to change number of slot for receiving more slots.
config SND_SOC_I2S_1840_TDM
bool "Enable TDM mode on Universal7890"
depends on SND_SAMSUNG_AUDSS && SND_SOC_SAMSUNG_ZERO_CLEARWATER
default n
help
Say Y if you want to add support for I2S TDM on the Universal7890
# For support SEIREN audio
source "sound/soc/samsung/seiren/Kconfig"

112
sound/soc/samsung/Makefile Normal file
View file

@ -0,0 +1,112 @@
# S3c24XX Platform Support
snd-soc-s3c24xx-objs := dma.o
snd-soc-s3c-dma-objs := dmaengine.o
snd-soc-idma-objs := idma.o
snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
snd-soc-ac97-objs := ac97.o
snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o
snd-soc-samsung-spdif-objs := spdif.o
snd-soc-pcm-objs := pcm.o
snd-soc-i2s-objs := i2s.o
snd-soc-lpass-objs := lpass.o
snd-soc-compr-objs := compr.o visualizercap-dummydai.o
snd-soc-eax-dai-objs := eax-dai.o
snd-soc-eax-dma-objs := eax-dma.o
snd-soc-esa-sa-effect-objs := esa_sa_effect.o
obj-$(CONFIG_SND_SOC_SAMSUNG) += snd-soc-s3c24xx.o
obj-$(CONFIG_SND_S3C24XX_I2S) += snd-soc-s3c24xx-i2s.o
obj-$(CONFIG_SND_SAMSUNG_AC97) += snd-soc-ac97.o
obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o
obj-$(CONFIG_SND_SAMSUNG_SPDIF) += snd-soc-samsung-spdif.o
obj-$(CONFIG_SND_SAMSUNG_PCM) += snd-soc-pcm.o
obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o
obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-idma.o
obj-$(CONFIG_SND_SAMSUNG_COMPR) += snd-soc-compr.o
ifeq ($(CONFIG_SND_SAMSUNG_AUDSS),y)
obj-$(CONFIG_SOC_EXYNOS5422) += lpass.o lpass-exynos5422.o
obj-$(CONFIG_SOC_EXYNOS5430) += lpass.o lpass-exynos5430.o
obj-$(CONFIG_SOC_EXYNOS5433) += lpass.o lpass-exynos5433.o
obj-$(CONFIG_SOC_EXYNOS7420) += lpass.o lpass-exynos7420.o
obj-$(CONFIG_SOC_EXYNOS7580) += lpass.o lpass-exynos7580.o
obj-$(CONFIG_SOC_EXYNOS7890) += lpass.o lpass-exynos7890.o
obj-$(CONFIG_SOC_EXYNOS8890) += lpass.o lpass-exynos8890.o
obj-$(CONFIG_SOC_EXYNOS7870) += lpass-exynos7870.o
obj-$(CONFIG_SOC_EXYNOS7570) += lpass-exynos7570.o
endif
obj-$(CONFIG_SND_SOC_SAMSUNG) += snd-soc-eax-dai.o snd-soc-eax-dma.o
obj-$(CONFIG_SND_ESA_SA_EFFECT) += snd-soc-esa-sa-effect.o
# S3C24XX Machine Support
snd-soc-jive-wm8750-objs := jive_wm8750.o
snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o
snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o
snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o
snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o
snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o
snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o
snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o
snd-soc-h1940-uda1380-objs := h1940_uda1380.o
snd-soc-rx1950-uda1380-objs := rx1950_uda1380.o
snd-soc-smdk-wm8580-objs := smdk_wm8580.o
snd-soc-smdk-wm8994-objs := smdk_wm8994.o
snd-soc-snow-objs := snow.o
snd-soc-smdk-wm9713-objs := smdk_wm9713.o
snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o
snd-soc-goni-wm8994-objs := goni_wm8994.o
snd-soc-smdk-spdif-objs := smdk_spdif.o
snd-soc-smdk-wm8580pcm-objs := smdk_wm8580pcm.o
snd-soc-smdk-wm8994pcm-objs := smdk_wm8994pcm.o
snd-soc-speyside-objs := speyside.o
snd-soc-tobermory-objs := tobermory.o
snd-soc-lowland-objs := lowland.o
snd-soc-littlemill-objs := littlemill.o
snd-soc-bells-objs := bells.o
snd-soc-odroidx2-max98090-objs := odroidx2_max98090.o
snd-soc-universal8890-dummy-objs := universal8890_dummy.o
snd-soc-smdk7870-cod3026x-objs := smdk7870-cod3026.o
snd-soc-smdk7570-cod3026x-objs := smdk7570-cod3026.o
snd-soc-smdk7570-cod9002x-objs := smdk7570-cod9002.o
snd-soc-universal7570-cod9002x-objs := universal7570-cod9002.o
snd-soc-universal7270-largo-objs := universal7270-largo.o
snd-soc-universal7870-cod3026x-objs := universal7870-cod3026.o
snd-soc-espresso8890-wm5110-objs := cp_dummy.o espresso8890_wm5110.o
obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o
obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o
obj-$(CONFIG_SND_SOC_SAMSUNG_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o
obj-$(CONFIG_SND_SOC_SAMSUNG_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC) += snd-soc-s3c24xx-simtec.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o
obj-$(CONFIG_SND_SOC_SAMSUNG_H1940_UDA1380) += snd-soc-h1940-uda1380.o
obj-$(CONFIG_SND_SOC_SAMSUNG_RX1950_UDA1380) += snd-soc-rx1950-uda1380.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8580) += snd-soc-smdk-wm8580.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994) += snd-soc-smdk-wm8994.o
obj-$(CONFIG_SND_SOC_SNOW) += snd-soc-snow.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM9713) += snd-soc-smdk-wm9713.o
obj-$(CONFIG_SND_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF) += snd-soc-smdk-spdif.o
obj-$(CONFIG_SND_SOC_GONI_AQUILA_WM8994) += snd-soc-goni-wm8994.o
obj-$(CONFIG_SND_SOC_SMDK_WM8580_PCM) += snd-soc-smdk-wm8580pcm.o
obj-$(CONFIG_SND_SOC_SMDK_WM8994_PCM) += snd-soc-smdk-wm8994pcm.o
obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o
obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o
obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o
obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o
obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o
obj-$(CONFIG_SND_SOC_ODROIDX2) += snd-soc-odroidx2-max98090.o
obj-$(CONFIG_SND_SOC_SAMSUNG_ESPRESSO8890_WM5110) += snd-soc-espresso8890-wm5110.o
obj-$(CONFIG_SND_SOC_SAMSUNG_UNIVERSAL8890_DUMMY) += snd-soc-universal8890-dummy.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK7870_COD3026X) += snd-soc-smdk7870-cod3026x.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK7570_COD3026X) += snd-soc-smdk7570-cod3026x.o
obj-$(CONFIG_SND_SOC_SAMSUNG_UNIVERSAL7870_COD3026X) += snd-soc-universal7870-cod3026x.o
obj-$(CONFIG_SND_SOC_SAMSUNG_DUMMY_CODEC) += snd-soc-universal8890-dummy.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK7570_COD9002X) += snd-soc-smdk7570-cod9002x.o
obj-$(CONFIG_SND_SOC_SAMSUNG_UNIVERSAL7570_COD9002X) += snd-soc-universal7570-cod9002x.o
obj-$(CONFIG_SND_SOC_SAMSUNG_UNIVERSAL7270_LARGO) += snd-soc-universal7270-largo.o
obj-$(CONFIG_SND_SAMSUNG_SEIREN) += seiren/

454
sound/soc/samsung/ac97.c Normal file
View file

@ -0,0 +1,454 @@
/* sound/soc/samsung/ac97.c
*
* ALSA SoC Audio Layer - S3C AC97 Controller driver
* Evolved from s3c2443-ac97.c
*
* Copyright (c) 2010 Samsung Electronics Co. Ltd
* Author: Jaswinder Singh <jassisinghbrar@gmail.com>
* Credits: Graeme Gregory, Sean Choi
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <sound/soc.h>
#include "regs-ac97.h"
#include <linux/platform_data/asoc-s3c.h>
#include "dma.h"
#define AC_CMD_ADDR(x) (x << 16)
#define AC_CMD_DATA(x) (x & 0xffff)
#define S3C_AC97_DAI_PCM 0
#define S3C_AC97_DAI_MIC 1
struct s3c_ac97_info {
struct clk *ac97_clk;
void __iomem *regs;
struct mutex lock;
struct completion done;
};
static struct s3c_ac97_info s3c_ac97;
static struct s3c_dma_params s3c_ac97_pcm_out = {
.dma_size = 4,
};
static struct s3c_dma_params s3c_ac97_pcm_in = {
.dma_size = 4,
};
static struct s3c_dma_params s3c_ac97_mic_in = {
.dma_size = 4,
};
static void s3c_ac97_activate(struct snd_ac97 *ac97)
{
u32 ac_glbctrl, stat;
stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
return; /* Return if already active */
reinit_completion(&s3c_ac97.done);
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
msleep(1);
ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
msleep(1);
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
pr_err("AC97: Unable to activate!");
}
static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
unsigned short reg)
{
u32 ac_glbctrl, ac_codec_cmd;
u32 stat, addr, data;
mutex_lock(&s3c_ac97.lock);
s3c_ac97_activate(ac97);
reinit_completion(&s3c_ac97.done);
ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
udelay(50);
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
pr_err("AC97: Unable to read!");
stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
addr = (stat >> 16) & 0x7f;
data = (stat & 0xffff);
if (addr != reg)
pr_err("ac97: req addr = %02x, rep addr = %02x\n",
reg, addr);
mutex_unlock(&s3c_ac97.lock);
return (unsigned short)data;
}
static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
unsigned short val)
{
u32 ac_glbctrl, ac_codec_cmd;
mutex_lock(&s3c_ac97.lock);
s3c_ac97_activate(ac97);
reinit_completion(&s3c_ac97.done);
ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
udelay(50);
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
pr_err("AC97: Unable to write!");
ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
mutex_unlock(&s3c_ac97.lock);
}
static void s3c_ac97_cold_reset(struct snd_ac97 *ac97)
{
pr_debug("AC97: Cold reset\n");
writel(S3C_AC97_GLBCTRL_COLDRESET,
s3c_ac97.regs + S3C_AC97_GLBCTRL);
msleep(1);
writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
msleep(1);
}
static void s3c_ac97_warm_reset(struct snd_ac97 *ac97)
{
u32 stat;
stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
return; /* Return if already active */
pr_debug("AC97: Warm reset\n");
writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
msleep(1);
writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
msleep(1);
s3c_ac97_activate(ac97);
}
static irqreturn_t s3c_ac97_irq(int irq, void *dev_id)
{
u32 ac_glbctrl, ac_glbstat;
ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
complete(&s3c_ac97.done);
}
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl |= (1<<30); /* Clear interrupt */
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
return IRQ_HANDLED;
}
static struct snd_ac97_bus_ops s3c_ac97_ops = {
.read = s3c_ac97_read,
.write = s3c_ac97_write,
.warm_reset = s3c_ac97_warm_reset,
.reset = s3c_ac97_cold_reset,
};
static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
u32 ac_glbctrl;
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
else
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
else
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
break;
}
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
return 0;
}
static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
u32 ac_glbctrl;
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
break;
}
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
return 0;
}
static const struct snd_soc_dai_ops s3c_ac97_dai_ops = {
.trigger = s3c_ac97_trigger,
};
static const struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
.trigger = s3c_ac97_mic_trigger,
};
static int s3c_ac97_dai_probe(struct snd_soc_dai *dai)
{
samsung_asoc_init_dma_data(dai, &s3c_ac97_pcm_out, &s3c_ac97_pcm_in);
return 0;
}
static int s3c_ac97_mic_dai_probe(struct snd_soc_dai *dai)
{
samsung_asoc_init_dma_data(dai, NULL, &s3c_ac97_mic_in);
return 0;
}
static struct snd_soc_dai_driver s3c_ac97_dai[] = {
[S3C_AC97_DAI_PCM] = {
.name = "samsung-ac97",
.ac97_control = 1,
.playback = {
.stream_name = "AC97 Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.stream_name = "AC97 Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.probe = s3c_ac97_dai_probe,
.ops = &s3c_ac97_dai_ops,
},
[S3C_AC97_DAI_MIC] = {
.name = "samsung-ac97-mic",
.ac97_control = 1,
.capture = {
.stream_name = "AC97 Mic Capture",
.channels_min = 1,
.channels_max = 1,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.probe = s3c_ac97_mic_dai_probe,
.ops = &s3c_ac97_mic_dai_ops,
},
};
static const struct snd_soc_component_driver s3c_ac97_component = {
.name = "s3c-ac97",
};
static int s3c_ac97_probe(struct platform_device *pdev)
{
struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
struct s3c_audio_pdata *ac97_pdata;
int ret;
ac97_pdata = pdev->dev.platform_data;
if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
return -EINVAL;
}
/* Check for availability of necessary resource */
dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dmatx_res) {
dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
return -ENXIO;
}
dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!dmarx_res) {
dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
return -ENXIO;
}
dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
if (!dmamic_res) {
dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
return -ENXIO;
}
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!irq_res) {
dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
return -ENXIO;
}
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
s3c_ac97.regs = devm_ioremap_resource(&pdev->dev, mem_res);
if (IS_ERR(s3c_ac97.regs))
return PTR_ERR(s3c_ac97.regs);
s3c_ac97_pcm_out.channel = dmatx_res->start;
s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
s3c_ac97_pcm_in.channel = dmarx_res->start;
s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
s3c_ac97_mic_in.channel = dmamic_res->start;
s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
init_completion(&s3c_ac97.done);
mutex_init(&s3c_ac97.lock);
s3c_ac97.ac97_clk = devm_clk_get(&pdev->dev, "ac97");
if (IS_ERR(s3c_ac97.ac97_clk)) {
dev_err(&pdev->dev, "ac97 failed to get ac97_clock\n");
ret = -ENODEV;
goto err2;
}
clk_prepare_enable(s3c_ac97.ac97_clk);
if (ac97_pdata->cfg_gpio(pdev)) {
dev_err(&pdev->dev, "Unable to configure gpio\n");
ret = -EINVAL;
goto err3;
}
ret = request_irq(irq_res->start, s3c_ac97_irq,
0, "AC97", NULL);
if (ret < 0) {
dev_err(&pdev->dev, "ac97: interrupt request failed.\n");
goto err4;
}
ret = snd_soc_set_ac97_ops(&s3c_ac97_ops);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);
goto err4;
}
ret = devm_snd_soc_register_component(&pdev->dev, &s3c_ac97_component,
s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
if (ret)
goto err5;
ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
goto err5;
}
return 0;
err5:
free_irq(irq_res->start, NULL);
err4:
err3:
clk_disable_unprepare(s3c_ac97.ac97_clk);
err2:
snd_soc_set_ac97_ops(NULL);
return ret;
}
static int s3c_ac97_remove(struct platform_device *pdev)
{
struct resource *irq_res;
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (irq_res)
free_irq(irq_res->start, NULL);
clk_disable_unprepare(s3c_ac97.ac97_clk);
snd_soc_set_ac97_ops(NULL);
return 0;
}
static struct platform_driver s3c_ac97_driver = {
.probe = s3c_ac97_probe,
.remove = s3c_ac97_remove,
.driver = {
.name = "samsung-ac97",
.owner = THIS_MODULE,
},
};
module_platform_driver(s3c_ac97_driver);
MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
MODULE_DESCRIPTION("AC97 driver for the Samsung SoC");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-ac97");

459
sound/soc/samsung/bells.c Normal file
View file

@ -0,0 +1,459 @@
/*
* Bells audio support
*
* Copyright 2012 Wolfson Microelectronics
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm5102.h"
#include "../codecs/wm9081.h"
/* BCLK2 is fixed at this currently */
#define BCLK2_RATE (64 * 8000)
/*
* Expect a 24.576MHz crystal if one is fitted (the driver will function
* if this is not fitted).
*/
#define MCLK_RATE 24576000
#define SYS_AUDIO_RATE 44100
#define SYS_MCLK_RATE (SYS_AUDIO_RATE * 512)
#define DAI_AP_DSP 0
#define DAI_DSP_CODEC 1
#define DAI_CODEC_CP 2
#define DAI_CODEC_SUB 3
struct bells_drvdata {
int sysclk_rate;
int asyncclk_rate;
};
static struct bells_drvdata wm2200_drvdata = {
.sysclk_rate = 22579200,
};
static struct bells_drvdata wm5102_drvdata = {
.sysclk_rate = 45158400,
.asyncclk_rate = 49152000,
};
static struct bells_drvdata wm5110_drvdata = {
.sysclk_rate = 135475200,
.asyncclk_rate = 147456000,
};
static int bells_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[DAI_DSP_CODEC].codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct bells_drvdata *bells = card->drvdata;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
break;
ret = snd_soc_codec_set_pll(codec, WM5102_FLL1,
ARIZONA_FLL_SRC_MCLK1,
MCLK_RATE,
bells->sysclk_rate);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
if (bells->asyncclk_rate) {
ret = snd_soc_codec_set_pll(codec, WM5102_FLL2,
ARIZONA_FLL_SRC_AIF2BCLK,
BCLK2_RATE,
bells->asyncclk_rate);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
}
break;
default:
break;
}
return 0;
}
static int bells_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[DAI_DSP_CODEC].codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct bells_drvdata *bells = card->drvdata;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, 0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
if (bells->asyncclk_rate) {
ret = snd_soc_codec_set_pll(codec, WM5102_FLL2,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
}
break;
default:
break;
}
dapm->bias_level = level;
return 0;
}
static int bells_late_probe(struct snd_soc_card *card)
{
struct bells_drvdata *bells = card->drvdata;
struct snd_soc_codec *wm0010 = card->rtd[DAI_AP_DSP].codec;
struct snd_soc_codec *codec = card->rtd[DAI_DSP_CODEC].codec;
struct snd_soc_dai *aif1_dai = card->rtd[DAI_DSP_CODEC].codec_dai;
struct snd_soc_dai *aif2_dai;
struct snd_soc_dai *aif3_dai;
struct snd_soc_dai *wm9081_dai;
int ret;
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_SYSCLK,
ARIZONA_CLK_SRC_FLL1,
bells->sysclk_rate,
SND_SOC_CLOCK_IN);
if (ret != 0) {
dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_sysclk(wm0010, 0, 0, SYS_MCLK_RATE, 0);
if (ret != 0) {
dev_err(wm0010->dev, "Failed to set WM0010 clock: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0);
if (ret != 0)
dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_OPCLK, 0,
SYS_MCLK_RATE, SND_SOC_CLOCK_OUT);
if (ret != 0)
dev_err(codec->dev, "Failed to set OPCLK: %d\n", ret);
if (card->num_rtd == DAI_CODEC_CP)
return 0;
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK,
ARIZONA_CLK_SRC_FLL2,
bells->asyncclk_rate,
SND_SOC_CLOCK_IN);
if (ret != 0) {
dev_err(codec->dev, "Failed to set ASYNCCLK: %d\n", ret);
return ret;
}
aif2_dai = card->rtd[DAI_CODEC_CP].cpu_dai;
ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0);
if (ret != 0) {
dev_err(aif2_dai->dev, "Failed to set AIF2 clock: %d\n", ret);
return ret;
}
if (card->num_rtd == DAI_CODEC_SUB)
return 0;
aif3_dai = card->rtd[DAI_CODEC_SUB].cpu_dai;
wm9081_dai = card->rtd[DAI_CODEC_SUB].codec_dai;
ret = snd_soc_dai_set_sysclk(aif3_dai, ARIZONA_CLK_SYSCLK, 0, 0);
if (ret != 0) {
dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_sysclk(wm9081_dai->codec, WM9081_SYSCLK_MCLK,
0, SYS_MCLK_RATE, 0);
if (ret != 0) {
dev_err(wm9081_dai->dev, "Failed to set MCLK: %d\n", ret);
return ret;
}
return 0;
}
static const struct snd_soc_pcm_stream baseband_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 2,
.channels_max = 2,
};
static const struct snd_soc_pcm_stream sub_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = SYS_AUDIO_RATE,
.rate_max = SYS_AUDIO_RATE,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link bells_dai_wm2200[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm0010-sdi1",
.platform_name = "samsung-i2s.0",
.codec_name = "spi0.0",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.cpu_dai_name = "wm0010-sdi2",
.codec_dai_name = "wm2200",
.codec_name = "wm2200.1-003a",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.params = &sub_params,
.ignore_suspend = 1,
},
};
static struct snd_soc_dai_link bells_dai_wm5102[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm0010-sdi1",
.platform_name = "samsung-i2s.0",
.codec_name = "spi0.0",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.cpu_dai_name = "wm0010-sdi2",
.codec_dai_name = "wm5102-aif1",
.codec_name = "wm5102-codec",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.params = &sub_params,
.ignore_suspend = 1,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm5102-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
.params = &baseband_params,
},
{
.name = "Sub",
.stream_name = "Sub",
.cpu_dai_name = "wm5102-aif3",
.codec_dai_name = "wm9081-hifi",
.codec_name = "wm9081.1-006c",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS,
.ignore_suspend = 1,
.params = &sub_params,
},
};
static struct snd_soc_dai_link bells_dai_wm5110[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm0010-sdi1",
.platform_name = "samsung-i2s.0",
.codec_name = "spi0.0",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.cpu_dai_name = "wm0010-sdi2",
.codec_dai_name = "wm5110-aif1",
.codec_name = "wm5110-codec",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.params = &sub_params,
.ignore_suspend = 1,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm5110-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
.params = &baseband_params,
},
{
.name = "Sub",
.stream_name = "Sub",
.cpu_dai_name = "wm5110-aif3",
.codec_dai_name = "wm9081-hifi",
.codec_name = "wm9081.1-006c",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS,
.ignore_suspend = 1,
.params = &sub_params,
},
};
static struct snd_soc_codec_conf bells_codec_conf[] = {
{
.dev_name = "wm9081.1-006c",
.name_prefix = "Sub",
},
};
static struct snd_soc_dapm_widget bells_widgets[] = {
SND_SOC_DAPM_MIC("DMIC", NULL),
};
static struct snd_soc_dapm_route bells_routes[] = {
{ "Sub CLK_SYS", NULL, "OPCLK" },
{ "CLKIN", NULL, "OPCLK" },
{ "DMIC", NULL, "MICBIAS2" },
{ "IN2L", NULL, "DMIC" },
{ "IN2R", NULL, "DMIC" },
};
static struct snd_soc_card bells_cards[] = {
{
.name = "Bells WM2200",
.owner = THIS_MODULE,
.dai_link = bells_dai_wm2200,
.num_links = ARRAY_SIZE(bells_dai_wm2200),
.codec_conf = bells_codec_conf,
.num_configs = ARRAY_SIZE(bells_codec_conf),
.late_probe = bells_late_probe,
.dapm_widgets = bells_widgets,
.num_dapm_widgets = ARRAY_SIZE(bells_widgets),
.dapm_routes = bells_routes,
.num_dapm_routes = ARRAY_SIZE(bells_routes),
.set_bias_level = bells_set_bias_level,
.set_bias_level_post = bells_set_bias_level_post,
.drvdata = &wm2200_drvdata,
},
{
.name = "Bells WM5102",
.owner = THIS_MODULE,
.dai_link = bells_dai_wm5102,
.num_links = ARRAY_SIZE(bells_dai_wm5102),
.codec_conf = bells_codec_conf,
.num_configs = ARRAY_SIZE(bells_codec_conf),
.late_probe = bells_late_probe,
.dapm_widgets = bells_widgets,
.num_dapm_widgets = ARRAY_SIZE(bells_widgets),
.dapm_routes = bells_routes,
.num_dapm_routes = ARRAY_SIZE(bells_routes),
.set_bias_level = bells_set_bias_level,
.set_bias_level_post = bells_set_bias_level_post,
.drvdata = &wm5102_drvdata,
},
{
.name = "Bells WM5110",
.owner = THIS_MODULE,
.dai_link = bells_dai_wm5110,
.num_links = ARRAY_SIZE(bells_dai_wm5110),
.codec_conf = bells_codec_conf,
.num_configs = ARRAY_SIZE(bells_codec_conf),
.late_probe = bells_late_probe,
.dapm_widgets = bells_widgets,
.num_dapm_widgets = ARRAY_SIZE(bells_widgets),
.dapm_routes = bells_routes,
.num_dapm_routes = ARRAY_SIZE(bells_routes),
.set_bias_level = bells_set_bias_level,
.set_bias_level_post = bells_set_bias_level_post,
.drvdata = &wm5110_drvdata,
},
};
static int bells_probe(struct platform_device *pdev)
{
int ret;
bells_cards[pdev->id].dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, &bells_cards[pdev->id]);
if (ret)
dev_err(&pdev->dev,
"snd_soc_register_card(%s) failed: %d\n",
bells_cards[pdev->id].name, ret);
return ret;
}
static struct platform_driver bells_driver = {
.driver = {
.name = "bells",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = bells_probe,
};
module_platform_driver(bells_driver);
MODULE_DESCRIPTION("Bells audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bells");

966
sound/soc/samsung/compr.c Normal file
View file

@ -0,0 +1,966 @@
/* sound/soc/samsung/compr.c
*
* ALSA SoC Audio Layer - Samsung Compress platform driver
*
* Copyright (c) 2014 Samsung Electronics Co. Ltd.
* Yeongman Seo <yman.seo@samsung.com>
* Lee Tae Ho <taeho07.lee@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/time.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include "compr.h"
#include "./seiren/seiren.h"
#ifdef CONFIG_SND_ESA_SA_EFFECT
#include "esa_sa_effect.h"
#endif
static struct snd_compr_caps compr_cap = {
.direction = SND_COMPRESS_PLAYBACK,
.min_fragment_size = 4 * 1024,
.max_fragment_size = 32 * 1024,
.min_fragments = 1,
.max_fragments = 5,
.num_codecs = 2,
.codecs[COMPR_MP3] = SND_AUDIOCODEC_MP3,
.codecs[COMPR_AAC] = SND_AUDIOCODEC_AAC,
};
struct audio_processor* compr_audio_processor_alloc(seiren_ops ops, void* priv)
{
struct audio_processor *ap;
ap = kzalloc(sizeof(struct audio_processor), GFP_KERNEL);
if (!ap)
return NULL;
ap->ops = ops;
ap->priv = priv;
ap->reg_ack = esa_compr_get_mem() + COMPR_ACK;
return ap;
}
#ifdef AUDIO_PERF
enum CHECK_TIMES {
OPEN_T = 0x0,
WRITE_T,
POINTER_T,
DRAIN_T,
ISR_T,
TOTAL_TIMES,
};
#endif
struct runtime_data {
struct snd_compr_stream *cstream;
struct snd_compr_caps *compr_cap;
struct snd_compr_params codec_param;
spinlock_t lock;
int state;
struct snd_soc_dai *cpu_dai;
struct snd_soc_dai *codec_dai;
struct snd_pcm_substream substream;
struct snd_pcm_hw_params hw_params;
uint32_t byte_offset;
u64 copied_total;
u64 received_total;
u64 app_pointer;
void *buffer;
#ifdef AUDIO_PERF
uint32_t start_time[TOTAL_TIMES];
uint32_t end_time[TOTAL_TIMES];
u64 total_time[TOTAL_TIMES];
#endif
atomic_t start;
atomic_t eos;
atomic_t created;
wait_queue_head_t flush_wait;
wait_queue_head_t exit_wait;
uint32_t stop_ack;
uint32_t exit_ack;
struct audio_processor* ap;
};
int compr_dai_cmd(struct runtime_data *prtd, int cmd);
static int compr_event_handler(uint32_t cmd, uint32_t size, void* priv)
{
struct runtime_data *prtd = priv;
struct snd_compr_runtime *runtime = prtd->cstream->runtime;
u64 bytes_available;
int ret;
pr_debug("%s: event handler cmd(%x)\n", __func__, cmd);
#ifdef AUDIO_PERF
prtd->start_time[ISR_T] = sched_clock();
#endif
switch(cmd) {
case INTR_CREATED:
pr_debug("%s: offload instance is created\n", __func__);
break;
case INTR_DECODED:
spin_lock(&prtd->lock);
/* update copied total bytes */
prtd->copied_total += size;
prtd->byte_offset += size;
if (prtd->byte_offset >= runtime->buffer_size)
prtd->byte_offset -= runtime->buffer_size;
snd_compr_fragment_elapsed(prtd->cstream);
if (!atomic_read(&prtd->start) &&
runtime->state != SNDRV_PCM_STATE_PAUSED) {
/* Writes must be restarted from _copy() */
pr_err("%s: write_done received while not started(%d)",
__func__, runtime->state);
spin_unlock(&prtd->lock);
return -EIO;
}
bytes_available = prtd->received_total -
prtd->copied_total;
pr_debug("%s: current free bufsize(%llu)\n", __func__,
runtime->buffer_size - bytes_available);
if (bytes_available < runtime->fragment_size) {
pr_debug("%s: WRITE_DONE Insufficient data to send.(avail:%llu)\n",
__func__, bytes_available);
}
spin_unlock(&prtd->lock);
break;
case INTR_FLUSH:
prtd->stop_ack = 1;
wake_up(&prtd->flush_wait);
break;
case INTR_PAUSED:
ret = compr_dai_cmd(prtd, cmd);
if (ret)
pr_err("%s: compr_dai_cmd fail(%d)\n", __func__, ret);
break;
case INTR_EOS:
if (atomic_read(&prtd->eos)) {
if (prtd->copied_total != prtd->received_total)
pr_err("%s: EOS is not sync!(%llu/%llu)\n", __func__,
prtd->copied_total, prtd->received_total);
/* ALSA Framework callback to notify drain complete */
snd_compr_drain_notify(prtd->cstream);
atomic_set(&prtd->eos, 0);
pr_info("%s: DATA_CMD_EOS wake up\n", __func__);
}
break;
case INTR_DESTROY:
prtd->exit_ack = 1;
wake_up(&prtd->exit_wait);
break;
default:
pr_err("%s: unknown command(%x)\n", __func__, cmd);
break;
}
#ifdef AUDIO_PERF
prtd->end_time[ISR_T] = sched_clock();
prtd->total_time[ISR_T] +=
prtd->end_time[ISR_T] - prtd->start_time[ISR_T];
#endif
return 0;
}
static int compr_config_substream(struct snd_compr_stream *cstream,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
int ret;
pr_debug("%s\n", __func__);
substream->pid = get_task_pid(current, PIDTYPE_PID);
substream->private_data = rtd;
substream->stream = (cstream->direction == SND_COMPRESS_PLAYBACK) ?
SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE;
substream->runtime = kzalloc(sizeof(struct snd_pcm_runtime), GFP_KERNEL);
if (substream->runtime == NULL) {
ret = -ENOMEM;
goto config_substream_runtime_err;
}
substream->runtime->hw_constraints.rules_num = 0;
substream->runtime->hw_constraints.rules_all = 1;
substream->runtime->hw_constraints.rules =
kzalloc(sizeof(struct snd_pcm_hw_rule), GFP_KERNEL);
if (substream->runtime->hw_constraints.rules == NULL) {
ret = -ENOMEM;
goto config_substream_runtime_rules_err;
}
return 0;
config_substream_runtime_rules_err:
kfree(substream->runtime);
config_substream_runtime_err:
return ret;
}
static int compr_dai_setup(struct runtime_data *prtd, struct snd_soc_pcm_runtime *rtd)
{
struct snd_pcm_substream *substream = &prtd->substream;
struct snd_soc_dai *cpu_dai = prtd->cpu_dai;
struct snd_soc_dai *codec_dai = prtd->codec_dai;
const struct snd_soc_dai_ops *cpu_dai_ops = cpu_dai->driver->ops;
const struct snd_soc_dai_ops *codec_dai_ops = codec_dai->driver->ops;
struct snd_pcm_hw_params *params = &prtd->hw_params;
int ret;
if (cpu_dai_ops->startup) {
ret = (*cpu_dai_ops->startup)(substream, cpu_dai);
if (ret < 0) {
dev_err(cpu_dai->dev, "can't open interface"
" %s: %d\n", cpu_dai->name, ret);
goto cpu_dai_err;
}
}
if (codec_dai_ops->startup) {
ret = (*codec_dai_ops->startup)(substream, codec_dai);
if (ret < 0) {
dev_err(codec_dai->dev, "can't open codec"
" %s: %d\n", codec_dai->name, ret);
goto codec_dai_err;
}
}
if (rtd->dai_link->ops->hw_params) {
ret = (*rtd->dai_link->ops->hw_params)(substream, params);
if (ret < 0) {
pr_err("%s: hw_params err(%d)\n", __func__, ret);
goto hw_params_err;
}
}
if (codec_dai_ops->hw_params) {
ret = (*codec_dai_ops->hw_params)(substream, params, codec_dai);
if (ret < 0) {
dev_err(codec_dai->dev, "can't set %s hw params:"
" %d\n", codec_dai->name, ret);
goto codec_dai_hw_param_err;
}
}
if (cpu_dai_ops->hw_params) {
ret = (*cpu_dai_ops->hw_params)(substream, params, cpu_dai);
if (ret < 0) {
dev_err(cpu_dai->dev, "can't set %s hw params:"
" %d\n", cpu_dai->name, ret);
goto cpu_dai_hw_param_err;
}
}
prtd->cpu_dai->rate = params_rate(params);
prtd->codec_dai->rate = params_rate(params);
return 0;
cpu_dai_hw_param_err:
if (cpu_dai_ops->hw_free)
(*cpu_dai_ops->hw_free)(substream, prtd->cpu_dai);
codec_dai_hw_param_err:
if (codec_dai_ops->hw_free)
(*codec_dai_ops->hw_free)(substream, prtd->codec_dai);
hw_params_err:
codec_dai_err:
if (codec_dai_ops->shutdown)
(*codec_dai_ops->shutdown)(substream, codec_dai);
cpu_dai_err:
if (cpu_dai_ops->shutdown)
(*cpu_dai_ops->shutdown)(substream, cpu_dai);
return ret;
}
int compr_dai_cmd(struct runtime_data *prtd, int cmd)
{
struct snd_pcm_substream *substream = &prtd->substream;
struct snd_soc_dai *cpu_dai = prtd->cpu_dai;
struct snd_soc_dai *codec_dai = prtd->codec_dai;
const struct snd_soc_dai_ops *cpu_dai_ops = cpu_dai->driver->ops;
const struct snd_soc_dai_ops *codec_dai_ops = codec_dai->driver->ops;
int ret;
if (codec_dai_ops->trigger) {
ret = (*codec_dai_ops->trigger)(substream, cmd, codec_dai);
if (ret < 0) {
pr_err("%s: error codec_dai trigger(%d)\n", __func__, cmd);
goto trigger_err;
}
}
if (cpu_dai_ops->trigger) {
ret = (*cpu_dai_ops->trigger)(substream, cmd, cpu_dai);
if (ret < 0) {
pr_err("%s: error cpu_dai trigger(%d)\n", __func__, cmd);
goto trigger_err;
}
}
return 0;
trigger_err:
return ret;
}
static int compr_dai_prepare(struct runtime_data *prtd)
{
struct snd_pcm_substream *substream = &prtd->substream;
struct snd_soc_dai *cpu_dai = prtd->cpu_dai;
struct snd_soc_dai *codec_dai = prtd->codec_dai;
const struct snd_soc_dai_ops *cpu_dai_ops = cpu_dai->driver->ops;
const struct snd_soc_dai_ops *codec_dai_ops = codec_dai->driver->ops;
int ret;
if (codec_dai_ops->prepare) {
ret = (*codec_dai_ops->prepare)(substream, codec_dai);
if (ret < 0) {
dev_err(codec_dai->dev, "DAI prepare error: %d\n",
ret);
goto prepare_err;
}
}
if (cpu_dai_ops->prepare) {
ret = (*cpu_dai_ops->prepare)(substream, cpu_dai);
if (ret < 0) {
dev_err(codec_dai->dev, "DAI prepare error: %d\n",
ret);
goto prepare_err;
}
}
return 0;
prepare_err:
return ret;
}
static void compr_config_hw_params(struct snd_pcm_hw_params *params,
struct snd_compr_params *compr_params)
{
u64 fmt;
int acodec_rate = 48000;
pr_debug("%s\n", __func__);
fmt = ffs(SNDRV_PCM_FMTBIT_S16_LE) - 1;
snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), fmt);
hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min = 16;
hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS)->min = 32;
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = 2;
#ifdef CONFIG_SND_ESA_SA_EFFECT
acodec_rate = esa_compr_get_sample_rate();
if (!acodec_rate)
acodec_rate = 48000;
#endif
pr_info("%s input_SR %d PCM_HW_PARAM_RATE %d \n", __func__,
compr_params->codec.sample_rate, acodec_rate);
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = acodec_rate;
}
static int compr_open(struct snd_compr_stream *cstream)
{
struct snd_compr_runtime *runtime = cstream->runtime;
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
struct runtime_data *prtd;
struct snd_pcm_substream *substream;
int ret;
pr_debug("%s\n", __func__);
prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
if (prtd == NULL)
return -ENOMEM;
spin_lock_init(&prtd->lock);
esa_compr_set_state(true);
prtd->ap = compr_audio_processor_alloc((seiren_ops)compr_event_handler,
prtd);
if(!prtd->ap) {
pr_err("%s: could not allocate memory\n", __func__);
ret = -ENOMEM;
goto compr_audio_processor_alloc_err;
}
#ifdef CONFIG_SND_ESA_SA_EFFECT
aud_vol.ap[COMPR_DAI_MULTIMEDIA_1] = prtd->ap;
#endif
runtime->private_data = prtd;
prtd->cpu_dai = rtd->cpu_dai;
prtd->codec_dai = rtd->codec_dai;
substream = &prtd->substream;
ret = compr_config_substream(cstream, substream);
if (ret) {
pr_err("%s: could not config substream(%d)\n", __func__, ret);
goto compr_audio_processor_alloc_err;
}
/* init runtime data */
prtd->cstream = cstream;
prtd->byte_offset = 0;
prtd->app_pointer = 0;
prtd->copied_total = 0;
prtd->received_total = 0;
prtd->compr_cap = &compr_cap;
prtd->ap->sample_rate = 44100;
prtd->ap->num_channels = 3; /* stereo channel */
atomic_set(&prtd->eos, 0);
atomic_set(&prtd->start, 0);
atomic_set(&prtd->created, 0);
init_waitqueue_head(&prtd->flush_wait);
init_waitqueue_head(&prtd->exit_wait);
#ifdef AUDIO_PERF
prtd->start_time[OPEN_T] = sched_clock();
#endif
esa_compr_open();
return 0;
compr_audio_processor_alloc_err:
kfree(prtd);
esa_compr_set_state(false);
return ret;
}
static int compr_free(struct snd_compr_stream *cstream)
{
struct snd_compr_runtime *runtime = cstream->runtime;
struct runtime_data *prtd = runtime->private_data;
struct snd_pcm_substream *substream;
struct snd_soc_dai *cpu_dai;
struct snd_soc_dai *codec_dai;
const struct snd_soc_dai_ops *cpu_dai_ops;
const struct snd_soc_dai_ops *codec_dai_ops;
unsigned long flags;
int ret;
#ifdef AUDIO_PERF
u64 playback_time, total_time = 0;
int idx;
#endif
pr_debug("%s\n", __func__);
if (!prtd) {
pr_info("compress dai has already freed.\n");
return 0;
}
substream = &prtd->substream;
cpu_dai = prtd->cpu_dai;
codec_dai = prtd->codec_dai;
cpu_dai_ops = cpu_dai->driver->ops;
codec_dai_ops = codec_dai->driver->ops;
if (atomic_read(&prtd->eos)) {
/* ALSA Framework callback to notify drain complete */
snd_compr_drain_notify(cstream);
atomic_set(&prtd->eos, 0);
pr_debug("%s Call Drain notify to wakeup\n", __func__);
}
if (atomic_read(&prtd->created)) {
spin_lock_irqsave(&prtd->lock, flags);
atomic_set(&prtd->created, 0);
prtd->exit_ack = 0;
ret = esa_compr_send_cmd(CMD_COMPR_DESTROY, prtd->ap);
if (ret) {
esa_err("%s: can't send CMD_COMPR_DESTROY (%d)\n",
__func__, ret);
spin_unlock_irqrestore(&prtd->lock, flags);
} else {
spin_unlock_irqrestore(&prtd->lock, flags);
ret = wait_event_interruptible_timeout(prtd->exit_wait,
prtd->exit_ack, 1 * HZ);
if (!ret)
pr_err("%s: CMD_DESTROY timed out!!!\n", __func__);
}
}
#ifdef CONFIG_SND_ESA_SA_EFFECT
aud_vol.ap[COMPR_DAI_MULTIMEDIA_1] = NULL;
#endif
esa_compr_set_state(false);
/* codec hw_free -> cpu hw_free ->
cpu shutdown -> codec shutdown */
if (codec_dai_ops->hw_free)
(*codec_dai_ops->hw_free)(substream, codec_dai);
if (cpu_dai_ops->hw_free)
(*cpu_dai_ops->hw_free)(substream, cpu_dai);
if (cpu_dai_ops->shutdown)
(*cpu_dai_ops->shutdown)(substream, cpu_dai);
if (codec_dai_ops->shutdown)
(*codec_dai_ops->shutdown)(substream, codec_dai);
if (substream->runtime)
kfree(substream->runtime->hw_constraints.rules);
kfree(substream->runtime);
esa_compr_close();
#ifdef AUDIO_PERF
prtd->end_time[OPEN_T] = sched_clock();
playback_time = prtd->end_time[OPEN_T] - prtd->start_time[OPEN_T];
for (idx = 0; idx < TOTAL_TIMES; idx++) {
total_time += prtd->total_time[idx];
}
pr_debug("%s: measure the audio waken time : %llu\n", __func__,
total_time);
pr_debug("%s: may be the ap sleep time : (%llu/%llu)\n", __func__,
playback_time - total_time, playback_time);
#endif
kfree(prtd->ap);
kfree(prtd);
return 0;
}
static int compr_set_params(struct snd_compr_stream *cstream,
struct snd_compr_params *params)
{
struct snd_compr_runtime *runtime = cstream->runtime;
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
struct runtime_data *prtd = runtime->private_data;
unsigned long flags;
int ret;
pr_debug("%s\n", __func__);
compr_config_hw_params(&prtd->hw_params, params);
pr_debug("%s, cpu_dai name = %s\n",
__func__, rtd->cpu_dai->name);
/* startup -> hw_params */
ret = compr_dai_setup(prtd, rtd);
if (ret) {
pr_err("%s: could not setup compr_dai(%d)\n", __func__, ret);
return -ENXIO;
}
ret = compr_dai_prepare(prtd);
if (ret) {
pr_err("%s: compr_dai_prepare() fail(%d)\n", __func__, ret);
return -ENXIO;
}
/* COMPR set_params */
memcpy(&prtd->codec_param, params, sizeof(struct snd_compr_params));
prtd->byte_offset = 0;
prtd->app_pointer = 0;
prtd->copied_total = 0;
prtd->ap->buffer_size = runtime->fragments * runtime->fragment_size;
prtd->ap->num_channels = prtd->codec_param.codec.ch_in;
prtd->ap->sample_rate = prtd->codec_param.codec.sample_rate;
if (prtd->ap->sample_rate == 0 ||
prtd->ap->num_channels == 0) {
pr_err("%s: invalid parameters: sample(%ld), ch(%ld)\n",
__func__, prtd->ap->sample_rate,
prtd->ap->num_channels);
return -EINVAL;
}
switch (prtd->codec_param.codec.id) {
case SND_AUDIOCODEC_MP3:
prtd->ap->codec_id = COMPR_MP3;
break;
case SND_AUDIOCODEC_AAC:
prtd->ap->codec_id = COMPR_AAC;
break;
default:
pr_err("%s: unknown codec id %d\n", __func__,
prtd->codec_param.codec.id);
break;
}
ret = esa_compr_set_param(prtd->ap, (uint8_t**)&prtd->buffer);
if (ret) {
pr_err("%s: esa_compr_set_param fail(%d)\n", __func__, ret);
return ret;
}
spin_lock_irqsave(&prtd->lock, flags);
atomic_set(&prtd->created, 1);
spin_unlock_irqrestore(&prtd->lock, flags);
pr_info("%s: sample rate:%ld, channels:%ld\n", __func__,
prtd->ap->sample_rate, prtd->ap->num_channels);
return 0;
}
static int compr_set_metadata(struct snd_compr_stream *cstream,
struct snd_compr_metadata *metadata)
{
pr_debug("%s\n", __func__);
if (!metadata || !cstream)
return -EINVAL;
if (metadata->key == SNDRV_COMPRESS_ENCODER_PADDING) {
pr_debug("%s, got encoder padding %u", __func__, metadata->value[0]);
} else if (metadata->key == SNDRV_COMPRESS_ENCODER_DELAY) {
pr_debug("%s, got encoder delay %u", __func__, metadata->value[0]);
}
return 0;
}
static int compr_trigger(struct snd_compr_stream *cstream, int cmd)
{
struct snd_compr_runtime *runtime = cstream->runtime;
struct runtime_data *prtd = runtime->private_data;
unsigned long flags;
int ret;
pr_debug("%s: trigger cmd(%d)\n", __func__, cmd);
/* platform -> codec -> cpu */
if (cstream->direction != SND_COMPRESS_PLAYBACK) {
pr_err("%s: Unsupported stream type\n", __func__);
return -EINVAL;
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pr_info("%s: SNDRV_PCM_TRIGGER_PAUSE_PUSH\n", __func__);
spin_lock_irqsave(&prtd->lock, flags);
ret = esa_compr_send_cmd(CMD_COMPR_PAUSE, prtd->ap);
if (ret) {
pr_err("%s: pause cmd failed(%d)\n", __func__,
ret);
spin_unlock_irqrestore(&prtd->lock, flags);
return ret;
}
spin_unlock_irqrestore(&prtd->lock, flags);
atomic_set(&prtd->start, 0);
break;
case SNDRV_PCM_TRIGGER_STOP:
pr_info("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__);
spin_lock_irqsave(&prtd->lock, flags);
if (atomic_read(&prtd->eos)) {
/* ALSA Framework callback to notify drain complete */
snd_compr_drain_notify(cstream);
atomic_set(&prtd->eos, 0);
pr_debug("%s: interrupt drain and eos wait queues", __func__);
}
pr_debug("CMD_STOP\n");
prtd->stop_ack = 0;
ret = esa_compr_send_cmd(CMD_COMPR_STOP, prtd->ap);
if (ret) {
pr_err("%s: stop cmd failed (%d)\n",
__func__, ret);
spin_unlock_irqrestore(&prtd->lock, flags);
return ret;
}
spin_unlock_irqrestore(&prtd->lock, flags);
ret = wait_event_interruptible_timeout(prtd->flush_wait,
prtd->stop_ack, 1 * HZ);
if (!ret) {
pr_err("CMD_STOP cmd timeout(%d)\n", ret);
ret = -ETIMEDOUT;
} else
ret = 0;
ret = compr_dai_cmd(prtd, cmd);
if (ret) {
pr_err("%s: compr_dai_cmd fail(%d)\n", __func__, ret);
return ret;
}
atomic_set(&prtd->start, 0);
/* reset */
prtd->stop_ack = 0;
prtd->byte_offset = 0;
prtd->app_pointer = 0;
prtd->copied_total = 0;
prtd->received_total = 0;
break;
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (SNDRV_PCM_TRIGGER_START == cmd)
pr_info("%s: SNDRV_PCM_TRIGGER_START\n", __func__);
else if (SNDRV_PCM_TRIGGER_PAUSE_RELEASE == cmd)
pr_info("%s: SNDRV_PCM_TRIGGER_PAUSE_RELEASE\n", __func__);
ret = compr_dai_cmd(prtd, cmd);
if (ret) {
pr_err("%s: compr_dai_cmd fail(%d)\n", __func__, ret);
return ret;
}
atomic_set(&prtd->start, 1);
spin_lock_irqsave(&prtd->lock, flags);
ret = esa_compr_send_cmd(CMD_COMPR_START, prtd->ap);
if (ret) {
pr_err("%s: start cmd failed\n", __func__);
spin_unlock_irqrestore(&prtd->lock, flags);
return ret;
}
spin_unlock_irqrestore(&prtd->lock, flags);
break;
case SND_COMPR_TRIGGER_NEXT_TRACK:
pr_info("%s: SND_COMPR_TRIGGER_NEXT_TRACK\n", __func__);
break;
case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
pr_info("%s: SND_COMPR_TRIGGER_PARTIAL_DRAIN\n", __func__);
case SND_COMPR_TRIGGER_DRAIN:
if (SND_COMPR_TRIGGER_DRAIN == cmd)
pr_info("%s: SND_COMPR_TRIGGER_DRAIN\n", __func__);
/* Make sure all the data is sent to F/W before sending EOS */
spin_lock_irqsave(&prtd->lock, flags);
#ifdef AUDIO_PERF
prtd->start_time[DRAIN_T] = sched_clock();
#endif
if (!atomic_read(&prtd->start)) {
pr_err("%s: stream is not in started state\n",
__func__);
ret = -EPERM;
spin_unlock_irqrestore(&prtd->lock, flags);
break;
}
atomic_set(&prtd->eos, 1);
pr_debug("%s: CMD_EOS\n", __func__);
ret = esa_compr_send_cmd(CMD_COMPR_EOS, prtd->ap);
if (ret) {
pr_err("%s: can't send eos (%d)\n", __func__, ret);
spin_unlock_irqrestore(&prtd->lock, flags);
return ret;
}
spin_unlock_irqrestore(&prtd->lock, flags);
#ifdef AUDIO_PERF
prtd->end_time[DRAIN_T] = sched_clock();
prtd->total_time[DRAIN_T] +=
prtd->end_time[DRAIN_T] - prtd->start_time[DRAIN_T];
#endif
pr_info("%s: Out of %s Drain", __func__,
(cmd == SND_COMPR_TRIGGER_DRAIN ? "Full" : "Partial"));
break;
default:
break;
}
return 0;
}
static int compr_pointer(struct snd_compr_stream *cstream,
struct snd_compr_tstamp *tstamp)
{
struct snd_compr_runtime *runtime = cstream->runtime;
struct runtime_data *prtd = runtime->private_data;
struct snd_compr_tstamp timestamp;
unsigned long flags;
int pcm_size, bytes_available;
int num_channel;
pr_debug("%s\n", __func__);
#ifdef AUDIO_PERF
prtd->start_time[POINTER_T] = sched_clock();
#endif
memset(&timestamp, 0x0, sizeof(struct snd_compr_tstamp));
spin_lock_irqsave(&prtd->lock, flags);
timestamp.sampling_rate = prtd->ap->sample_rate;
timestamp.byte_offset = prtd->byte_offset;
timestamp.copied_total = prtd->copied_total;
pcm_size = esa_compr_pcm_size();
/* set the number of channels */
if (prtd->ap->num_channels == 1 || prtd->ap->num_channels == 2)
num_channel = 1;
else if (prtd->ap->num_channels == 3)
num_channel = 2;
else
num_channel = 2;
spin_unlock_irqrestore(&prtd->lock, flags);
if (pcm_size) {
bytes_available = prtd->received_total - prtd->copied_total;
timestamp.pcm_io_frames = (snd_pcm_uframes_t)div64_u64(pcm_size,
2 * num_channel);
pr_debug("%s: pcm_size(%u), frame_count(%u), copied_total(%llu), \
free_size(%llu)\n", __func__, pcm_size,
timestamp.pcm_io_frames, prtd->copied_total,
runtime->buffer_size - bytes_available);
}
memcpy(tstamp, &timestamp, sizeof(struct snd_compr_tstamp));
#ifdef AUDIO_PERF
prtd->end_time[POINTER_T] = sched_clock();
prtd->total_time[POINTER_T] +=
prtd->end_time[POINTER_T] - prtd->start_time[POINTER_T];
#endif
return 0;
}
static int compr_copy(struct snd_compr_stream *cstream, char __user* buf, size_t bytes)
{
struct snd_compr_runtime *runtime = cstream->runtime;
struct runtime_data *prtd = runtime->private_data;
u64 bytes_available;
unsigned long flags;
unsigned long copy;
void *dstn;
int ret;
pr_debug("%s\n", __func__);
#ifdef AUDIO_PERF
prtd->start_time[WRITE_T] = sched_clock();
#endif
if (!prtd->buffer) {
pr_err("%s: Buffer is not allocated yet ??", __func__);
return -ENOMEM;
}
/* check the free area */
if (bytes <= 0) {
pr_err("%s: Buffer size is zero(%ld)\n", __func__, bytes);
return 0;
}
pr_debug("copying %ld at %lld\n",
(unsigned long)bytes, prtd->app_pointer);
dstn = prtd->buffer + prtd->app_pointer;
if (bytes < runtime->buffer_size - prtd->app_pointer) {
if (copy_from_user(dstn, buf, bytes))
return -EFAULT;
prtd->app_pointer += bytes;
} else {
copy = runtime->buffer_size - prtd->app_pointer;
if (copy_from_user(dstn, buf, copy))
return -EFAULT;
if (copy_from_user(prtd->buffer, buf + copy, bytes - copy))
return -EFAULT;
prtd->app_pointer = bytes - copy;
}
/*
* since the available bytes fits fragment_size, copy the data right away
*/
spin_lock_irqsave(&prtd->lock, flags);
prtd->received_total += bytes;
bytes_available = prtd->received_total - prtd->copied_total;
spin_unlock_irqrestore(&prtd->lock, flags);
pr_debug("%s: bytes_received(%llu), free_size(%llu)\n",
__func__, prtd->received_total,
runtime->buffer_size - bytes_available);
/* get the bytes to write */
if (bytes_available > 0) {
//TODO: issue: unknown mp3 fragment should be checked
#if 0
u64 pointer = div64_u64(prtd->copied_total,
runtime->buffer_size);
pointer = prtd->copied_total - (pointer * runtime->buffer_size);
pr_info("%s: bytes to write offset in buffer(%d/%llu)\n",
__func__, prtd->byte_offset, prtd->app_pointer);
pr_info("%s: [%2llx][%2llx][%2llx][%2llx] (%d)\n",
__func__, (u64)(((char*)prtd->buffer)[pointer]),
(u64)(((char*)prtd->buffer)[pointer + 1]),
(u64)(((char*)prtd->buffer)[pointer + 2]),
(u64)(((char*)prtd->buffer)[pointer + 3]),
bytes);
#endif
pr_debug("%s: needs to be copied to the buffer = %llu\n",
__func__, bytes_available);
ret = esa_compr_send_buffer(bytes, prtd->ap);
if (ret) {
pr_err("%s: can't send buffer %ld bytes (%d)",
__func__, bytes, ret);
return -EFAULT;
}
}
#ifdef AUDIO_PERF
prtd->end_time[WRITE_T] = sched_clock();
prtd->total_time[WRITE_T] +=
prtd->end_time[WRITE_T] - prtd->start_time[WRITE_T];
#endif
return bytes;
}
static int compr_get_caps(struct snd_compr_stream *cstream,
struct snd_compr_caps *caps)
{
struct snd_compr_runtime *runtime = cstream->runtime;
struct runtime_data *prtd = runtime->private_data;
pr_debug("%s\n", __func__);
memcpy(caps, prtd->compr_cap, sizeof(struct snd_compr_caps));
return 0;
}
static int compr_get_codec_caps(struct snd_compr_stream *cstream,
struct snd_compr_codec_caps *codec)
{
pr_debug("%s\n", __func__);
return 0;
}
static struct snd_compr_ops compr_ops = {
.open = compr_open,
.free = compr_free,
.set_params = compr_set_params,
.set_metadata = compr_set_metadata,
.trigger = compr_trigger,
.pointer = compr_pointer,
.copy = compr_copy,
.get_caps = compr_get_caps,
.get_codec_caps = compr_get_codec_caps,
};
static struct snd_soc_platform_driver samsung_compr_platform = {
.compr_ops = &compr_ops,
};
int asoc_compr_platform_register(struct device *dev)
{
return snd_soc_register_platform(dev, &samsung_compr_platform);
}
EXPORT_SYMBOL_GPL(asoc_compr_platform_register);
void asoc_compr_platform_unregister(struct device *dev)
{
snd_soc_unregister_platform(dev);
}
EXPORT_SYMBOL_GPL(asoc_compr_platform_unregister);
MODULE_AUTHOR("Yeongman Seo, <yman.seo@samsung.com>");
MODULE_AUTHOR("Taeho Lee <taeho07.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC Compress Driver");
MODULE_LICENSE("GPL");

18
sound/soc/samsung/compr.h Normal file
View file

@ -0,0 +1,18 @@
/*
* compr.h --
*
* 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.
*
* ALSA COMPRESS interface for the Samsung SoC
*/
#ifndef _SAMSUNG_AUDIO_COMPR_H
#define _SAMSUNG_AUDIO_COMPR_H
int asoc_compr_platform_register(struct device *dev);
void asoc_compr_platform_unregister(struct device *dev);
#endif

View file

@ -0,0 +1,80 @@
/*
* ALSA SoC CP dummy cpu dai driver
*
* This driver provides one dummy dai.
*
* Copyright (c) 2014 Samsung Electronics
* http://www.samsungsemi.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/of.h>
#include <sound/soc.h>
static struct snd_soc_dai_driver cp_dummy_dai_drv = {
.name = "espresso voice call",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
};
static const struct snd_soc_component_driver cp_dummy_i2s_component = {
.name = "cp-dummy-i2s",
};
static int dummy_cpu_probe(struct platform_device *pdev)
{
snd_soc_register_component(&pdev->dev, &cp_dummy_i2s_component,
&cp_dummy_dai_drv, 1);
return 0;
}
static int dummy_cpu_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
return 0;
}
static const struct of_device_id dummy_cpu_of_match[] = {
{ .compatible = "samsung,cp_dummy", },
{},
};
MODULE_DEVICE_TABLE(of, dummy_cpu_of_match);
static struct platform_driver dummy_cpu_driver = {
.probe = dummy_cpu_probe,
.remove = dummy_cpu_remove,
.driver = {
.name = "cp-dummy-i2s",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(dummy_cpu_of_match),
},
};
module_platform_driver(dummy_cpu_driver);
MODULE_AUTHOR("Hyunwoong Kim <khw0178.kim@samsung.com>");
MODULE_DESCRIPTION("Dummy dai driver");
MODULE_LICENSE("GPL");

792
sound/soc/samsung/dma.c Normal file
View file

@ -0,0 +1,792 @@
/*
* dma.c -- ALSA Soc Audio Layer
*
* (c) 2006 Wolfson Microelectronics PLC.
* Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*
* Copyright 2004-2005 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/iommu.h>
#include <linux/dma/dma-pl330.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/exynos.h>
#if 0
#include <mach/map.h>
#endif
#include "dma.h"
#ifdef CONFIG_SND_SAMSUNG_SEIREN_DMA
#include "seiren/seiren-dma.h"
#endif
#define PERIOD_MIN 4
#define ST_RUNNING (1<<0)
#define ST_OPENED (1<<1)
#define SRAM_END (0x04000000)
#define MAX_DEEPBUF_SIZE (0xA000) /* 40 KB */
static atomic_t dram_usage_cnt;
static struct snd_dma_buffer *sram_rx_buf = NULL;
static struct snd_dma_buffer *dram_uhqa_tx_buf = NULL;
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 1,
.channels_max = 8,
.buffer_bytes_max = 256*1024,
.period_bytes_min = 128,
.period_bytes_max = 64*1024,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
struct runtime_data {
spinlock_t lock;
int state;
unsigned int dma_loaded;
unsigned int dma_period;
dma_addr_t dma_start;
dma_addr_t dma_pos;
dma_addr_t dma_end;
struct s3c_dma_params *params;
struct snd_pcm_hardware hw;
bool dram_used;
dma_addr_t irq_pos;
u32 irq_cnt;
};
#ifdef CONFIG_SND_SAMSUNG_IOMMU
struct dma_iova {
dma_addr_t iova;
dma_addr_t pa;
unsigned char *va;
struct list_head node;
};
static LIST_HEAD(iova_list);
#endif
static void audio_buffdone(void *data);
/* check_adma_status
*
* ADMA status is checked for AP Power mode.
* return 1 : ADMA use dram area and it is running.
* return 0 : ADMA has a fine condition to enter Low Power Mode.
*/
int check_adma_status(void)
{
return atomic_read(&dram_usage_cnt) ? 1 : 0;
}
/* dma_enqueue
*
* place a dma buffer onto the queue for the dma system
* to handle.
*/
static void dma_enqueue(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
dma_addr_t pos = prtd->dma_pos;
unsigned int limit;
struct samsung_dma_prep dma_info;
pr_info("Entered %s\n", __func__);
limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
pr_debug("%s: loaded %d, limit %d\n",
__func__, prtd->dma_loaded, limit);
dma_info.cap = prtd->params->esa_dma ? DMA_CYCLIC :
(samsung_dma_has_circular() ? DMA_CYCLIC : DMA_SLAVE);
dma_info.direction =
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM);
dma_info.fp = audio_buffdone;
dma_info.fp_param = substream;
dma_info.period = prtd->dma_period;
dma_info.len = prtd->dma_period*limit;
if (prtd->params->esa_dma || samsung_dma_has_infiniteloop()) {
dma_info.buf = prtd->dma_pos;
dma_info.infiniteloop = limit;
prtd->params->ops->prepare(prtd->params->ch, &dma_info);
} else {
dma_info.infiniteloop = 0;
while (prtd->dma_loaded < limit) {
pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
if ((pos + dma_info.period) > prtd->dma_end) {
dma_info.period = prtd->dma_end - pos;
pr_debug("%s: corrected dma len %ld\n",
__func__, dma_info.period);
}
dma_info.buf = pos;
prtd->params->ops->prepare(prtd->params->ch, &dma_info);
prtd->dma_loaded++;
pos += prtd->dma_period;
if (pos >= prtd->dma_end)
pos = prtd->dma_start;
}
prtd->dma_pos = pos;
}
}
static void audio_buffdone(void *data)
{
struct snd_pcm_substream *substream = data;
struct runtime_data *prtd;
dma_addr_t src, dst, pos;
pr_debug("Entered %s\n", __func__);
if (!substream)
return;
prtd = substream->runtime->private_data;
if (prtd->state & ST_RUNNING) {
prtd->params->ops->getposition(prtd->params->ch, &src, &dst);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
pos = dst - prtd->dma_start;
else
pos = src - prtd->dma_start;
prtd->irq_cnt++;
prtd->irq_pos = pos;
pos /= prtd->dma_period;
pos = prtd->dma_start + (pos * prtd->dma_period);
if (pos >= prtd->dma_end)
pos = prtd->dma_start;
prtd->dma_pos = pos;
snd_pcm_period_elapsed(substream);
if (!prtd->params->esa_dma && !samsung_dma_has_circular()) {
spin_lock(&prtd->lock);
prtd->dma_loaded--;
if (!samsung_dma_has_infiniteloop())
dma_enqueue(substream);
spin_unlock(&prtd->lock);
}
}
}
static int dma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
unsigned long totbytes = params_buffer_bytes(params);
struct s3c_dma_params *dma =
snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
struct samsung_dma_req req;
struct samsung_dma_config config;
pr_debug("Entered %s\n", __func__);
/* return if this is a bufferless transfer e.g.
* codec <--> BT codec or GSM modem -- lg FIXME */
if (!dma)
return 0;
/* this may get called several times by oss emulation
* with different params -HW */
if (prtd->params == NULL) {
/* prepare DMA */
prtd->params = dma;
pr_debug("params %p, client %p, channel %d\n", prtd->params,
prtd->params->client, prtd->params->channel);
if (prtd->params->esa_dma) {
prtd->params->ops = samsung_dma_get_ops();
req.cap = DMA_CYCLIC;
} else {
pr_info("No esa_dma %s\n", __func__);
prtd->params->ops = samsung_dma_get_ops();
req.cap = (samsung_dma_has_circular() ?
DMA_CYCLIC : DMA_SLAVE);
}
req.client = prtd->params->client;
config.direction =
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM);
config.width = prtd->params->dma_size;
/* config.maxburst = 1; */
config.fifo = prtd->params->dma_addr;
if (prtd->params->compr_dma) {
pr_info("%s: %d\n", __func__, __LINE__);
prtd->params->ch = prtd->params->ops->request(
prtd->params->channel, &req,
prtd->params->sec_dma_dev,
prtd->params->ch_name);
} else {
pr_info("%s: %d\n", __func__, __LINE__);
prtd->params->ch = prtd->params->ops->request(
prtd->params->channel, &req, rtd->cpu_dai->dev,
prtd->params->ch_name);
}
pr_info("dma_request: ch %d, req %p, dev %p, ch_name [%s]\n",
prtd->params->channel, &req, rtd->cpu_dai->dev,
prtd->params->ch_name);
prtd->params->ops->config(prtd->params->ch, &config);
}
if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) &&
sram_rx_buf && (totbytes <= sram_rx_buf->bytes))
snd_pcm_set_runtime_buffer(substream, sram_rx_buf);
else if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) &&
(totbytes > MAX_DEEPBUF_SIZE) && dram_uhqa_tx_buf)
snd_pcm_set_runtime_buffer(substream, dram_uhqa_tx_buf);
else
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = totbytes;
spin_lock_irq(&prtd->lock);
prtd->dma_loaded = 0;
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 + totbytes;
prtd->dram_used = runtime->dma_addr < SRAM_END ? false : true;
while ((totbytes / prtd->dma_period) < PERIOD_MIN)
prtd->dma_period >>= 1;
spin_unlock_irq(&prtd->lock);
pr_info("ADMA:%s:DmaAddr=@%x Total=%d PrdSz=%d(%d) #Prds=%d dma_area=0x%p\n",
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? "P" : "C",
(u32)prtd->dma_start, (u32)runtime->dma_bytes,
params_period_bytes(params),(u32) prtd->dma_period,
params_periods(params), runtime->dma_area);
return 0;
}
static int dma_hw_free(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
pr_debug("Entered %s\n", __func__);
snd_pcm_set_runtime_buffer(substream, NULL);
if (prtd->params) {
prtd->params->ops->flush(prtd->params->ch);
prtd->params->ops->release(prtd->params->ch,
prtd->params->client);
prtd->params = NULL;
}
return 0;
}
static int dma_prepare(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_info("Entered %s\n", __func__);
/* return if this is a bufferless transfer e.g.
* codec <--> BT codec or GSM modem -- lg FIXME */
if (!prtd->params)
return 0;
/* flush the DMA channel */
prtd->params->ops->flush(prtd->params->ch);
prtd->dma_loaded = 0;
prtd->dma_pos = prtd->dma_start;
prtd->irq_pos = prtd->dma_start;
prtd->irq_cnt = 0;
/* enqueue dma buffers */
dma_enqueue(substream);
return ret;
}
static int dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_debug("Entered %s\n", __func__);
spin_lock(&prtd->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
prtd->state |= ST_RUNNING;
lpass_dma_enable(true);
prtd->params->ops->trigger(prtd->params->ch);
if (prtd->dram_used)
atomic_inc(&dram_usage_cnt);
break;
case SNDRV_PCM_TRIGGER_STOP:
prtd->state &= ~ST_RUNNING;
prtd->params->ops->stop(prtd->params->ch);
lpass_dma_enable(false);
if (prtd->dram_used)
atomic_dec(&dram_usage_cnt);
break;
default:
ret = -EINVAL;
break;
}
spin_unlock(&prtd->lock);
return ret;
}
static snd_pcm_uframes_t dma_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
unsigned long res;
pr_debug("Entered %s\n", __func__);
res = prtd->dma_pos - prtd->dma_start;
pr_debug("Pointer offset: %lu\n", res);
/* we seem to be getting the odd error from the pcm library due
* to out-of-bounds pointers. this is maybe due to the dma engine
* not having loaded the new values for the channel before being
* called... (todo - fix )
*/
if (res >= snd_pcm_lib_buffer_bytes(substream)) {
if (res == snd_pcm_lib_buffer_bytes(substream))
res = 0;
}
return bytes_to_frames(substream->runtime, res);
}
static int dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd;
pr_debug("Entered %s\n", __func__);
prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
if (prtd == NULL)
return -ENOMEM;
spin_lock_init(&prtd->lock);
memcpy(&prtd->hw, &dma_hardware, sizeof(struct snd_pcm_hardware));
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
runtime->private_data = prtd;
snd_soc_set_runtime_hwparams(substream, &prtd->hw);
pr_info("%s: prtd = %p\n", __func__, prtd);
return 0;
}
static int dma_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
pr_debug("Entered %s\n", __func__);
if (!prtd)
pr_debug("dma_close called with prtd == NULL\n");
pr_info("%s: prtd = %p, irq_cnt %u\n",
__func__, prtd, prtd->irq_cnt);
kfree(prtd);
return 0;
}
static int dma_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
dma_addr_t dma_pa = runtime->dma_addr;
#ifdef CONFIG_SND_SAMSUNG_IOMMU
struct dma_iova *di;
#endif
pr_debug("Entered %s\n", __func__);
#ifdef CONFIG_SND_SAMSUNG_IOMMU
list_for_each_entry(di, &iova_list, node) {
if (di->iova == runtime->dma_addr)
dma_pa = di->pa;
}
#endif
return dma_mmap_writecombine(substream->pcm->card->dev, vma,
runtime->dma_area, dma_pa,
runtime->dma_bytes);
}
static struct snd_pcm_ops pcm_dma_ops = {
.open = dma_open,
.close = dma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dma_hw_params,
.hw_free = dma_hw_free,
.prepare = dma_prepare,
.trigger = dma_trigger,
.pointer = dma_pointer,
.mmap = dma_mmap,
};
static int 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 = dma_hardware.buffer_bytes_max;
pr_debug("Entered %s\n", __func__);
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = dma_alloc_coherent(pcm->card->dev, size,
&buf->addr, GFP_KERNEL);
if (!buf->area)
return -ENOMEM;
buf->bytes = size;
return 0;
}
#ifndef CONFIG_SOC_EXYNOS7870
static const char *dma_prop_addr[2] = {
[SNDRV_PCM_STREAM_PLAYBACK] = "samsung,tx-buf",
[SNDRV_PCM_STREAM_CAPTURE] = "samsung,rx-buf"
};
static const char *dma_prop_size[2] = {
[SNDRV_PCM_STREAM_PLAYBACK] = "samsung,tx-size",
[SNDRV_PCM_STREAM_CAPTURE] = "samsung,rx-size"
};
#endif
static const char *dma_prop_iommu[2] = {
[SNDRV_PCM_STREAM_PLAYBACK] = "samsung,tx-iommu",
[SNDRV_PCM_STREAM_CAPTURE] = "samsung,rx-iommu"
};
static int preallocate_dma_buffer_of(struct snd_pcm *pcm, int stream,
struct device_node *np)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
size_t size;
#ifndef CONFIG_SOC_EXYNOS7870
dma_addr_t dma_addr;
u32 val;
#endif
#ifdef CONFIG_SND_SAMSUNG_IOMMU
struct iommu_domain *domain = lpass_get_iommu_domain();
dma_addr_t dma_buf_pa;
struct dma_iova *di, *di_uhqa;
int ret;
#endif
pr_debug("Entered %s\n", __func__);
#ifndef CONFIG_SOC_EXYNOS7870
if (of_property_read_u32(np, dma_prop_addr[stream], &val))
return -ENOMEM;
dma_addr = (dma_addr_t)val;
if (of_property_read_u32(np, dma_prop_size[stream], &val))
return -ENOMEM;
size = (size_t)val;
buf->addr = dma_addr;
#else
size = dma_hardware.buffer_bytes_max;
buf->area = dma_alloc_coherent(pcm->card->dev, size,
&buf->addr, GFP_KERNEL);
#endif
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
/*
* With LPC Recording, Platform DAI driver should provide USER with SRAM
* Buffer if recording buffer size is smaller than sram size.
* Below code parses information of REC_SRAM.
*/
if (!sram_rx_buf) {
if (of_find_property(np, "samsung,rx-sram", NULL)) {
u32 sram_info[2];
sram_rx_buf = devm_kzalloc(pcm->card->dev,
sizeof(*sram_rx_buf), GFP_KERNEL);
if (!sram_rx_buf) {
pr_err("Failed to allocate rx-sram buffer = %pa\n",
sram_rx_buf);
return -ENOMEM;
}
sram_rx_buf->dev.type = SNDRV_DMA_TYPE_DEV;
sram_rx_buf->dev.dev = pcm->card->dev;
/* Array Value : RX-SRAM Base Address RX-SRAM Size*/
of_property_read_u32_array(np, "samsung,rx-sram",
sram_info, 2);
sram_rx_buf->addr = (dma_addr_t)sram_info[0];
sram_rx_buf->bytes = (size_t)sram_info[1];
if (!sram_rx_buf->addr || sram_rx_buf->addr > SRAM_END) {
pr_err("Failed to find rx-sram addr = %pa\n",
&sram_info[0]);
return -ENOMEM;
}
if (sram_rx_buf->bytes == 0) {
pr_err("Failed to find rx-sram size = %x\n",
sram_info[1]);
return -EINVAL;
}
sram_rx_buf->area = ioremap(sram_rx_buf->addr, sram_rx_buf->bytes);
if (!sram_rx_buf->area) {
pr_info("Failed to map RX-SRAM into kernel virtual\n");
return -ENOMEM;
}
pr_info("Audio RX-SRAM Information, pa = %pa, size = %zx, kva = %p\n",
&sram_rx_buf->addr, sram_rx_buf->bytes, sram_rx_buf->area);
}
}
#ifdef CONFIG_SND_SAMSUNG_IOMMU
if (of_find_property(np, dma_prop_iommu[stream], NULL)) {
di = devm_kzalloc(pcm->card->dev,
sizeof(struct dma_iova), GFP_KERNEL);
if (!di)
return -ENOMEM;
buf->area = dma_alloc_coherent(pcm->card->dev, size,
&dma_buf_pa, GFP_KERNEL);
if (!buf->area)
return -ENOMEM;
ret = iommu_map(domain, dma_addr, dma_buf_pa, size, 0);
if (ret) {
dma_free_coherent(pcm->card->dev, size,
buf->area, dma_buf_pa);
pr_err("%s: Failed to iommu_map: %d\n", __func__, ret);
return -ENOMEM;
}
di->iova = buf->addr;
di->pa = dma_buf_pa;
di->va = buf->area;
list_add(&di->node, &iova_list);
pr_info("%s: DmaAddr-iommu %08X dma_buf_pa %08X\n",
__func__, (u32)dma_addr, (u32)dma_buf_pa);
} else {
buf->area = ioremap(buf->addr, size);
}
/*
* With UHQA Playback, Platform DAI driver should provide USER with DRAM
* Buffer if UHQA buffer size is bigger than MAX_DEEPBUF_SIZE(40KB).
*/
if (!dram_uhqa_tx_buf) {
if (of_find_property(np, "samsung,tx-uhqa-buf", NULL)) {
u32 dram_info[2];
phys_addr_t tx_uhqa_buf_pa = 0;
dram_uhqa_tx_buf = devm_kzalloc(pcm->card->dev,
sizeof(*dram_uhqa_tx_buf), GFP_KERNEL);
if (!dram_uhqa_tx_buf) {
pr_err("Failed to allocate dram-tx-uhqa buffer = %pa\n",
dram_uhqa_tx_buf);
return -ENOMEM;
}
dram_uhqa_tx_buf->dev.type = SNDRV_DMA_TYPE_DEV;
dram_uhqa_tx_buf->dev.dev = pcm->card->dev;
/* Array Value : TX-UHQA DVA Base Address Size*/
of_property_read_u32_array(np, "samsung,tx-uhqa-buf",
dram_info, 2);
dram_uhqa_tx_buf->addr = (dma_addr_t)dram_info[0];
dram_uhqa_tx_buf->bytes = (size_t)dram_info[1];
if (!dram_uhqa_tx_buf->addr || !dram_uhqa_tx_buf->bytes) {
pr_err("Failed to find tx-uhqa-buf information\n");
return -ENOMEM;
}
di_uhqa = devm_kzalloc(pcm->card->dev,
sizeof(struct dma_iova), GFP_KERNEL);
if (!di_uhqa)
return -ENOMEM;
dram_uhqa_tx_buf->area = dma_alloc_coherent(pcm->card->dev,
dram_uhqa_tx_buf->bytes,
&tx_uhqa_buf_pa, GFP_KERNEL);
if (!dram_uhqa_tx_buf->area)
return -ENOMEM;
ret = iommu_map(domain, dram_uhqa_tx_buf->addr,
tx_uhqa_buf_pa, dram_uhqa_tx_buf->bytes, 0);
if (ret) {
dma_free_coherent(pcm->card->dev, size,
dram_uhqa_tx_buf->area, tx_uhqa_buf_pa);
pr_err("%s: Failed to iommu_map: %d\n", __func__, ret);
return -ENOMEM;
}
di_uhqa->iova = dram_uhqa_tx_buf->addr;
di_uhqa->pa = tx_uhqa_buf_pa;
di_uhqa->va = dram_uhqa_tx_buf->area;
list_add(&di_uhqa->node, &iova_list);
pr_info("Audio TX-UHQA-BUF Information, pa = %pa, size = %zx, kva = %p\n",
&dram_uhqa_tx_buf->addr, dram_uhqa_tx_buf->bytes,
dram_uhqa_tx_buf->area);
}
}
#else
if (of_find_property(np, dma_prop_iommu[stream], NULL))
return -ENOMEM;
else
#ifndef CONFIG_SOC_EXYNOS7870
buf->area = ioremap(buf->addr, size);
#endif
#endif
if (!buf->area)
return -ENOMEM;
buf->bytes = size;
return 0;
}
static void dma_free_dma_buffers(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
struct runtime_data *prtd;
int stream;
pr_debug("Entered %s\n", __func__);
for (stream = 0; stream < 2; stream++) {
substream = pcm->streams[stream].substream;
if (!substream)
continue;
buf = &substream->dma_buffer;
if (!buf->area)
continue;
prtd = substream->runtime->private_data;
if (prtd->dram_used) {
dma_free_coherent(pcm->card->dev, buf->bytes,
buf->area, buf->addr);
} else {
iounmap(buf->area);
}
buf->area = NULL;
}
}
static u64 dma_mask = DMA_BIT_MASK(32);
static int dma_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
struct device_node *np = rtd->cpu_dai->dev->of_node;
int ret = 0;
pr_debug("Entered %s\n", __func__);
if (!card->dev->dma_mask)
card->dev->dma_mask = &dma_mask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = 0;
if (np)
ret = preallocate_dma_buffer_of(pcm,
SNDRV_PCM_STREAM_PLAYBACK, np);
if (ret)
ret = preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
if (ret)
goto out;
}
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
ret = 0;
if (np)
ret = preallocate_dma_buffer_of(pcm,
SNDRV_PCM_STREAM_CAPTURE, np);
if (ret)
ret = preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_CAPTURE);
if (ret)
goto out;
}
out:
return ret;
}
static struct snd_soc_platform_driver samsung_asoc_platform = {
.ops = &pcm_dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
int asoc_dma_platform_register(struct device *dev)
{
return snd_soc_register_platform(dev, &samsung_asoc_platform);
}
EXPORT_SYMBOL_GPL(asoc_dma_platform_register);
void asoc_dma_platform_unregister(struct device *dev)
{
snd_soc_unregister_platform(dev);
}
EXPORT_SYMBOL_GPL(asoc_dma_platform_unregister);
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("Samsung ASoC DMA Driver");
MODULE_LICENSE("GPL");

35
sound/soc/samsung/dma.h Normal file
View file

@ -0,0 +1,35 @@
/*
* dma.h --
*
* 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.
*
* ALSA PCM interface for the Samsung SoC
*/
#ifndef _S3C_AUDIO_H
#define _S3C_AUDIO_H
struct s3c_dma_params {
struct s3c2410_dma_client *client; /* stream identifier */
int channel; /* Channel ID */
dma_addr_t dma_addr;
int dma_size; /* Size of the DMA transfer */
#ifdef CONFIG_ARM64
unsigned long ch;
#else
unsigned ch;
#endif
struct samsung_dma_ops *ops;
struct device *sec_dma_dev; /* stream identifier */
char *ch_name;
bool esa_dma;
bool compr_dma;
};
int asoc_dma_platform_register(struct device *dev);
void asoc_dma_platform_unregister(struct device *dev);
#endif

View file

@ -0,0 +1,81 @@
/*
* dmaengine.c - Samsung dmaengine wrapper
*
* Author: Mark Brown <broonie@linaro.org>
* Copyright 2013 Linaro
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/amba/pl08x.h>
#include <linux/platform_data/dma-s3c24xx.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/dmaengine_pcm.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "dma.h"
#ifdef CONFIG_ARCH_S3C64XX
#define filter_fn pl08x_filter_id
#elif defined(CONFIG_ARCH_S3C24XX)
#define filter_fn s3c24xx_dma_filter
#else
#define filter_fn NULL
#endif
static const struct snd_dmaengine_pcm_config samsung_dmaengine_pcm_config = {
.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
.compat_filter_fn = filter_fn,
};
void samsung_asoc_init_dma_data(struct snd_soc_dai *dai,
struct s3c_dma_params *playback,
struct s3c_dma_params *capture)
{
struct snd_dmaengine_dai_dma_data *playback_data = NULL;
struct snd_dmaengine_dai_dma_data *capture_data = NULL;
if (playback) {
playback_data = &playback->dma_data;
playback_data->filter_data = (void *)playback->channel;
playback_data->chan_name = playback->ch_name;
playback_data->addr = playback->dma_addr;
playback_data->addr_width = playback->dma_size;
}
if (capture) {
capture_data = &capture->dma_data;
capture_data->filter_data = (void *)capture->channel;
capture_data->chan_name = capture->ch_name;
capture_data->addr = capture->dma_addr;
capture_data->addr_width = capture->dma_size;
}
snd_soc_dai_init_dma_data(dai, playback_data, capture_data);
}
EXPORT_SYMBOL_GPL(samsung_asoc_init_dma_data);
int samsung_asoc_dma_platform_register(struct device *dev)
{
return devm_snd_dmaengine_pcm_register(dev,
&samsung_dmaengine_pcm_config,
SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME |
SND_DMAENGINE_PCM_FLAG_COMPAT);
}
EXPORT_SYMBOL_GPL(samsung_asoc_dma_platform_register);
MODULE_AUTHOR("Mark Brown <broonie@linaro.org>");
MODULE_DESCRIPTION("Samsung dmaengine ASoC driver");
MODULE_LICENSE("GPL");

566
sound/soc/samsung/eax-dai.c Normal file
View file

@ -0,0 +1,566 @@
/* sound/soc/samsung/eax-dai.c
*
* Exynos Audio Mixer driver
*
* Copyright (c) 2014 Samsung Electronics Co. Ltd.
* Yeongman Seo <yman.seo@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/serio.h>
#include <linux/time.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/uaccess.h>
#include <linux/pm_runtime.h>
#include <linux/iommu.h>
#include <linux/dma-mapping.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/firmware.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/exynos.h>
#include "lpass.h"
#include "dma.h"
#include "eax.h"
#define EAX_CH_MAX 8
#define EAX_NAME_MAX PLATFORM_NAME_SIZE
#define EAX_RATES SNDRV_PCM_RATE_8000_192000
#define EAX_FMTS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
static struct eax_info {
struct platform_device *pdev;
struct mutex mutex;
spinlock_t lock;
int ch_max;
bool master;
struct snd_soc_dai *master_dai;
const struct snd_soc_dai_ops *master_dai_ops;
int (*master_dai_suspend)(struct snd_soc_dai *dai);
int (*master_dai_resume)(struct snd_soc_dai *dai);
} ei;
struct ch_info {
char name[EAX_NAME_MAX];
struct platform_device *pdev;
struct device *dev_master;
struct s3c_dma_params *dma_params;
struct snd_soc_dai_driver dai_drv;
bool opened;
bool running;
struct list_head node;
};
static LIST_HEAD(ch_list);
static DECLARE_WAIT_QUEUE_HEAD(eax_wq);
static int eax_dai_probe(struct snd_soc_dai *dai);
static int eax_dai_remove(struct snd_soc_dai *dai);
static int eax_dai_suspend(struct snd_soc_dai *dai);
static int eax_dai_resume(struct snd_soc_dai *dai);
static const struct snd_soc_dai_ops eax_dai_ops;
int eax_dev_register(struct device *dev_master, const char *name,
struct s3c_dma_params *dma_params, int ch)
{
struct ch_info *ci;
int ret, n;
if (ch > EAX_CH_MAX) {
pr_err("%s: Channel error! (max. %d)\n",
__func__, EAX_CH_MAX);
return -EINVAL;
}
eax_dma_params_register(dma_params);
for (n = 0; n < ch; n++) {
ci = kzalloc(sizeof(struct ch_info), GFP_KERNEL);
if (!ci) {
pr_err("%s: Memory alloc fails!\n", __func__);
return -ENOMEM;
}
snprintf(ci->name, EAX_NAME_MAX, "samsung-eax.%d", n);
ci->dev_master = dev_master;
ci->dma_params = dma_params;
ci->opened = false;
ci->running = false;
ci->dai_drv.name = name;
ci->dai_drv.symmetric_rates = 1;
ci->dai_drv.probe = eax_dai_probe;
ci->dai_drv.remove = eax_dai_remove;
ci->dai_drv.ops = &eax_dai_ops;
ci->dai_drv.suspend = eax_dai_suspend;
ci->dai_drv.resume = eax_dai_resume;
ci->dai_drv.playback.channels_min = 2;
ci->dai_drv.playback.channels_max = 2;
ci->dai_drv.playback.rates = EAX_RATES;
ci->dai_drv.playback.formats = EAX_FMTS;
ci->pdev = platform_device_alloc(ci->name, -1);
if (IS_ERR(ci->pdev)) {
kfree(ci);
return -ENODEV;
}
ei.ch_max++;
list_add(&ci->node, &ch_list);
platform_set_drvdata(ci->pdev, ci);
ret = platform_device_add(ci->pdev);
if (ret < 0)
return -ENODEV;
}
return 0;
}
int eax_dai_register(struct snd_soc_dai *dai,
const struct snd_soc_dai_ops *dai_ops,
int (*dai_suspend)(struct snd_soc_dai *dai),
int (*dai_resume)(struct snd_soc_dai *dai))
{
pr_debug("%s: dai %p, dai_ops %p\n", __func__, dai, dai_ops);
ei.master = true;
ei.master_dai = dai;
ei.master_dai_ops = dai_ops;
ei.master_dai_suspend = dai_suspend;
ei.master_dai_resume = dai_resume;
eax_dma_dai_register(dai);
return 0;
}
int eax_dai_unregister(void)
{
ei.master = false;
ei.master_dai = NULL;
ei.master_dai_ops = NULL;
ei.master_dai_suspend = NULL;
ei.master_dai_resume = NULL;
eax_dma_dai_unregister();
return 0;
}
static inline struct ch_info *to_info(struct snd_soc_dai *dai)
{
return snd_soc_dai_get_drvdata(dai);
}
static inline bool eax_dai_any_tx_opened(void)
{
struct ch_info *ci;
list_for_each_entry(ci, &ch_list, node) {
if (ci->opened)
return true;
}
return false;
}
static inline bool eax_dai_any_tx_running(void)
{
struct ch_info *ci;
list_for_each_entry(ci, &ch_list, node) {
if (ci->running)
return true;
}
return false;
}
static int eax_dai_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct ch_info *ci = to_info(dai);
int ret = 0;
if (!ei.master)
return -ENODEV;
spin_lock(&ei.lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
if (!eax_dai_any_tx_running())
ret = (*ei.master_dai_ops->trigger)(substream,
cmd, ei.master_dai);
ci->running = true;
break;
case SNDRV_PCM_TRIGGER_STOP:
ci->running = false;
if (!eax_dai_any_tx_running())
ret = (*ei.master_dai_ops->trigger)(substream,
cmd, ei.master_dai);
break;
default:
break;
}
spin_unlock(&ei.lock);
return ret;
}
#ifdef CONFIG_SND_SOC_I2S_1840_TDM
static int eax_dai_set_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
{
if (!ei.master)
return -ENODEV;
spin_lock(&ei.lock);
if (eax_dai_any_tx_running()) {
spin_unlock(&ei.lock);
return 0;
}
spin_unlock(&ei.lock);
return (*ei.master_dai_ops->set_tdm_slot)(ei.master_dai,
tx_mask, rx_mask, slots, slot_width);
}
#endif
static int eax_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
if (!ei.master)
return -ENODEV;
spin_lock(&ei.lock);
if (eax_dai_any_tx_running()) {
spin_unlock(&ei.lock);
return 0;
}
spin_unlock(&ei.lock);
return (*ei.master_dai_ops->hw_params)(substream, params,
ei.master_dai);
}
static int eax_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
if (!ei.master)
return -ENODEV;
spin_lock(&ei.lock);
if (eax_dai_any_tx_running()) {
spin_unlock(&ei.lock);
return 0;
}
spin_unlock(&ei.lock);
return (*ei.master_dai_ops->set_fmt)(ei.master_dai, fmt);
}
static int eax_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
{
if (!ei.master)
return -ENODEV;
spin_lock(&ei.lock);
if (eax_dai_any_tx_running()) {
spin_unlock(&ei.lock);
return 0;
}
spin_unlock(&ei.lock);
return (*ei.master_dai_ops->set_clkdiv)(ei.master_dai, div_id, div);
}
static int eax_dai_set_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int rfs, int dir)
{
if (!ei.master)
return -ENODEV;
spin_lock(&ei.lock);
if (eax_dai_any_tx_running()) {
spin_unlock(&ei.lock);
return 0;
}
spin_unlock(&ei.lock);
return (*ei.master_dai_ops->set_sysclk)(ei.master_dai,
clk_id, rfs, dir);
}
static int eax_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct ch_info *ci = to_info(dai);
int ret = 0;
if (!ei.master)
return -ENODEV;
lpass_add_stream();
mutex_lock(&ei.mutex);
if (!eax_dai_any_tx_opened())
ret = (*ei.master_dai_ops->startup)(substream, ei.master_dai);
ci->opened = true;
mutex_unlock(&ei.mutex);
return ret;
}
static void eax_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct ch_info *ci = to_info(dai);
if (!ei.master)
return;
mutex_lock(&ei.mutex);
ci->opened = false;
if (!eax_dai_any_tx_opened())
(*ei.master_dai_ops->shutdown)(substream, ei.master_dai);
mutex_unlock(&ei.mutex);
lpass_remove_stream();
}
static int eax_dai_probe(struct snd_soc_dai *dai)
{
pr_debug("%s\n", __func__);
return 0;
}
static int eax_dai_remove(struct snd_soc_dai *dai)
{
pr_debug("%s\n", __func__);
return 0;
}
static int eax_dai_suspend(struct snd_soc_dai *dai)
{
if (dai->active && ei.master_dai_suspend)
return (*ei.master_dai_suspend)(ei.master_dai);
return 0;
}
static int eax_dai_resume(struct snd_soc_dai *dai)
{
if (dai->active && ei.master_dai_resume)
return (*ei.master_dai_resume)(ei.master_dai);
return 0;
}
static const struct snd_soc_dai_ops eax_dai_ops = {
.trigger = eax_dai_trigger,
.hw_params = eax_dai_hw_params,
.set_fmt = eax_dai_set_fmt,
.set_clkdiv = eax_dai_set_clkdiv,
.set_sysclk = eax_dai_set_sysclk,
#ifdef CONFIG_SND_SOC_I2S_1840_TDM
.set_tdm_slot = eax_dai_set_tdm_slot,
#endif
.startup = eax_dai_startup,
.shutdown = eax_dai_shutdown,
};
static const struct snd_soc_component_driver eax_dai_component = {
.name = "samsung-eax",
};
static int eax_ch_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ch_info *ci;
ci = platform_get_drvdata(pdev);
snd_soc_register_component(dev, &eax_dai_component, &ci->dai_drv, 1);
eax_asoc_platform_register(dev);
return 0;
}
static int eax_ch_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
snd_soc_unregister_component(dev);
return 0;
}
static const char banner[] =
KERN_INFO "Exynos Audio Mixer driver, (c)2014 Samsung Electronics\n";
static int eax_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
printk(banner);
mutex_init(&ei.mutex);
spin_lock_init(&ei.lock);
ei.pdev = pdev;
pm_runtime_enable(dev);
return 0;
}
static int eax_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return 0;
}
#ifdef CONFIG_PM_RUNTIME
static int eax_runtime_suspend(struct device *dev)
{
dev_dbg(dev, "%s entered\n", __func__);
return 0;
}
static int eax_runtime_resume(struct device *dev)
{
dev_dbg(dev, "%s entered\n", __func__);
return 0;
}
#endif
#if !defined(CONFIG_PM_RUNTIME) && defined(CONFIG_PM_SLEEP)
static int eax_suspend(struct device *dev)
{
dev_dbg(dev, "%s entered\n", __func__);
return 0;
}
static int eax_resume(struct device *dev)
{
dev_dbg(dev, "%s entered\n", __func__);
return 0;
}
#else
#define eax_suspend NULL
#define eax_resume NULL
#endif
static struct platform_device_id eax_ch_driver_ids[] = {
{ .name = "samsung-eax.0", },
{ .name = "samsung-eax.1", },
{ .name = "samsung-eax.2", },
{ .name = "samsung-eax.3", },
{ .name = "samsung-eax.4", },
{ .name = "samsung-eax.5", },
{ .name = "samsung-eax.6", },
{ .name = "samsung-eax.7", },
{},
};
MODULE_DEVICE_TABLE(platform, eax_ch_driver_ids);
static const struct dev_pm_ops eax_ch_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(
eax_suspend,
eax_resume
)
SET_RUNTIME_PM_OPS(
eax_runtime_suspend,
eax_runtime_resume,
NULL
)
};
static struct platform_driver eax_dai_driver = {
.probe = eax_ch_probe,
.remove = eax_ch_remove,
.id_table = eax_ch_driver_ids,
.driver = {
.name = "samsung-eax",
.owner = THIS_MODULE,
.pm = &eax_ch_pmops,
},
};
module_platform_driver(eax_dai_driver);
static struct platform_device_id eax_driver_ids[] = {
{
.name = "samsung-amixer",
},
{},
};
MODULE_DEVICE_TABLE(platform, eax_driver_ids);
#ifdef CONFIG_OF
static const struct of_device_id exynos_eax_match[] = {
{
.compatible = "samsung,exynos-amixer",
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_eax_match);
#endif
static struct platform_driver eax_driver = {
.probe = eax_probe,
.remove = eax_remove,
.id_table = eax_driver_ids,
.driver = {
.name = "samsung-amixer",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(exynos_eax_match),
},
};
module_platform_driver(eax_driver);
MODULE_AUTHOR("Yeongman Seo <yman.seo@samsung.com>");
MODULE_DESCRIPTION("Exynos Audio Mixer driver");
MODULE_LICENSE("GPL");

1147
sound/soc/samsung/eax-dma.c Normal file

File diff suppressed because it is too large Load diff

28
sound/soc/samsung/eax.h Normal file
View file

@ -0,0 +1,28 @@
/* sound/soc/samsung/eax.h
*
* Exynos Audio Mixer driver
*
* Copyright (c) 2014 Samsung Electronics Co. Ltd.
* Yeongman Seo <yman.seo@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef _EXYNOS_AMIXER_H
#define _EXYNOS_AMIXER_H
extern int eax_dev_register(struct device *dev, const char *name,
struct s3c_dma_params *dma, int ch);
extern int eax_dai_register(struct snd_soc_dai *dai,
const struct snd_soc_dai_ops *dai_ops,
int (*dai_suspend)(struct snd_soc_dai *dai),
int (*dai_resume)(struct snd_soc_dai *dai));
extern int eax_dai_unregister(void);
extern int eax_dma_dai_register(struct snd_soc_dai *dai);
extern int eax_dma_dai_unregister(void);
extern int eax_dma_params_register(struct s3c_dma_params *dma);
extern int eax_asoc_platform_register(struct device *dev);
#endif

View file

@ -0,0 +1,377 @@
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/module.h>
#include <linux/io.h>
#include "esa_sa_effect.h"
#include "seiren/seiren.h"
#define COMPRESSED_LR_VOL_MAX_STEPS 0x2000
const DECLARE_TLV_DB_LINEAR(compr_vol_gain, 0, COMPRESSED_LR_VOL_MAX_STEPS);
struct compr_pdata aud_vol;
static int esa_ctl_sa_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = SA_MAX_COUNT;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
static int esa_ctl_my_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = MYSOUND_MAX_COUNT;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
static int esa_ctl_ps_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = VSP_MAX_COUNT;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1000;
return 0;
}
static int esa_ctl_sb_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = LRSM_MAX_COUNT;
uinfo->value.integer.min = -50;
uinfo->value.integer.max = 50;
return 0;
}
static int esa_ctl_bb_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = BB_MAX_COUNT;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
static int esa_ctl_eq_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = EQ_MAX_COUNT;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 14000;
return 0;
}
static int esa_ctl_ms_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = MYSPACE_MAX_COUNT;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 5;
return 0;
}
static int esa_ctl_sr_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 192000;
return 0;
}
static int esa_ctl_sa_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
int effect_val[SA_MAX_COUNT];
pr_info("%s\n", __func__);
for (i = 0; i < SA_MAX_COUNT; i++) {
effect_val[i] = ucontrol->value.integer.value[i];
}
esa_effect_write(SOUNDALIVE, effect_val, SA_MAX_COUNT);
return 0;
}
static int esa_ctl_my_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
int effect_val[MYSOUND_MAX_COUNT];
pr_info("%s\n", __func__);
for (i = 0; i < MYSOUND_MAX_COUNT; i++) {
effect_val[i] = ucontrol->value.integer.value[i];
}
esa_effect_write(MYSOUND, effect_val, MYSOUND_MAX_COUNT);
return 0;
}
static int esa_ctl_ps_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
int effect_val[VSP_MAX_COUNT];
pr_info("%s\n", __func__);
for (i = 0; i < VSP_MAX_COUNT; i++) {
effect_val[i] = ucontrol->value.integer.value[i];
}
esa_effect_write(PLAYSPEED, effect_val, VSP_MAX_COUNT);
return 0;
}
static int esa_ctl_sb_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
int effect_val[LRSM_MAX_COUNT];
pr_info("%s\n", __func__);
for (i = 0; i < LRSM_MAX_COUNT; i++) {
effect_val[i] = ucontrol->value.integer.value[i];
}
esa_effect_write(SOUNDBALANCE, effect_val, LRSM_MAX_COUNT);
return 0;
}
static int esa_ctl_bb_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
int effect_val[BB_MAX_COUNT];
pr_info("%s\n", __func__);
for (i = 0; i < BB_MAX_COUNT; i++) {
effect_val[i] = ucontrol->value.integer.value[i];
}
esa_effect_write(BASSBOOST, effect_val, BB_MAX_COUNT);
return 0;
}
static int esa_ctl_eq_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
int effect_val[EQ_MAX_COUNT];
pr_info("%s\n", __func__);
for (i = 0; i < EQ_MAX_COUNT; i++) {
effect_val[i] = ucontrol->value.integer.value[i];
}
esa_effect_write(EQUALIZER, effect_val, EQ_MAX_COUNT);
return 0;
}
static int esa_ctl_ms_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
int effect_val[MYSPACE_MAX_COUNT];
pr_info("%s\n", __func__);
for (i = 0; i < MYSPACE_MAX_COUNT; i++) {
effect_val[i] = ucontrol->value.integer.value[i];
}
esa_effect_write(MYSPACE, effect_val, MYSPACE_MAX_COUNT);
return 0;
}
static int esa_ctl_sr_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u32 rate = (u32)ucontrol->value.integer.value[0];
pr_info("%s set sample rate = %dHz\n", __func__, rate);
esa_compr_set_sample_rate(rate);
return 0;
}
static int esa_ctl_sa_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_info("%s\n", __func__);
return 0;
}
static int esa_ctl_my_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_info("%s\n", __func__);
return 0;
}
static int esa_ctl_ps_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_info("%s\n", __func__);
return 0;
}
static int esa_ctl_sb_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_info("%s\n", __func__);
return 0;
}
static int esa_ctl_bb_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_info("%s\n", __func__);
return 0;
}
static int esa_ctl_eq_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_info("%s\n", __func__);
return 0;
}
static int esa_ctl_ms_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_info("%s\n", __func__);
return 0;
}
static int esa_ctl_sr_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_info("%s\n", __func__);
return 0;
}
#define ESA_CTL_EQ_SWITCH(xname, xval, xinfo, xget_effect, xset_effect) {\
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.info = xinfo, \
.get = xget_effect, .put = xset_effect, \
.private_value = xval }
int esa_compr_set_volume(struct audio_processor *ap, int left, int right)
{
void __iomem *mailbox;
mailbox = esa_compr_get_mem();
writel(left, mailbox + COMPR_LEFT_VOL);
writel(right, mailbox + COMPR_RIGHT_VOL);
return 0;
}
static int compr_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct audio_processor *ap = aud_vol.ap[mc->reg];
uint32_t *volume = aud_vol.volume[mc->reg];
volume[0] = ucontrol->value.integer.value[0];
volume[1] = ucontrol->value.integer.value[1];
pr_info("%s: mc->reg %d left_vol %d right_vol %d\n",
__func__, mc->reg, volume[0], volume[1]);
if (ap)
esa_compr_set_volume(ap, volume[0], volume[1]);
return 0;
}
static int compr_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
uint32_t *volume = aud_vol.volume[mc->reg];
pr_info("%s: mc->reg %d left_vol %d right_vol %d\n",
__func__, mc->reg, volume[0], volume[1]);
ucontrol->value.integer.value[0] = volume[0];
ucontrol->value.integer.value[1] = volume[1];
return 0;
}
static const struct snd_kcontrol_new esa_effect_controls[] = {
ESA_CTL_EQ_SWITCH("SA data", 0, \
esa_ctl_sa_info, esa_ctl_sa_get, esa_ctl_sa_put),
ESA_CTL_EQ_SWITCH("Audio DHA data", 0, \
esa_ctl_my_info, esa_ctl_my_get, esa_ctl_my_put),
ESA_CTL_EQ_SWITCH("VSP data", 0, \
esa_ctl_ps_info, esa_ctl_ps_get, esa_ctl_ps_put),
ESA_CTL_EQ_SWITCH("LRSM data", 0, \
esa_ctl_sb_info, esa_ctl_sb_get, esa_ctl_sb_put),
ESA_CTL_EQ_SWITCH("MSP data", 0, \
esa_ctl_ms_info, esa_ctl_ms_get, esa_ctl_ms_put),
ESA_CTL_EQ_SWITCH("SR data", 0, \
esa_ctl_sr_info, esa_ctl_sr_get, esa_ctl_sr_put),
SOC_DOUBLE_EXT_TLV("Compress Playback 3 Volume", COMPR_DAI_MULTIMEDIA_1,
0, 8, COMPRESSED_LR_VOL_MAX_STEPS, 0,
compr_volume_get, compr_volume_put, compr_vol_gain),
ESA_CTL_EQ_SWITCH("ESA BBoost data", 0, \
esa_ctl_bb_info, esa_ctl_bb_get, esa_ctl_bb_put),
ESA_CTL_EQ_SWITCH("ESA EQ data", 0, \
esa_ctl_eq_info, esa_ctl_eq_get, esa_ctl_eq_put),
};
int esa_effect_register(struct snd_soc_card *card)
{
int ret;
ret = snd_soc_add_card_controls(card, esa_effect_controls,
ARRAY_SIZE(esa_effect_controls));
if (ret < 0)
pr_err("Failed to add controls : %d", ret);
return ret;
}
MODULE_AUTHOR("HaeKwang Park, <haekwang0808.park@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC SEIREN effect Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,111 @@
#ifndef _ESA_EFFECT_AUDIO_H
#define _ESA_EFFECT_AUDIO_H
enum {
SOUNDALIVE = 0,
MYSOUND,
PLAYSPEED,
SOUNDBALANCE,
MYSPACE,
BASSBOOST,
EQUALIZER,
};
/* Effect offset */
#define EFFECT_BASE (0x1A000)
#define SA_BASE (0x0)
#define SA_CHANGE_BIT (0x0)
#define SA_OUT_DEVICE (0x10)
#define SA_PRESET (0x14)
#define SA_EQ_BEGIN (0x18)
#define SA_EQ_END (0x30)
#define SA_3D_LEVEL (0x34)
#define SA_BE_LEVEL (0x38)
#define SA_REVERB (0x3C)
#define SA_ROOMSIZE (0x40)
#define SA_CLA_LEVEL (0x44)
#define SA_VOLUME_LEVEL (0x48)
#define SA_SQUARE_ROW (0x4C)
#define SA_SQUARE_COLUMN (0x50)
#define SA_TAB_INFO (0x54)
#define SA_NEW_UI (0x58)
#define SA_3D_ON (0x5C)
#define SA_3D_ANGLE_L (0x60)
#define SA_3D_ANGLE_R (0x64)
#define SA_3D_GAIN_L (0x68)
#define SA_3D_GAIN_R (0x6C)
#define SA_MAX_COUNT (24)
#define MYSOUND_BASE (0x100)
#define MYSOUND_CHANGE_BIT (0x0)
#define MYSOUND_DHA_ENABLE (0x10)
#define MYSOUND_GAIN_BEGIN (0x14)
#define MYSOUND_OUT_DEVICE (0x48)
#define MYSOUND_MAX_COUNT (14)
#define VSP_BASE (0x200)
#define VSP_CHANGE_BIT (0x0)
#define VSP_INDEX (0x10)
#define VSP_MAX_COUNT (2)
#define LRSM_BASE (0x300)
#define LRSM_CHANGE_BIT (0x0)
#define LRSM_INDEX0 (0x10)
#define LRSM_INDEX1 (0x20)
#define LRSM_MAX_COUNT (3)
#define MYSPACE_BASE (0x340)
#define MYSPACE_CHANGE_BIT (0x0)
#define MYSPACE_PRESET (0x10)
#define MYSPACE_MAX_COUNT (2)
#define BB_BASE (0x400)
#define BB_CHANGE_BIT (0x0)
#define BB_STATUS (0x10)
#define BB_STRENGTH (0x14)
#define BB_MAX_COUNT (2)
#define EQ_BASE (0x500)
#define EQ_CHANGE_BIT (0x0)
#define EQ_STATUS (0x10)
#define EQ_PRESET (0x14)
#define EQ_NBAND (0x18)
#define EQ_NBAND_LEVEL (0x1c) /* 10 nband levels */
#define EQ_NBAND_FREQ (0x44) /* 10 nband frequencies */
#define EQ_MAX_COUNT (23)
/* CommBox ELPE Parameter */
#define ELPE_BASE (0x600)
#define ELPE_CMD (0x0)
#define ELPE_ARG0 (0x4)
#define ELPE_ARG1 (0x8)
#define ELPE_ARG2 (0xC)
#define ELPE_ARG3 (0x10)
#define ELPE_ARG4 (0x14)
#define ELPE_ARG5 (0x18)
#define ELPE_ARG6 (0x1C)
#define ELPE_ARG7 (0x20)
#define ELPE_ARG8 (0x24)
#define ELPE_ARG9 (0x28)
#define ELPE_ARG10 (0x2C)
#define ELPE_ARG11 (0x30)
#define ELPE_ARG12 (0x34)
#define ELPE_RET (0x38)
#define ELPE_DONE (0x3C)
#define CHANGE_BIT (1)
int esa_effect_register(struct snd_soc_card *card);
enum {
COMPR_DAI_MULTIMEDIA_1 = 0,
COMPR_DAI_MAX,
};
struct compr_pdata {
struct audio_processor *ap[COMPR_DAI_MAX];
uint32_t volume[COMPR_DAI_MAX][2]; /* Left & Right */
};
extern struct compr_pdata aud_vol;
#endif

View file

@ -0,0 +1,926 @@
/*
* espresso7420_wm5110.c
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/mfd/arizona/registers.h>
#include <linux/mfd/arizona/core.h>
#include <sound/tlv.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#if 0
#include <mach/regs-pmu.h>
#endif
#include "i2s.h"
#include "i2s-regs.h"
#include "../codecs/wm5102.h"
#include "../codecs/wm5110.h"
#ifdef CONFIG_SND_ESA_SA_EFFECT
#include "esa_sa_effect.h"
#endif
/* ESPRESSO use CLKOUT from AP */
#define ESPRESSO_MCLK_FREQ 26000000
#define ESPRESSO_AUD_PLL_FREQ 491520000
#define ESPRESSO_DEFAULT_MCLK1 26000000
#define ESPRESSO_DEFAULT_MCLK2 32768
#define ESPRESSO_TELCLK_RATE (48000 * 1024)
static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0);
struct arizona_machine_priv {
int clock_mode;
struct snd_soc_jack jack;
struct snd_soc_codec *codec;
struct snd_soc_dai *aif[3];
struct delayed_work mic_work;
int aif2mode;
int aif1rate;
int aif2rate;
int asyncclk_rate;
unsigned int hp_impedance_step;
};
static struct arizona_machine_priv *priv = NULL;
static const struct snd_soc_component_driver espresso_cmpnt = {
.name = "espresso-audio",
};
const char *aif2_mode_text[] = {
"Slave", "Master"
};
static const struct soc_enum aif2_mode_enum[] = {
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text),
};
#if 0
static struct {
int min; /* Minimum impedance */
int max; /* Maximum impedance */
unsigned int gain; /* Register value to set for this measurement */
} hp_gain_table[] = {
{ 0, 42, 0 },
{ 43, 100, 2 },
{ 101, 200, 4 },
{ 201, 450, 6 },
{ 451, 1000, 8 },
{ 1001, INT_MAX, 0 },
};
#endif
static bool clkout_enabled;
static struct snd_soc_card espresso;
static struct snd_soc_codec *the_codec;
extern void update_cp_available(bool cpen);
#if 0
void espresso_wm5110_hpdet_cb(unsigned int meas)
{
int i;
struct arizona_machine_priv *priv;
WARN_ON(!the_codec);
if (!the_codec)
return;
priv = snd_soc_card_get_drvdata(the_codec->card);
for (i = 0; i < ARRAY_SIZE(hp_gain_table); i++) {
if (meas < hp_gain_table[i].min || meas > hp_gain_table[i].max)
continue;
dev_info(the_codec->dev, "SET GAIN %d step for %d ohms\n",
hp_gain_table[i].gain, meas);
priv->hp_impedance_step = hp_gain_table[i].gain;
}
}
#endif
static int arizona_put_impedance_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
#if 0
unsigned int reg = mc->reg;
#endif
unsigned int shift = mc->shift;
int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
int err = 0;
unsigned int val, val_mask;
val = (ucontrol->value.integer.value[0] & mask);
val += priv->hp_impedance_step;
dev_info(component->dev, "SET GAIN %d according to impedance, moved %d step\n",
val, priv->hp_impedance_step);
if (invert)
val = max - val;
val_mask = mask << shift;
val = val << shift;
#if 0
err = snd_soc_update_bits_locked(codec, reg, val_mask, val);
if (err < 0)
return err;
#endif
return err;
}
static void espresso_enable_mclk(bool on)
{
pr_debug("%s: %s\n", __func__, on ? "on" : "off");
clkout_enabled = on;
#if 0
writel(on ? 0x1F00 : 0x1F00, EXYNOS_PMU_PMU_DEBUG);
#endif
}
static int get_aif2_mode(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = priv->aif2mode;
return 0;
}
static int set_aif2_mode(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
priv->aif2mode = ucontrol->value.integer.value[0];
dev_info(component->dev, "set aif2 mode: %s\n",
aif2_mode_text[priv->aif2mode]);
return 0;
}
static const struct snd_kcontrol_new espresso_codec_controls[] = {
SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0],
get_aif2_mode, set_aif2_mode),
SOC_SINGLE_EXT_TLV("HPOUT1L Impedance Volume",
ARIZONA_DAC_DIGITAL_VOLUME_1L,
ARIZONA_OUT1L_VOL_SHIFT, 0xbf, 0,
snd_soc_get_volsw, arizona_put_impedance_volsw,
digital_tlv),
SOC_SINGLE_EXT_TLV("HPOUT1R Impedance Volume",
ARIZONA_DAC_DIGITAL_VOLUME_1R,
ARIZONA_OUT1L_VOL_SHIFT, 0xbf, 0,
snd_soc_get_volsw, arizona_put_impedance_volsw,
digital_tlv),
};
static const struct snd_kcontrol_new espresso_controls[] = {
SOC_DAPM_PIN_SWITCH("HP"),
SOC_DAPM_PIN_SWITCH("SPK"),
SOC_DAPM_PIN_SWITCH("HDMI"),
SOC_DAPM_PIN_SWITCH("Main Mic"),
SOC_DAPM_PIN_SWITCH("Sub Mic"),
};
const struct snd_soc_dapm_widget espresso_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("HDMIL"),
SND_SOC_DAPM_OUTPUT("HDMIR"),
SND_SOC_DAPM_HP("HP", NULL),
SND_SOC_DAPM_SPK("SPK", NULL),
SND_SOC_DAPM_LINE("HDMI", NULL),
SND_SOC_DAPM_MIC("Main Mic", NULL),
SND_SOC_DAPM_MIC("Sub Mic", NULL),
};
const struct snd_soc_dapm_route espresso_dapm_routes[] = {
{ "HP", NULL, "HPOUT1L" },
{ "HP", NULL, "HPOUT1R" },
{ "SPK", NULL, "SPKOUTLN" },
{ "SPK", NULL, "SPKOUTLP" },
{ "SPK", NULL, "SPKOUTRN" },
{ "SPK", NULL, "SPKOUTRP" },
{ "Main Mic", NULL, "MICBIAS1" },
{ "Sub Mic", NULL, "MICBIAS2" },
{ "IN1L", NULL, "Main Mic" },
{ "IN2L", NULL, "Sub Mic" },
};
int espresso_set_media_clocking(struct snd_soc_card *card,
struct snd_soc_codec *codec)
{
int ret, fs;
if (priv->aif1rate >= 192000)
fs = 256;
else
fs = 1024;
#ifdef CONFIG_MFD_WM5110
ret = snd_soc_codec_set_pll(codec, WM5110_FLL1_REFCLK,
ARIZONA_FLL_SRC_NONE, 0, 0);
if (ret != 0) {
dev_err(card->dev, "Failed to start FLL1 REF: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_pll(codec, WM5110_FLL1, ARIZONA_CLK_SRC_MCLK1,
ESPRESSO_DEFAULT_MCLK1,
priv->aif1rate * fs);
if (ret != 0) {
dev_err(card->dev, "Failed to start FLL1: %d\n", ret);
return ret;
}
#else
ret = snd_soc_codec_set_pll(codec, WM5102_FLL1_REFCLK,
ARIZONA_FLL_SRC_NONE, 0, 0);
if (ret != 0) {
dev_err(card->dev, "Failed to start FLL1 REF: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, ARIZONA_CLK_SRC_MCLK1,
ESPRESSO_DEFAULT_MCLK1,
priv->aif1rate * fs);
if (ret != 0) {
dev_err(card->dev, "Failed to start FLL1: %d\n", ret);
return ret;
}
#endif
ret = snd_soc_codec_set_sysclk(codec,
ARIZONA_CLK_SYSCLK,
ARIZONA_CLK_SRC_FLL1,
priv->aif1rate * fs,
SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(card->dev, "Failed to set SYSCLK to FLL1: %d\n", ret);
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK,
ARIZONA_CLK_SRC_FLL2,
ESPRESSO_TELCLK_RATE,
SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(card->dev,
"Unable to set ASYNCCLK to FLL2: %d\n", ret);
/* AIF1 from SYSCLK, AIF2 and 3 from ASYNCCLK */
ret = snd_soc_dai_set_sysclk(priv->aif[0], ARIZONA_CLK_SYSCLK, 0, 0);
if (ret < 0)
dev_err(card->dev, "Can't set AIF1 to SYSCLK: %d\n", ret);
ret = snd_soc_dai_set_sysclk(priv->aif[2], ARIZONA_CLK_ASYNCCLK, 0, 0);
if (ret < 0)
dev_err(card->dev, "Can't set AIF2 to ASYNCCLK: %d\n", ret);
return 0;
}
static int espresso_aif1_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "%s\n", __func__);
return 0;
}
static void espresso_aif1_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "%s\n", __func__);
}
static int espresso_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int ret;
dev_info(card->dev, "aif1: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
priv->aif1rate = params_rate(params);
espresso_set_media_clocking(card, codec_dai->codec);
/* Set Codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0) {
dev_err(card->dev, "Failed to set aif1 codec fmt: %d\n", ret);
return ret;
}
/* Set CPU DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0) {
dev_err(card->dev, "Failed to set aif1 cpu fmt: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
0, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "Failed to set SAMSUNG_I2S_CDCL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "Failed to set SAMSUNG_I2S_OPCL: %d\n", ret);
return ret;
}
return ret;
}
static int espresso_aif1_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "%s\n", __func__);
return 0;
}
static int espresso_aif1_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "%s\n", __func__);
return 0;
}
static int espresso_aif1_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "%s\n", __func__);
return 0;
}
static struct snd_soc_ops espresso_aif1_ops = {
.startup = espresso_aif1_startup,
.shutdown = espresso_aif1_shutdown,
.hw_params = espresso_aif1_hw_params,
.hw_free = espresso_aif1_hw_free,
.prepare = espresso_aif1_prepare,
.trigger = espresso_aif1_trigger,
};
static int espresso_aif2_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
unsigned int fmt;
int ret;
int prate, bclk;
dev_info(card->dev, ":::::::::::: %s-%d %dch, %dHz\n",
rtd->dai_link->name, substream->stream,
params_channels(params), params_rate(params));
prate = params_rate(params);
switch (prate) {
case 8000:
bclk = 256000;
break;
case 16000:
bclk = 512000;
break;
default:
dev_warn(card->dev,
"Unsupported LRCLK %d, falling back to 8000Hz\n",
(int)params_rate(params));
bclk = 256000;
}
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
/* Set the codec DAI configuration, aif2_mode:0 is slave */
if (priv->aif2mode == 0)
ret = snd_soc_dai_set_fmt(codec_dai, fmt | SND_SOC_DAIFMT_CBS_CFS);
else
ret = snd_soc_dai_set_fmt(codec_dai, fmt | SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0) {
dev_err(card->dev,
"Failed to set audio format in codec: %d\n", ret);
return ret;
}
priv->asyncclk_rate = 49152000;
if (priv->aif2mode == 0) {
ret = snd_soc_dai_set_pll(codec_dai, WM5110_FLL2_REFCLK,
ARIZONA_FLL_SRC_MCLK1,
ESPRESSO_DEFAULT_MCLK1,
priv->asyncclk_rate);
if (ret != 0) {
dev_err(card->dev,
"Failed to start FLL2 REF: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, WM5110_FLL2,
ARIZONA_FLL_SRC_AIF2BCLK,
bclk,
priv->asyncclk_rate);
if (ret != 0) {
dev_err(card->dev,
"Failed to start FLL2%d\n", ret);
return ret;
}
} else {
ret = snd_soc_dai_set_pll(codec_dai, WM5110_FLL2, 0, 0, 0);
if (ret != 0)
dev_err(card->dev,
"Failed to stop FLL2: %d\n", ret);
ret = snd_soc_dai_set_pll(codec_dai, WM5102_FLL2_REFCLK,
ARIZONA_FLL_SRC_NONE, 0, 0);
if (ret != 0) {
dev_err(card->dev,
"Failed to start FLL2 REF: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, WM5110_FLL2,
ARIZONA_CLK_SRC_MCLK1,
ESPRESSO_DEFAULT_MCLK1,
priv->asyncclk_rate);
if (ret != 0) {
dev_err(card->dev,
"Failed to start FLL2: %d\n", ret);
return ret;
}
}
update_cp_available(true);
return 0;
}
static void espresso_aif2_shutdown(struct snd_pcm_substream *substream)
{
update_cp_available(false);
}
static struct snd_soc_ops espresso_aif2_ops = {
.hw_params = espresso_aif2_hw_params,
.shutdown = espresso_aif2_shutdown,
};
#if 0 /* later */
static int set_aud_pll_rate(unsigned long rate)
{
struct clk *fout_aud_pll;
fout_aud_pll = clk_get(espresso.dev, "aud_pll");
if (IS_ERR(fout_aud_pll)) {
printk(KERN_ERR "%s: failed to get fout_aud_pll\n", __func__);
return PTR_ERR(fout_aud_pll);
}
if (rate == clk_get_rate(fout_aud_pll))
goto out;
rate += 20; /* margin */
clk_set_rate(fout_aud_pll, rate);
pr_debug("%s: aud_pll rate = %ld\n",
__func__, clk_get_rate(fout_aud_pll));
out:
clk_put(fout_aud_pll);
return 0;
}
#endif
static struct snd_soc_dai_link espresso_dai[] = {
{ /* playback & recording */
.name = "espresso-arizona playback",
.stream_name = "i2s0-pri",
.cpu_dai_name = "11440000.i2s",
.platform_name = "11440000.i2s",
#ifdef CONFIG_MFD_WM5110
.codec_dai_name = "wm5110-aif1",
.codec_name = "wm5110-codec",
#else
.codec_dai_name = "wm5102-aif1",
#endif
.ops = &espresso_aif1_ops,
}, { /* deep buffer playback */
.name = "espresso-arizona multimedia playback",
.stream_name = "i2s0-sec",
.cpu_dai_name = "samsung-i2s-sec",
.platform_name = "samsung-i2s-sec",
#ifdef CONFIG_MFD_WM5110
.codec_dai_name = "wm5110-aif1",
.codec_name = "wm5110-codec",
#else
.codec_dai_name = "wm5102-aif1",
#endif
.ops = &espresso_aif1_ops,
}, { /* Voice Call */
.name = "cp",
.stream_name = "voice call",
.cpu_dai_name = "cp_dummy",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "wm5110-aif2",
.codec_name = "wm5110-codec",
.ops = &espresso_aif2_ops,
.ignore_suspend = 1,
}, { /* eax0 playback */
.name = "playback-eax0",
.stream_name = "eax0",
.cpu_dai_name = "samsung-eax.0",
.platform_name = "samsung-eax.0",
#ifdef CONFIG_MFD_WM5110
.codec_dai_name = "wm5110-aif1",
.codec_name = "wm5110-codec",
#else
.codec_dai_name = "wm5102-aif1",
#endif
.ops = &espresso_aif1_ops,
}, { /* eax1 playback */
.name = "playback-eax1",
.stream_name = "eax1",
.cpu_dai_name = "samsung-eax.1",
.platform_name = "samsung-eax.1",
#ifdef CONFIG_MFD_WM5110
.codec_dai_name = "wm5110-aif1",
.codec_name = "wm5110-codec",
#else
.codec_dai_name = "wm5102-aif1",
#endif
.ops = &espresso_aif1_ops,
}, { /* eax2 playback */
.name = "playback-eax2",
.stream_name = "eax2",
.cpu_dai_name = "samsung-eax.2",
.platform_name = "samsung-eax.2",
#ifdef CONFIG_MFD_WM5110
.codec_dai_name = "wm5110-aif1",
.codec_name = "wm5110-codec",
#else
.codec_dai_name = "wm5102-aif1",
#endif
.ops = &espresso_aif1_ops,
}, { /* eax3 playback */
.name = "playback-eax3",
.stream_name = "eax3",
.cpu_dai_name = "samsung-eax.3",
.platform_name = "samsung-eax.3",
#ifdef CONFIG_MFD_WM5110
.codec_dai_name = "wm5110-aif1",
.codec_name = "wm5110-codec",
#else
.codec_dai_name = "wm5102-aif1",
#endif
.ops = &espresso_aif1_ops,
}, { /* compress playback */
.name = "espresso-arizona compress playback",
.stream_name = "i2s0-compr",
.cpu_dai_name = "samsung-i2s-compr",
.platform_name = "samsung-i2s-compr",
#ifdef CONFIG_MFD_WM5110
.codec_dai_name = "wm5110-aif1",
.codec_name = "wm5110-codec",
#else
.codec_dai_name = "wm5102-aif1",
#endif
.ops = &espresso_aif1_ops,
#if 0
}, { /* compress capture */
.name = "compress capture",
.stream_name = "compr-cap",
.codec_dai_name = "dummy-aif1",
#endif
}
};
static int espresso_late_probe(struct snd_soc_card *card)
{
struct snd_soc_codec *codec = card->rtd[0].codec;
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
struct snd_soc_dai *cpu_dai = card->rtd[0].cpu_dai;
int i, ret;
priv->codec = codec;
the_codec = codec;
for (i = 0; i < 3; i++)
priv->aif[i] = card->rtd[i].codec_dai;
codec_dai->driver->playback.channels_max =
cpu_dai->driver->playback.channels_max;
espresso_enable_mclk(true);
ret = snd_soc_add_codec_controls(codec, espresso_codec_controls,
ARRAY_SIZE(espresso_codec_controls));
if (ret < 0) {
dev_err(codec->dev,
"Failed to add controls to codec: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_sysclk(codec,
ARIZONA_CLK_SYSCLK,
ARIZONA_CLK_SRC_FLL1,
48000 * 1024,
SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(card->dev, "Failed to set SYSCLK to FLL1: %d\n", ret);
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK,
ARIZONA_CLK_SRC_FLL2,
ESPRESSO_TELCLK_RATE,
SND_SOC_CLOCK_IN);
#ifdef CONFIG_SND_ESA_SA_EFFECT
ret = esa_effect_register(card);
if (ret < 0) {
pr_err("Failed to add controls to effect: %d\n", ret);
return ret;
}
#endif
dev_info(card->dev, "%s: Successfully created\n", __func__);
#if 0
arizona_set_hpdet_cb(codec, espresso_wm5110_hpdet_cb);
#endif
espresso_enable_mclk(false);
return 0;
}
static int espresso_suspend_post(struct snd_soc_card *card)
{
/* espresso_enable_mclk(false); */
return 0;
}
static int espresso_resume_pre(struct snd_soc_card *card)
{
/* espresso_enable_mclk(true); */
return 0;
}
static int espresso_start_sysclk(struct snd_soc_card *card)
{
struct arizona_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret, fs;
if (!priv->aif1rate)
priv->aif1rate = 48000;
if (priv->aif1rate >= 192000)
fs = 256;
else
fs = 1024;
espresso_enable_mclk(true);
ret = snd_soc_codec_set_pll(priv->codec, WM5110_FLL1_REFCLK,
ARIZONA_FLL_SRC_NONE, 0, 0);
if (ret != 0) {
dev_err(card->dev, "Failed to start FLL1 REF: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_pll(priv->codec, WM5110_FLL1,
ARIZONA_CLK_SRC_MCLK1,
ESPRESSO_DEFAULT_MCLK1,
priv->aif1rate * fs);
if (ret != 0) {
dev_err(card->dev, "Failed to start FLL1: %d\n", ret);
return ret;
}
return ret;
}
static int espresso_stop_sysclk(struct snd_soc_card *card)
{
struct arizona_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
ret = snd_soc_codec_set_pll(priv->codec, WM5110_FLL1, 0, 0, 0);
if (ret != 0) {
dev_err(card->dev, "Failed to stop FLL1: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_pll(priv->codec, WM5110_FLL2, 0, 0, 0);
if (ret != 0) {
dev_err(card->dev, "Failed to stop FLL1: %d\n", ret);
return ret;
}
espresso_enable_mclk(false);
return ret;
}
static int espresso_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct arizona_machine_priv *priv = snd_soc_card_get_drvdata(card);
if (!priv->codec || dapm != &priv->codec->dapm)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
if (card->dapm.bias_level == SND_SOC_BIAS_OFF) {
espresso_start_sysclk(card);
if (IS_ERR(devm_pinctrl_get_select(card->dev, "default")))
dev_err(card->dev, "no pinctrl for irq\n");
}
break;
case SND_SOC_BIAS_OFF:
if (IS_ERR(devm_pinctrl_get_select(card->dev, "idle")))
dev_err(card->dev, "no pinctrl for irq\n");
espresso_stop_sysclk(card);
break;
case SND_SOC_BIAS_PREPARE:
break;
default:
break;
}
card->dapm.bias_level = level;
dev_dbg(card->dev, "%s: %d\n", __func__, level);
return 0;
}
static int espresso_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
dev_dbg(card->dev, "%s: %d\n", __func__, level);
return 0;
}
static struct snd_soc_card espresso = {
.name = "ESPRESSO-WM5110",
.owner = THIS_MODULE,
.dai_link = espresso_dai,
.num_links = ARRAY_SIZE(espresso_dai),
.controls = espresso_controls,
.num_controls = ARRAY_SIZE(espresso_controls),
.dapm_widgets = espresso_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(espresso_dapm_widgets),
.dapm_routes = espresso_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(espresso_dapm_routes),
.late_probe = espresso_late_probe,
.suspend_post = espresso_suspend_post,
.resume_pre = espresso_resume_pre,
.set_bias_level = espresso_set_bias_level,
.set_bias_level_post = espresso_set_bias_level_post,
};
static int espresso_audio_probe(struct platform_device *pdev)
{
int n, ret;
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &espresso;
bool hdmi_avail = true;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (priv == NULL) {
dev_err(&pdev->dev, "Failed to allocate memory\n");
return -ENOMEM;
}
card->dev = &pdev->dev;
for (n = 0; np && n < ARRAY_SIZE(espresso_dai); n++) {
if (!espresso_dai[n].cpu_dai_name) {
espresso_dai[n].cpu_of_node = of_parse_phandle(np,
"samsung,audio-cpu", n);
if (!espresso_dai[n].cpu_of_node && hdmi_avail) {
espresso_dai[n].cpu_of_node = of_parse_phandle(np,
"samsung,audio-cpu-hdmi", 0);
hdmi_avail = false;
}
if (!espresso_dai[n].cpu_of_node) {
dev_err(&pdev->dev, "Property "
"'samsung,audio-cpu' missing or invalid\n");
ret = -EINVAL;
}
}
if (!espresso_dai[n].platform_name)
espresso_dai[n].platform_of_node = espresso_dai[n].cpu_of_node;
#if 0
espresso_dai[n].codec_of_node = of_parse_phandle(np,
"samsung,audio-codec", n);
if (!espresso_dai[0].codec_of_node) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec' missing or invalid\n");
ret = -EINVAL;
}
#endif
}
snd_soc_card_set_drvdata(card, priv);
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret);
snd_soc_unregister_component(card->dev);
kfree(priv);
}
return ret;
}
static int espresso_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct arizona_machine_priv *priv = snd_soc_card_get_drvdata(card);
snd_soc_unregister_card(card);
kfree(priv);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id espresso_wm5110_of_match[] = {
{ .compatible = "samsung,espresso_wm5110", },
{},
};
MODULE_DEVICE_TABLE(of, espresso_wm5110_of_match);
#endif /* CONFIG_OF */
static struct platform_driver espresso_audio_driver = {
.driver = {
.name = "espresso-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(espresso_wm5110_of_match),
},
.probe = espresso_audio_probe,
.remove = espresso_audio_remove,
};
module_platform_driver(espresso_audio_driver);
MODULE_DESCRIPTION("ALSA SoC ESPRESSO WM5110");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:espresso-audio");

View file

@ -0,0 +1,304 @@
/*
* goni_wm8994.c
*
* Copyright (C) 2010 Samsung Electronics Co.Ltd
* Author: Chanwoo Choi <cw00.choi@samsung.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <asm/mach-types.h>
#include <mach/gpio-samsung.h>
#include "../codecs/wm8994.h"
#define MACHINE_NAME 0
#define CPU_VOICE_DAI 1
static const char *aquila_str[] = {
[MACHINE_NAME] = "aquila",
[CPU_VOICE_DAI] = "aquila-voice-dai",
};
static struct snd_soc_card goni;
static struct platform_device *goni_snd_device;
/* 3.5 pie jack */
static struct snd_soc_jack jack;
/* 3.5 pie jack detection DAPM pins */
static struct snd_soc_jack_pin jack_pins[] = {
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
}, {
.pin = "Headset Stereophone",
.mask = SND_JACK_HEADPHONE | SND_JACK_MECHANICAL |
SND_JACK_AVOUT,
},
};
/* 3.5 pie jack detection gpios */
static struct snd_soc_jack_gpio jack_gpios[] = {
{
.gpio = S5PV210_GPH0(6),
.name = "DET_3.5",
.report = SND_JACK_HEADSET | SND_JACK_MECHANICAL |
SND_JACK_AVOUT,
.debounce_time = 200,
},
};
static const struct snd_soc_dapm_widget goni_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Ext Left Spk", NULL),
SND_SOC_DAPM_SPK("Ext Right Spk", NULL),
SND_SOC_DAPM_SPK("Ext Rcv", NULL),
SND_SOC_DAPM_HP("Headset Stereophone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Main Mic", NULL),
SND_SOC_DAPM_MIC("2nd Mic", NULL),
SND_SOC_DAPM_LINE("Radio In", NULL),
};
static const struct snd_soc_dapm_route goni_dapm_routes[] = {
{"Ext Left Spk", NULL, "SPKOUTLP"},
{"Ext Left Spk", NULL, "SPKOUTLN"},
{"Ext Right Spk", NULL, "SPKOUTRP"},
{"Ext Right Spk", NULL, "SPKOUTRN"},
{"Ext Rcv", NULL, "HPOUT2N"},
{"Ext Rcv", NULL, "HPOUT2P"},
{"Headset Stereophone", NULL, "HPOUT1L"},
{"Headset Stereophone", NULL, "HPOUT1R"},
{"IN1RN", NULL, "Headset Mic"},
{"IN1RP", NULL, "Headset Mic"},
{"IN1RN", NULL, "2nd Mic"},
{"IN1RP", NULL, "2nd Mic"},
{"IN1LN", NULL, "Main Mic"},
{"IN1LP", NULL, "Main Mic"},
{"IN2LN", NULL, "Radio In"},
{"IN2RN", NULL, "Radio In"},
};
static int goni_wm8994_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
int ret;
/* set endpoints to not connected */
snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN");
snd_soc_dapm_nc_pin(dapm, "IN2RP:VXRP");
snd_soc_dapm_nc_pin(dapm, "LINEOUT1N");
snd_soc_dapm_nc_pin(dapm, "LINEOUT1P");
snd_soc_dapm_nc_pin(dapm, "LINEOUT2N");
snd_soc_dapm_nc_pin(dapm, "LINEOUT2P");
if (machine_is_aquila()) {
snd_soc_dapm_nc_pin(dapm, "SPKOUTRN");
snd_soc_dapm_nc_pin(dapm, "SPKOUTRP");
}
/* Headset jack detection */
ret = snd_soc_jack_new(codec, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_MECHANICAL | SND_JACK_AVOUT,
&jack);
if (ret)
return ret;
ret = snd_soc_jack_add_pins(&jack, ARRAY_SIZE(jack_pins), jack_pins);
if (ret)
return ret;
ret = snd_soc_jack_add_gpios(&jack, ARRAY_SIZE(jack_gpios), jack_gpios);
if (ret)
return ret;
return 0;
}
static int goni_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
unsigned int pll_out = 24000000;
int ret = 0;
/* set the cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
/* set the codec FLL */
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, pll_out,
params_rate(params) * 256);
if (ret < 0)
return ret;
/* set the codec system clock */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
params_rate(params) * 256, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops goni_hifi_ops = {
.hw_params = goni_hifi_hw_params,
};
static int goni_voice_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
unsigned int pll_out = 24000000;
int ret = 0;
if (params_rate(params) != 8000)
return -EINVAL;
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_LEFT_J |
SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
/* set the codec FLL */
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, 0, pll_out,
params_rate(params) * 256);
if (ret < 0)
return ret;
/* set the codec system clock */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2,
params_rate(params) * 256, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_dai_driver voice_dai = {
.name = "goni-voice-dai",
.id = 0,
.playback = {
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
};
static const struct snd_soc_component_driver voice_component = {
.name = "goni-voice",
};
static struct snd_soc_ops goni_voice_ops = {
.hw_params = goni_voice_hw_params,
};
static struct snd_soc_dai_link goni_dai[] = {
{
.name = "WM8994",
.stream_name = "WM8994 HiFi",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8994-codec.0-001a",
.init = goni_wm8994_init,
.ops = &goni_hifi_ops,
}, {
.name = "WM8994 Voice",
.stream_name = "Voice",
.cpu_dai_name = "goni-voice-dai",
.codec_dai_name = "wm8994-aif2",
.codec_name = "wm8994-codec.0-001a",
.ops = &goni_voice_ops,
},
};
static struct snd_soc_card goni = {
.name = "goni",
.owner = THIS_MODULE,
.dai_link = goni_dai,
.num_links = ARRAY_SIZE(goni_dai),
.dapm_widgets = goni_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(goni_dapm_widgets),
.dapm_routes = goni_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(goni_dapm_routes),
};
static int __init goni_init(void)
{
int ret;
if (machine_is_aquila()) {
voice_dai.name = aquila_str[CPU_VOICE_DAI];
goni_dai[1].cpu_dai_name = aquila_str[CPU_VOICE_DAI];
goni.name = aquila_str[MACHINE_NAME];
} else if (!machine_is_goni())
return -ENODEV;
goni_snd_device = platform_device_alloc("soc-audio", -1);
if (!goni_snd_device)
return -ENOMEM;
/* register voice DAI here */
ret = devm_snd_soc_register_component(&goni_snd_device->dev,
&voice_component, &voice_dai, 1);
if (ret) {
platform_device_put(goni_snd_device);
return ret;
}
platform_set_drvdata(goni_snd_device, &goni);
ret = platform_device_add(goni_snd_device);
if (ret)
platform_device_put(goni_snd_device);
return ret;
}
static void __exit goni_exit(void)
{
platform_device_unregister(goni_snd_device);
}
module_init(goni_init);
module_exit(goni_exit);
/* Module information */
MODULE_DESCRIPTION("ALSA SoC WM8994 GONI(S5PV210)");
MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,278 @@
/*
* h1940-uda1380.c -- ALSA Soc Audio Layer
*
* Copyright (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
* Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com>
*
* Based on version from Arnaud Patard <arnaud.patard@rtp-net.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include "regs-iis.h"
#include <asm/mach-types.h>
#include <mach/gpio-samsung.h>
#include "s3c24xx-i2s.h"
static unsigned int rates[] = {
11025,
22050,
44100,
};
static struct snd_pcm_hw_constraint_list hw_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static struct snd_soc_jack hp_jack;
static struct snd_soc_jack_pin hp_jack_pins[] = {
{
.pin = "Headphone Jack",
.mask = SND_JACK_HEADPHONE,
},
{
.pin = "Speaker",
.mask = SND_JACK_HEADPHONE,
.invert = 1,
},
};
static struct snd_soc_jack_gpio hp_jack_gpios[] = {
{
.gpio = S3C2410_GPG(4),
.name = "hp-gpio",
.report = SND_JACK_HEADPHONE,
.invert = 1,
.debounce_time = 200,
},
};
static int h1940_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
return snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&hw_rates);
}
static int h1940_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int div;
int ret;
unsigned int rate = params_rate(params);
switch (rate) {
case 11025:
case 22050:
case 44100:
div = s3c24xx_i2s_get_clockrate() / (384 * rate);
if (s3c24xx_i2s_get_clockrate() % (384 * rate) > (192 * rate))
div++;
break;
default:
dev_err(rtd->dev, "%s: rate %d is not supported\n",
__func__, rate);
return -EINVAL;
}
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* set cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* select clock source */
ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_PCLK, rate,
SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
/* set MCLK division for sample rate */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
S3C2410_IISMOD_384FS);
if (ret < 0)
return ret;
/* set BCLK division for sample rate */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
S3C2410_IISMOD_32FS);
if (ret < 0)
return ret;
/* set prescaler division for sample rate */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
S3C24XX_PRESCALE(div, div));
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops h1940_ops = {
.startup = h1940_startup,
.hw_params = h1940_hw_params,
};
static int h1940_spk_power(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
if (SND_SOC_DAPM_EVENT_ON(event))
gpio_set_value(S3C_GPIO_END + 9, 1);
else
gpio_set_value(S3C_GPIO_END + 9, 0);
return 0;
}
/* h1940 machine dapm widgets */
static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_SPK("Speaker", h1940_spk_power),
};
/* h1940 machine audio_map */
static const struct snd_soc_dapm_route audio_map[] = {
/* headphone connected to VOUTLHP, VOUTRHP */
{"Headphone Jack", NULL, "VOUTLHP"},
{"Headphone Jack", NULL, "VOUTRHP"},
/* ext speaker connected to VOUTL, VOUTR */
{"Speaker", NULL, "VOUTL"},
{"Speaker", NULL, "VOUTR"},
/* mic is connected to VINM */
{"VINM", NULL, "Mic Jack"},
};
static struct platform_device *s3c24xx_snd_device;
static int h1940_uda1380_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
&hp_jack);
snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
hp_jack_pins);
snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
hp_jack_gpios);
return 0;
}
static int h1940_uda1380_card_remove(struct snd_soc_card *card)
{
snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
hp_jack_gpios);
return 0;
}
/* s3c24xx digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link h1940_uda1380_dai[] = {
{
.name = "uda1380",
.stream_name = "UDA1380 Duplex",
.cpu_dai_name = "s3c24xx-iis",
.codec_dai_name = "uda1380-hifi",
.init = h1940_uda1380_init,
.platform_name = "s3c24xx-iis",
.codec_name = "uda1380-codec.0-001a",
.ops = &h1940_ops,
},
};
static struct snd_soc_card h1940_asoc = {
.name = "h1940",
.owner = THIS_MODULE,
.remove = h1940_uda1380_card_remove,
.dai_link = h1940_uda1380_dai,
.num_links = ARRAY_SIZE(h1940_uda1380_dai),
.dapm_widgets = uda1380_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
};
static int __init h1940_init(void)
{
int ret;
if (!machine_is_h1940())
return -ENODEV;
/* configure some gpios */
ret = gpio_request(S3C_GPIO_END + 9, "speaker-power");
if (ret)
goto err_out;
ret = gpio_direction_output(S3C_GPIO_END + 9, 0);
if (ret)
goto err_gpio;
s3c24xx_snd_device = platform_device_alloc("soc-audio", -1);
if (!s3c24xx_snd_device) {
ret = -ENOMEM;
goto err_gpio;
}
platform_set_drvdata(s3c24xx_snd_device, &h1940_asoc);
ret = platform_device_add(s3c24xx_snd_device);
if (ret)
goto err_plat;
return 0;
err_plat:
platform_device_put(s3c24xx_snd_device);
err_gpio:
gpio_free(S3C_GPIO_END + 9);
err_out:
return ret;
}
static void __exit h1940_exit(void)
{
platform_device_unregister(s3c24xx_snd_device);
gpio_free(S3C_GPIO_END + 9);
}
module_init(h1940_init);
module_exit(h1940_exit);
/* Module information */
MODULE_AUTHOR("Arnaud Patard, Vasily Khoruzhick");
MODULE_DESCRIPTION("ALSA SoC H1940");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,228 @@
/*
* linux/sound/soc/samsung/i2s-regs.h
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung I2S driver's register header
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#ifndef __SND_SOC_SAMSUNG_I2S_REGS_H
#define __SND_SOC_SAMSUNG_I2S_REGS_H
#define I2SCON 0x0
#define I2SMOD 0x4
#define I2SFIC 0x8
#define I2SPSR 0xc
#define I2STXD 0x10
#define I2SRXD 0x14
#define I2SFICS 0x18
#define I2STXDS 0x1c
#define I2SAHB 0x20
#define I2SSTR0 0x24
#define I2SSIZE 0x28
#define I2STRNCNT 0x2c
#define I2SLVL0ADDR 0x30
#define I2SLVL1ADDR 0x34
#define I2SLVL2ADDR 0x38
#define I2SLVL3ADDR 0x3c
#define I2SSTR1 0x40
#define I2SVER 0x44
#define I2SFIC1 0x48
#define I2STDM 0x4c
#define CON_RSTCLR (1 << 31)
#define CON_FRXOFSTATUS (1 << 26)
#define CON_FRXORINTEN (1 << 25)
#define CON_FTXSURSTAT (1 << 24)
#define CON_FTXSURINTEN (1 << 23)
#define CON_TXSDMA_PAUSE (1 << 20)
#define CON_TXSDMA_ACTIVE (1 << 18)
#define CON_FTXURSTATUS (1 << 17)
#define CON_FTXURINTEN (1 << 16)
#define CON_TXFIFO2_EMPTY (1 << 15)
#define CON_TXFIFO1_EMPTY (1 << 14)
#define CON_TXFIFO2_FULL (1 << 13)
#define CON_TXFIFO1_FULL (1 << 12)
#define CON_LRINDEX (1 << 11)
#define CON_TXFIFO_EMPTY (1 << 10)
#define CON_RXFIFO_EMPTY (1 << 9)
#define CON_TXFIFO_FULL (1 << 8)
#define CON_RXFIFO_FULL (1 << 7)
#define CON_TXDMA_PAUSE (1 << 6)
#define CON_RXDMA_PAUSE (1 << 5)
#define CON_TXCH_PAUSE (1 << 4)
#define CON_RXCH_PAUSE (1 << 3)
#define CON_TXDMA_ACTIVE (1 << 2)
#define CON_RXDMA_ACTIVE (1 << 1)
#define CON_ACTIVE (1 << 0)
#define MOD_OPCLK_CDCLK_OUT (0 << 30)
#define MOD_OPCLK_CDCLK_IN (1 << 30)
#define MOD_OPCLK_BCLK_OUT (2 << 30)
#define MOD_OPCLK_PCLK (3 << 30)
#define MOD_OPCLK_MASK (3 << 30)
#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
#define MOD_BLCS_SHIFT 26
#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT)
#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT)
#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT)
#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT)
#define MOD_BLCP_SHIFT 24
#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT)
#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT)
#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT)
#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT)
#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */
#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */
#define MOD_C1DD_HHALF (1 << 19)
#define MOD_C1DD_LHALF (1 << 18)
#define MOD_DC2_EN (1 << 17)
#define MOD_DC1_EN (1 << 16)
#define MOD_BLC_16BIT (0 << 13)
#define MOD_BLC_8BIT (1 << 13)
#define MOD_BLC_24BIT (2 << 13)
#define MOD_BLC_MASK (3 << 13)
#define MOD_CDCLKCON (1 << 12)
#define MOD_SLAVE (1 << 11)
#define MOD_RCLKSRC (1 << 10)
/*
* H/W Guide suggests that it is not allowed to change TXR mode while I2S is
* running. So I2S should use dedicated TXR mode as TX RX simultaneous mode.
* Since it is not possible to know beforehand whether the I2S operation would
* be only TX/RX or simultaneous TX RX, it is better to always set TXR mode.
*/
#define MOD_TXR_TXONLY 2
#define MOD_TXR_RXONLY 2
#define MOD_TXR_TXRX 2
#define MOD_TXR_SHIFT 8
#define MOD_TXR_MASK 3
#define MOD_LRP (1 << 7)
#define MOD_LRP_SHIFT 7
#define MOD_LR_LLOW 0
#define MOD_LR_RLOW 1
#define MOD_SDF_SHIFT 5
#define MOD_SDF_IIS 0
#define MOD_SDF_MSB 1
#define MOD_SDF_LSB 2
#define MOD_SDF_MASK 3
#define MOD_RCLK_SHIFT 3
#define MOD_RCLK_256FS 0
#define MOD_RCLK_512FS 1
#define MOD_RCLK_384FS 2
#define MOD_RCLK_768FS 3
#define MOD_RCLK_MASK 3
#define MOD_BCLK_SHIFT 1
#define MOD_BCLK_32FS 0
#define MOD_BCLK_48FS 1
#define MOD_BCLK_16FS 2
#define MOD_BCLK_24FS 3
#define MOD_BCLK_MASK 3
#define MOD_8BIT (1 << 0)
#define I2S_STR_MOD_LRP_SHIFT 15
#define I2S_STR_MOD_LRP BIT(I2S_STR_MOD_LRP_SHIFT)
#define I2S_STR_MOD_CDCLKCON MOD_CDCLKCON
#define I2S_STR_MOD_SLAVE MOD_SLAVE
#define I2S_STR_MOD_RCLKSRC MOD_RCLKSRC
#define I2S_STR_MOD_TXR_SHIFT MOD_TXR_SHIFT
#define I2S_STR_MOD_TXR_MASK MOD_TXR_MASK
#define I2S_STR_MOD_SDF_SHIFT 6
#define I2S_STR_MOD_RCLK_SHIFT MOD_RCLK_SHIFT
#define I2S_STR_MOD_RCLK_MASK 0x7
#define I2S_STR_MOD_BCLK_SHIFT 0
#define I2S_STR_MOD_BCLK_MASK 0x7
#define EXYNOS5420_MOD_LRP (1 << 15)
#define EXYNOS5420_MOD_CDCLKCON MOD_CDCLKCON
#define EXYNOS5420_MOD_SLAVE MOD_SLAVE
#define EXYNOS5420_MOD_RCLKSRC MOD_RCLKSRC
#define EXYNOS5420_MOD_LRP_SHIFT 15
#define EXYNOS5420_MOD_TXR_SHIFT MOD_TXR_SHIFT
#define EXYNOS5420_MOD_TXR_MASK MOD_TXR_MASK
#define EXYNOS5420_MOD_SDF_SHIFT 6
#define EXYNOS5420_MOD_RCLK_SHIFT 4
#define EXYNOS5420_MOD_RCLK_MASK MOD_RCLK_MASK
#define EXYNOS5420_MOD_BCLK_SHIFT 0
#define EXYNOS5420_MOD_BCLK_64FS 4
#define EXYNOS5420_MOD_BCLK_96FS 5
#define EXYNOS5420_MOD_BCLK_128FS 6
#define EXYNOS5420_MOD_BCLK_192FS 7
#define EXYNOS5420_MOD_BCLK_256FS 8
#define EXYNOS5420_MOD_BCLK_MASK 0xf
#define EXYNOS5430_MOD_LRP EXYNOS5420_MOD_LRP
#define EXYNOS5430_MOD_CDCLKCON (1 << 22)
#define EXYNOS5430_MOD_SLAVE (1 << 12)
#define EXYNOS5430_MOD_RCLKSRC (1 << 11)
#define EXYNOS5430_MOD_LRP_SHIFT EXYNOS5420_MOD_LRP_SHIFT
#define EXYNOS5430_MOD_TXR_SHIFT 9
#define EXYNOS5430_MOD_TXR_MASK EXYNOS5420_MOD_TXR_MASK
#define EXYNOS5430_MOD_SDF_SHIFT 7
#define EXYNOS5430_MOD_RCLK_SHIFT 4
#define EXYNOS5430_MOD_RCLK_MASK 7
#define EXYNOS5430_MOD_RCLK_64FS 4
#define EXYNOS5430_MOD_RCLK_128FS 5
#define EXYNOS5430_MOD_RCLK_96FS 6
#define EXYNOS5430_MOD_RCLK_192FS 7
#define EXYNOS5430_MOD_BCLK_SHIFT EXYNOS5420_MOD_BCLK_SHIFT
#define EXYNOS5430_MOD_BCLK_MASK EXYNOS5420_MOD_BCLK_MASK
#define PSR_PSREN (1 << 15)
#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf)
#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf)
#define FIC_TXFLUSH (1 << 15)
#define FIC_RXFLUSH (1 << 7)
#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf)
#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf)
#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
#define AHB_INTENLVL0 (1 << 24)
#define AHB_LVL0INT (1 << 20)
#define AHB_CLRLVL0INT (1 << 16)
#define AHB_DMARLD (1 << 5)
#define AHB_INTMASK (1 << 3)
#define AHB_DMAEN (1 << 0)
#define AHB_LVLINTMASK (0xf << 20)
#define I2SSIZE_TRNMSK (0xffff)
#define I2SSIZE_SHIFT (16)
#define TDM_LRCLK_WIDTH_SHIFT 12
#define TDM_LRCLK_WIDTH_MASK 0xFF
#define TDM_RX_SLOTS_SHIFT 8
#define TDM_RX_SLOTS_MASK 7
#define TDM_TX_SLOTS_SHIFT 4
#define TDM_TX_SLOTS_MASK 7
#define TDM_MODE_MASK (1 << 1)
#define TDM_MODE_DSPA (0 << 1)
#define TDM_MODE_DSPB (1 << 1)
#define TDM_ENABLE (1 << 0)
#define TDM_RX_SLOTS_MAX 2
#define TDM_TX_SLOTS_MAX 8
#endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */

2100
sound/soc/samsung/i2s.c Normal file

File diff suppressed because it is too large Load diff

24
sound/soc/samsung/i2s.h Normal file
View file

@ -0,0 +1,24 @@
/* sound/soc/samsung/i2s.h
*
* ALSA SoC Audio Layer - Samsung I2S Controller driver
*
* Copyright (c) 2010 Samsung Electronics Co. Ltd.
* Jaswinder Singh <jassisinghbrar@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_SAMSUNG_I2S_H
#define __SND_SOC_SAMSUNG_I2S_H
#define SAMSUNG_I2S_DIV_BCLK 1
#define SAMSUNG_I2S_DIV_RCLK 2
#define SAMSUNG_I2S_RCLKSRC_0 0
#define SAMSUNG_I2S_RCLKSRC_1 1
#define SAMSUNG_I2S_CDCLK 2
#define SAMSUNG_I2S_OPCLK 3
#endif /* __SND_SOC_SAMSUNG_I2S_H */

431
sound/soc/samsung/idma.c Normal file
View file

@ -0,0 +1,431 @@
/*
* sound/soc/samsung/idma.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* I2S0's Internal DMA driver
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "i2s.h"
#include "idma.h"
#include "dma.h"
#include "i2s-regs.h"
#define ST_RUNNING (1<<0)
#define ST_OPENED (1<<1)
static const struct snd_pcm_hardware idma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.buffer_bytes_max = MAX_IDMA_BUFFER,
.period_bytes_min = 128,
.period_bytes_max = MAX_IDMA_PERIOD,
.periods_min = 1,
.periods_max = 2,
};
struct idma_ctrl {
spinlock_t lock;
int state;
dma_addr_t start;
dma_addr_t pos;
dma_addr_t end;
dma_addr_t period;
dma_addr_t periodsz;
void *token;
void (*cb)(void *dt, int bytes_xfer);
};
static struct idma_info {
spinlock_t lock;
void __iomem *regs;
dma_addr_t lp_tx_addr;
} idma;
static int idma_irq;
static void idma_getpos(dma_addr_t *src)
{
*src = idma.lp_tx_addr +
(readl(idma.regs + I2STRNCNT) & 0xffffff) * 4;
}
static int idma_enqueue(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = substream->runtime->private_data;
u32 val;
spin_lock(&prtd->lock);
prtd->token = (void *) substream;
spin_unlock(&prtd->lock);
/* Internal DMA Level0 Interrupt Address */
val = idma.lp_tx_addr + prtd->periodsz;
writel(val, idma.regs + I2SLVL0ADDR);
/* Start address0 of I2S internal DMA operation. */
val = idma.lp_tx_addr;
writel(val, idma.regs + I2SSTR0);
/*
* Transfer block size for I2S internal DMA.
* Should decide transfer size before start dma operation
*/
val = readl(idma.regs + I2SSIZE);
val &= ~(I2SSIZE_TRNMSK << I2SSIZE_SHIFT);
val |= (((runtime->dma_bytes >> 2) &
I2SSIZE_TRNMSK) << I2SSIZE_SHIFT);
writel(val, idma.regs + I2SSIZE);
val = readl(idma.regs + I2SAHB);
val |= AHB_INTENLVL0;
writel(val, idma.regs + I2SAHB);
return 0;
}
static void idma_setcallbk(struct snd_pcm_substream *substream,
void (*cb)(void *, int))
{
struct idma_ctrl *prtd = substream->runtime->private_data;
spin_lock(&prtd->lock);
prtd->cb = cb;
spin_unlock(&prtd->lock);
}
static void idma_control(int op)
{
u32 val = readl(idma.regs + I2SAHB);
spin_lock(&idma.lock);
switch (op) {
case LPAM_DMA_START:
val |= (AHB_INTENLVL0 | AHB_DMAEN);
break;
case LPAM_DMA_STOP:
val &= ~(AHB_INTENLVL0 | AHB_DMAEN);
break;
default:
spin_unlock(&idma.lock);
return;
}
writel(val, idma.regs + I2SAHB);
spin_unlock(&idma.lock);
}
static void idma_done(void *id, int bytes_xfer)
{
struct snd_pcm_substream *substream = id;
struct idma_ctrl *prtd = substream->runtime->private_data;
if (prtd && (prtd->state & ST_RUNNING))
snd_pcm_period_elapsed(substream);
}
static int idma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = substream->runtime->private_data;
u32 mod = readl(idma.regs + I2SMOD);
u32 ahb = readl(idma.regs + I2SAHB);
ahb |= (AHB_DMARLD | AHB_INTMASK);
mod |= MOD_TXS_IDMA;
writel(ahb, idma.regs + I2SAHB);
writel(mod, idma.regs + I2SMOD);
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = params_buffer_bytes(params);
prtd->start = prtd->pos = runtime->dma_addr;
prtd->period = params_periods(params);
prtd->periodsz = params_period_bytes(params);
prtd->end = runtime->dma_addr + runtime->dma_bytes;
idma_setcallbk(substream, idma_done);
return 0;
}
static int idma_hw_free(struct snd_pcm_substream *substream)
{
snd_pcm_set_runtime_buffer(substream, NULL);
return 0;
}
static int idma_prepare(struct snd_pcm_substream *substream)
{
struct idma_ctrl *prtd = substream->runtime->private_data;
prtd->pos = prtd->start;
/* flush the DMA channel */
idma_control(LPAM_DMA_STOP);
idma_enqueue(substream);
return 0;
}
static int idma_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct idma_ctrl *prtd = substream->runtime->private_data;
int ret = 0;
spin_lock(&prtd->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
prtd->state |= ST_RUNNING;
idma_control(LPAM_DMA_START);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
prtd->state &= ~ST_RUNNING;
idma_control(LPAM_DMA_STOP);
break;
default:
ret = -EINVAL;
break;
}
spin_unlock(&prtd->lock);
return ret;
}
static snd_pcm_uframes_t
idma_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = runtime->private_data;
dma_addr_t src;
unsigned long res;
spin_lock(&prtd->lock);
idma_getpos(&src);
res = src - prtd->start;
spin_unlock(&prtd->lock);
return bytes_to_frames(substream->runtime, res);
}
static int idma_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned long size, offset;
int ret;
/* From snd_pcm_lib_mmap_iomem */
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
size = vma->vm_end - vma->vm_start;
offset = vma->vm_pgoff << PAGE_SHIFT;
ret = io_remap_pfn_range(vma, vma->vm_start,
(runtime->dma_addr + offset) >> PAGE_SHIFT,
size, vma->vm_page_prot);
return ret;
}
static irqreturn_t iis_irq(int irqno, void *dev_id)
{
struct idma_ctrl *prtd = (struct idma_ctrl *)dev_id;
u32 iisahb, val, addr;
iisahb = readl(idma.regs + I2SAHB);
val = (iisahb & AHB_LVL0INT) ? AHB_CLRLVL0INT : 0;
if (val) {
iisahb |= val;
writel(iisahb, idma.regs + I2SAHB);
addr = readl(idma.regs + I2SLVL0ADDR) - idma.lp_tx_addr;
addr += prtd->periodsz;
addr %= (u32)(prtd->end - prtd->start);
addr += idma.lp_tx_addr;
writel(addr, idma.regs + I2SLVL0ADDR);
if (prtd->cb)
prtd->cb(prtd->token, prtd->period);
}
return IRQ_HANDLED;
}
static int idma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd;
int ret;
snd_soc_set_runtime_hwparams(substream, &idma_hardware);
prtd = kzalloc(sizeof(struct idma_ctrl), GFP_KERNEL);
if (prtd == NULL)
return -ENOMEM;
ret = request_irq(idma_irq, iis_irq, 0, "i2s", prtd);
if (ret < 0) {
pr_err("fail to claim i2s irq , ret = %d\n", ret);
kfree(prtd);
return ret;
}
spin_lock_init(&prtd->lock);
runtime->private_data = prtd;
return 0;
}
static int idma_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = runtime->private_data;
free_irq(idma_irq, prtd);
if (!prtd)
pr_err("idma_close called with prtd == NULL\n");
kfree(prtd);
return 0;
}
static struct snd_pcm_ops idma_ops = {
.open = idma_open,
.close = idma_close,
.ioctl = snd_pcm_lib_ioctl,
.trigger = idma_trigger,
.pointer = idma_pointer,
.mmap = idma_mmap,
.hw_params = idma_hw_params,
.hw_free = idma_hw_free,
.prepare = idma_prepare,
};
static void idma_free(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
if (!substream)
return;
buf = &substream->dma_buffer;
if (!buf->area)
return;
iounmap((void __iomem *)buf->area);
buf->area = NULL;
buf->addr = 0;
}
static int preallocate_idma_buffer(struct snd_pcm *pcm, int stream)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
/* Assign PCM buffer pointers */
buf->dev.type = SNDRV_DMA_TYPE_CONTINUOUS;
buf->addr = idma.lp_tx_addr;
buf->bytes = idma_hardware.buffer_bytes_max;
buf->area = (unsigned char * __force)ioremap(buf->addr, buf->bytes);
return 0;
}
static int idma_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
int ret;
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = preallocate_idma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
}
return ret;
}
void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr)
{
spin_lock_init(&idma.lock);
idma.regs = regs;
idma.lp_tx_addr = addr;
}
EXPORT_SYMBOL_GPL(idma_reg_addr_init);
static struct snd_soc_platform_driver asoc_idma_platform = {
.ops = &idma_ops,
.pcm_new = idma_new,
.pcm_free = idma_free,
};
static int asoc_idma_platform_probe(struct platform_device *pdev)
{
idma_irq = platform_get_irq(pdev, 0);
if (idma_irq < 0)
return idma_irq;
return devm_snd_soc_register_platform(&pdev->dev, &asoc_idma_platform);
}
static struct platform_driver asoc_idma_driver = {
.driver = {
.name = "samsung-idma",
.owner = THIS_MODULE,
},
.probe = asoc_idma_platform_probe,
};
module_platform_driver(asoc_idma_driver);
MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
MODULE_DESCRIPTION("Samsung ASoC IDMA Driver");
MODULE_LICENSE("GPL");

26
sound/soc/samsung/idma.h Normal file
View file

@ -0,0 +1,26 @@
/*
* sound/soc/samsung/idma.h
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#ifndef __SND_SOC_SAMSUNG_IDMA_H_
#define __SND_SOC_SAMSUNG_IDMA_H_
extern void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr);
/* dma_state */
#define LPAM_DMA_STOP 0
#define LPAM_DMA_START 1
#define MAX_IDMA_PERIOD (128 * 1024)
#define MAX_IDMA_BUFFER (160 * 1024)
#endif /* __SND_SOC_SAMSUNG_IDMA_H_ */

View file

@ -0,0 +1,114 @@
static ssize_t earjack_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cod9002x_priv *cod9002x = dev_get_drvdata(dev);
struct cod9002x_jack_det *jackdet = &cod9002x->jack_det;
int status = jackdet->jack_det;
int report = 0;
if (status)
report = 1;
return sprintf(buf, "%d\n", report);
}
static ssize_t earjack_state_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
return size;
}
static ssize_t earjack_key_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cod9002x_priv *cod9002x = dev_get_drvdata(dev);
struct cod9002x_jack_det *jackdet = &cod9002x->jack_det;
int report = 0;
report = jackdet->button_det ? true : false;
return sprintf(buf, "%d\n", report);
}
static ssize_t earjack_key_state_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
return size;
}
static ssize_t earjack_select_jack_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return 0;
}
static ssize_t earjack_select_jack_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct cod9002x_priv *cod9002x = dev_get_drvdata(dev);
if ((!size) || (buf[0] != '1'))
switch_set_state(&cod9002x->sdev, 0);
else
switch_set_state(&cod9002x->sdev, 1);
return size;
}
static ssize_t earjack_mic_adc_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cod9002x_priv *cod9002x = dev_get_drvdata(dev);
struct cod9002x_jack_det *jackdet = &cod9002x->jack_det;
return sprintf(buf, "%d\n", jackdet->adc_val);
}
static ssize_t earjack_mic_adc_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
return size;
}
static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP,
earjack_select_jack_show, earjack_select_jack_store);
static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP,
earjack_key_state_show, earjack_key_state_store);
static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP,
earjack_state_show, earjack_state_store);
static DEVICE_ATTR(mic_adc, S_IRUGO | S_IWUSR | S_IWGRP,
earjack_mic_adc_show, earjack_mic_adc_store);
static void create_jack_devices(struct cod9002x_priv *info)
{
static struct class *jack_class;
static struct device *jack_dev;
jack_class = class_create(THIS_MODULE, "audio");
if (IS_ERR(jack_class))
pr_err("Failed to create class\n");
jack_dev = device_create(jack_class, NULL, 0, info, "earjack");
if (device_create_file(jack_dev, &dev_attr_select_jack) < 0)
pr_err("Failed to create device file (%s)!\n",
dev_attr_select_jack.attr.name);
if (device_create_file(jack_dev, &dev_attr_key_state) < 0)
pr_err("Failed to create device file (%s)!\n",
dev_attr_key_state.attr.name);
if (device_create_file(jack_dev, &dev_attr_state) < 0)
pr_err("Failed to create device file (%s)!\n",
dev_attr_state.attr.name);
if (device_create_file(jack_dev, &dev_attr_mic_adc) < 0)
pr_err("Failed to create device file (%s)!\n",
dev_attr_mic_adc.attr.name);
}

View file

@ -0,0 +1,174 @@
/* sound/soc/samsung/jive_wm8750.c
*
* Copyright 2007,2008 Simtec Electronics
*
* Based on sound/soc/pxa/spitz.c
* Copyright 2005 Wolfson Microelectronics PLC.
* Copyright 2005 Openedhand Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include <asm/mach-types.h>
#include "s3c2412-i2s.h"
#include "../codecs/wm8750.h"
static const struct snd_soc_dapm_route audio_map[] = {
{ "Headphone Jack", NULL, "LOUT1" },
{ "Headphone Jack", NULL, "ROUT1" },
{ "Internal Speaker", NULL, "LOUT2" },
{ "Internal Speaker", NULL, "ROUT2" },
{ "LINPUT1", NULL, "Line Input" },
{ "RINPUT1", NULL, "Line Input" },
};
static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Internal Speaker", NULL),
SND_SOC_DAPM_LINE("Line In", NULL),
};
static int jive_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct s3c_i2sv2_rate_calc div;
unsigned int clk = 0;
int ret = 0;
switch (params_rate(params)) {
case 8000:
case 16000:
case 48000:
case 96000:
clk = 12288000;
break;
case 11025:
case 22050:
case 44100:
clk = 11289600;
break;
}
s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params),
s3c_i2sv2_get_clock(cpu_dai));
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* set cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* set the codec system clock for DAC and ADC */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_RCLK, div.fs_div);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_PRESCALER,
div.clk_div - 1);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops jive_ops = {
.hw_params = jive_hw_params,
};
static int jive_wm8750_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
/* These endpoints are not being used. */
snd_soc_dapm_nc_pin(dapm, "LINPUT2");
snd_soc_dapm_nc_pin(dapm, "RINPUT2");
snd_soc_dapm_nc_pin(dapm, "LINPUT3");
snd_soc_dapm_nc_pin(dapm, "RINPUT3");
snd_soc_dapm_nc_pin(dapm, "OUT3");
snd_soc_dapm_nc_pin(dapm, "MONO");
return 0;
}
static struct snd_soc_dai_link jive_dai = {
.name = "wm8750",
.stream_name = "WM8750",
.cpu_dai_name = "s3c2412-i2s",
.codec_dai_name = "wm8750-hifi",
.platform_name = "s3c2412-i2s",
.codec_name = "wm8750.0-001a",
.init = jive_wm8750_init,
.ops = &jive_ops,
};
/* jive audio machine driver */
static struct snd_soc_card snd_soc_machine_jive = {
.name = "Jive",
.owner = THIS_MODULE,
.dai_link = &jive_dai,
.num_links = 1,
.dapm_widgets = wm8750_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
};
static struct platform_device *jive_snd_device;
static int __init jive_init(void)
{
int ret;
if (!machine_is_jive())
return 0;
printk("JIVE WM8750 Audio support\n");
jive_snd_device = platform_device_alloc("soc-audio", -1);
if (!jive_snd_device)
return -ENOMEM;
platform_set_drvdata(jive_snd_device, &snd_soc_machine_jive);
ret = platform_device_add(jive_snd_device);
if (ret)
platform_device_put(jive_snd_device);
return ret;
}
static void __exit jive_exit(void)
{
platform_device_unregister(jive_snd_device);
}
module_init(jive_init);
module_exit(jive_exit);
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
MODULE_DESCRIPTION("ALSA SoC Jive Audio support");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,329 @@
/*
* Littlemill audio support
*
* Copyright 2011 Wolfson Microelectronics
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm8994.h"
static int sample_rate = 44100;
static int littlemill_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
int ret;
if (dapm->dev != aif1_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
/*
* If we've not already clocked things via hw_params()
* then do so now, otherwise these are noops.
*/
if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
WM8994_FLL_SRC_MCLK2, 32768,
sample_rate * 512);
if (ret < 0) {
pr_err("Failed to start FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif1_dai,
WM8994_SYSCLK_FLL1,
sample_rate * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
}
break;
default:
break;
}
return 0;
}
static int littlemill_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
int ret;
if (dapm->dev != aif1_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to switch away from FLL1: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL1: %d\n", ret);
return ret;
}
break;
default:
break;
}
dapm->bias_level = level;
return 0;
}
static int littlemill_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret;
sample_rate = params_rate(params);
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
WM8994_FLL_SRC_MCLK2, 32768,
sample_rate * 512);
if (ret < 0) {
pr_err("Failed to start FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai,
WM8994_SYSCLK_FLL1,
sample_rate * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
return 0;
}
static struct snd_soc_ops littlemill_ops = {
.hw_params = littlemill_hw_params,
};
static const struct snd_soc_pcm_stream baseband_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link littlemill_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8994-codec",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ops = &littlemill_ops,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm8994-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
.params = &baseband_params,
},
};
static int bbclk_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = w->dapm->card;
struct snd_soc_dai *aif2_dai = card->rtd[1].cpu_dai;
int ret;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
WM8994_FLL_SRC_BCLK, 64 * 8000,
8000 * 256);
if (ret < 0) {
pr_err("Failed to start FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_FLL2,
8000 * 256,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
break;
case SND_SOC_DAPM_POST_PMD:
ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to switch away from FLL2: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL2: %d\n", ret);
return ret;
}
break;
default:
return -EINVAL;
}
return 0;
}
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
};
static struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("AMIC", NULL),
SND_SOC_DAPM_MIC("DMIC", NULL),
SND_SOC_DAPM_SUPPLY_S("Baseband Clock", -1, SND_SOC_NOPM, 0, 0,
bbclk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
};
static struct snd_soc_dapm_route audio_paths[] = {
{ "Headphone", NULL, "HPOUT1L" },
{ "Headphone", NULL, "HPOUT1R" },
{ "AMIC", NULL, "MICBIAS1" }, /* Default for AMICBIAS jumper */
{ "IN1LN", NULL, "AMIC" },
{ "DMIC", NULL, "MICBIAS2" }, /* Default for DMICBIAS jumper */
{ "DMIC1DAT", NULL, "DMIC" },
{ "DMIC2DAT", NULL, "DMIC" },
{ "AIF2CLK", NULL, "Baseband Clock" },
};
static struct snd_soc_jack littlemill_headset;
static int littlemill_late_probe(struct snd_soc_card *card)
{
struct snd_soc_codec *codec = card->rtd[0].codec;
struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
struct snd_soc_dai *aif2_dai = card->rtd[1].cpu_dai;
int ret;
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_jack_new(codec, "Headset",
SND_JACK_HEADSET | SND_JACK_MECHANICAL |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3 |
SND_JACK_BTN_4 | SND_JACK_BTN_5,
&littlemill_headset);
if (ret)
return ret;
/* This will check device compatibility itself */
wm8958_mic_detect(codec, &littlemill_headset, NULL, NULL, NULL, NULL);
/* As will this */
wm8994_mic_detect(codec, &littlemill_headset, 1);
return 0;
}
static struct snd_soc_card littlemill = {
.name = "Littlemill",
.owner = THIS_MODULE,
.dai_link = littlemill_dai,
.num_links = ARRAY_SIZE(littlemill_dai),
.set_bias_level = littlemill_set_bias_level,
.set_bias_level_post = littlemill_set_bias_level_post,
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.late_probe = littlemill_late_probe,
};
static int littlemill_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &littlemill;
int ret;
card->dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
static struct platform_driver littlemill_driver = {
.driver = {
.name = "littlemill",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = littlemill_probe,
};
module_platform_driver(littlemill_driver);
MODULE_DESCRIPTION("Littlemill audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:littlemill");

View file

@ -0,0 +1,72 @@
/*
* SoC audio for ln2440sbc
*
* Copyright 2007 KonekTel, a.s.
* Author: Ivan Kuten
* ivan.kuten@promwad.com
*
* Heavily based on smdk2443_wm9710.c
* Copyright 2007 Wolfson Microelectronics PLC.
* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <sound/soc.h>
static struct snd_soc_card ln2440sbc;
static struct snd_soc_dai_link ln2440sbc_dai[] = {
{
.name = "AC97",
.stream_name = "AC97 HiFi",
.cpu_dai_name = "samsung-ac97",
.codec_dai_name = "ac97-hifi",
.codec_name = "ac97-codec",
.platform_name = "samsung-ac97",
},
};
static struct snd_soc_card ln2440sbc = {
.name = "LN2440SBC",
.owner = THIS_MODULE,
.dai_link = ln2440sbc_dai,
.num_links = ARRAY_SIZE(ln2440sbc_dai),
};
static struct platform_device *ln2440sbc_snd_ac97_device;
static int __init ln2440sbc_init(void)
{
int ret;
ln2440sbc_snd_ac97_device = platform_device_alloc("soc-audio", -1);
if (!ln2440sbc_snd_ac97_device)
return -ENOMEM;
platform_set_drvdata(ln2440sbc_snd_ac97_device, &ln2440sbc);
ret = platform_device_add(ln2440sbc_snd_ac97_device);
if (ret)
platform_device_put(ln2440sbc_snd_ac97_device);
return ret;
}
static void __exit ln2440sbc_exit(void)
{
platform_device_unregister(ln2440sbc_snd_ac97_device);
}
module_init(ln2440sbc_init);
module_exit(ln2440sbc_exit);
/* Module information */
MODULE_AUTHOR("Ivan Kuten");
MODULE_DESCRIPTION("ALSA SoC ALC650 LN2440SBC");
MODULE_LICENSE("GPL");

212
sound/soc/samsung/lowland.c Normal file
View file

@ -0,0 +1,212 @@
/*
* Lowland audio support
*
* Copyright 2011 Wolfson Microelectronics
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm5100.h"
#include "../codecs/wm9081.h"
#define MCLK1_RATE (44100 * 512)
#define CLKOUT_RATE (44100 * 256)
static struct snd_soc_jack lowland_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin lowland_headset_pins[] = {
{
.pin = "Headphone",
.mask = SND_JACK_HEADPHONE | SND_JACK_LINEOUT,
},
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
};
static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
int ret;
ret = snd_soc_codec_set_sysclk(codec, WM5100_CLK_SYSCLK,
WM5100_CLKSRC_MCLK1, MCLK1_RATE,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK clock source: %d\n", ret);
return ret;
}
/* Clock OPCLK, used by the other audio components. */
ret = snd_soc_codec_set_sysclk(codec, WM5100_CLK_OPCLK, 0,
CLKOUT_RATE, 0);
if (ret < 0) {
pr_err("Failed to set OPCLK rate: %d\n", ret);
return ret;
}
ret = snd_soc_jack_new(codec, "Headset",
SND_JACK_LINEOUT | SND_JACK_HEADSET |
SND_JACK_BTN_0,
&lowland_headset);
if (ret)
return ret;
ret = snd_soc_jack_add_pins(&lowland_headset,
ARRAY_SIZE(lowland_headset_pins),
lowland_headset_pins);
if (ret)
return ret;
wm5100_detect(codec, &lowland_headset);
return 0;
}
static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
snd_soc_dapm_nc_pin(&codec->dapm, "LINEOUT");
/* At any time the WM9081 is active it will have this clock */
return snd_soc_codec_set_sysclk(codec, WM9081_SYSCLK_MCLK, 0,
CLKOUT_RATE, 0);
}
static const struct snd_soc_pcm_stream sub_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 44100,
.rate_max = 44100,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link lowland_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm5100-aif1",
.platform_name = "samsung-i2s.0",
.codec_name = "wm5100.1-001a",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.init = lowland_wm5100_init,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm5100-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
},
{
.name = "Sub Speaker",
.stream_name = "Sub Speaker",
.cpu_dai_name = "wm5100-aif3",
.codec_dai_name = "wm9081-hifi",
.codec_name = "wm9081.1-006c",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
.params = &sub_params,
.init = lowland_wm9081_init,
},
};
static struct snd_soc_codec_conf lowland_codec_conf[] = {
{
.dev_name = "wm9081.1-006c",
.name_prefix = "Sub",
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("Main DMIC"),
SOC_DAPM_PIN_SWITCH("Main AMIC"),
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
SOC_DAPM_PIN_SWITCH("Headphone"),
};
static struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
SND_SOC_DAPM_MIC("Main AMIC", NULL),
SND_SOC_DAPM_MIC("Main DMIC", NULL),
};
static struct snd_soc_dapm_route audio_paths[] = {
{ "Sub IN1", NULL, "HPOUT2L" },
{ "Sub IN2", NULL, "HPOUT2R" },
{ "Main Speaker", NULL, "Sub SPKN" },
{ "Main Speaker", NULL, "Sub SPKP" },
{ "Main Speaker", NULL, "SPKDAT1" },
};
static struct snd_soc_card lowland = {
.name = "Lowland",
.owner = THIS_MODULE,
.dai_link = lowland_dai,
.num_links = ARRAY_SIZE(lowland_dai),
.codec_conf = lowland_codec_conf,
.num_configs = ARRAY_SIZE(lowland_codec_conf),
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
};
static int lowland_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &lowland;
int ret;
card->dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
static struct platform_driver lowland_driver = {
.driver = {
.name = "lowland",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = lowland_probe,
};
module_platform_driver(lowland_driver);
MODULE_DESCRIPTION("Lowland audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:lowland");

View file

@ -0,0 +1,758 @@
/*
* Audio SubSystem driver for Samsung Exynos7570
*
* Copyright (c) 2015 Samsung Electronics Co. Ltd.
* Tushar Behera <tushar.b@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/proc_fs.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <soc/samsung/exynos-powermode.h>
#include <sound/exynos.h>
#include <sound/exynos-audmixer.h>
#ifdef CONFIG_SND_SOC_SAMSUNG_VERBOSE_DEBUG
#ifdef dev_dbg
#undef dev_dbg
#endif
#define dev_dbg dev_err
#endif
#define AUD_PLL_FREQ (98304020U)
#define AUD_MI2S_FREQ (110000000U + 100)
#define AUD_MIXER_FREQ (49152000U + 100)
#define EXYNOS7570_DISPAUD_CFG 0x1000
#define EXYNOS7570_DISPAUD_INTMASK 0x101C
#define DISPAUD_CFG_ADMA_SWRST_BIT 3
#define DISPAUD_CFG_AMP_SWRST_BIT 2
#define DISPAUD_CFG_MI2S_SWRST_BIT 1
#define DISPAUD_CFG_MIXER_SWRST_BIT 0
#define EXYNOS_GPIO_MODE_AUD_SYS_PWR_REG_OFFSET 0x1340
#define EXYNOS_PAD_RETENTION_AUD_OPTION_OFFSET 0x3028
static struct lpass_cmu_info {
struct clk *clk_fout_aud_pll;
struct clk *clk_dout_sclk_mi2s;
struct clk *clk_dout_sclk_mixer;
struct clk *clk_mi2s_aud_bclk;
struct clk *clk_aclk_aud;
struct clk *clk_dispaud_decon;
} lpass_cmu;
void __iomem *lpass_cmu_save[] = {
NULL, /* endmark */
};
/* Audio subsystem version */
enum {
LPASS_VER_7570, /* Java */
LPASS_VER_MAX
};
static struct lpass_info {
spinlock_t lock;
bool valid;
bool enabled;
struct platform_device *pdev;
void __iomem *regs;
struct proc_dir_entry *proc_file;
atomic_t use_cnt;
atomic_t stream_cnt;
struct regmap *pmureg;
bool display_on;
bool uhqa_on;
int idle_ip_index;
} lpass;
struct aud_reg {
void __iomem *reg;
u32 val;
struct list_head node;
};
struct subip_info {
struct device *dev;
const char *name;
void (*cb)(struct device *dev);
atomic_t use_cnt;
struct list_head node;
};
static LIST_HEAD(reg_list);
static LIST_HEAD(subip_list);
static void lpass_update_qos(void);
static void lpass_enable_pll(bool on)
{
if (on) {
clk_prepare_enable(lpass_cmu.clk_dispaud_decon);
clk_prepare_enable(lpass_cmu.clk_fout_aud_pll);
clk_prepare_enable(lpass_cmu.clk_dout_sclk_mi2s);
} else {
clk_disable_unprepare(lpass_cmu.clk_fout_aud_pll);
clk_disable_unprepare(lpass_cmu.clk_dout_sclk_mi2s);
clk_disable_unprepare(lpass_cmu.clk_dispaud_decon);
}
}
/*
* lpass_set_clk_hierarchy(): Define clock settings for audio
*
* This configures the default state of the MUX/DIV clocks used in Audio
* sub-system. Since this called from driver probe function only, it is safe to
* use devm_clk_get() APIs and remove the goto statements.
*
* Arguments:
* 1. dev: 'struct device *', pointer to LPASS device
*
* Return value:
* 0 on success, error code on failure.
*/
static int lpass_set_clk_hierarchy(struct device *dev)
{
lpass_cmu.clk_fout_aud_pll = devm_clk_get(dev, "fout_aud_pll");
if (IS_ERR(lpass_cmu.clk_fout_aud_pll)) {
dev_err(dev, "fout_aud_pll clk not found\n");
return PTR_ERR(lpass_cmu.clk_fout_aud_pll);
}
lpass_cmu.clk_dout_sclk_mi2s = devm_clk_get(dev, "dout_sclk_mi2s");
if (IS_ERR(lpass_cmu.clk_dout_sclk_mi2s)) {
dev_err(dev, "dout_sclk_mi2s clk not found\n");
return PTR_ERR(lpass_cmu.clk_dout_sclk_mi2s);
}
lpass_cmu.clk_dispaud_decon = devm_clk_get(dev, "dispaud_decon");
if (IS_ERR(lpass_cmu.clk_dispaud_decon)) {
dev_err(dev, "clk_dispaud_decon clk not found\n");
return PTR_ERR(lpass_cmu.clk_dispaud_decon);
}
lpass_enable_pll(true);
clk_set_rate(lpass_cmu.clk_fout_aud_pll, AUD_PLL_FREQ);
dev_info(dev, "PLL rate = %lu\n",
clk_get_rate(lpass_cmu.clk_fout_aud_pll));
clk_set_rate(lpass_cmu.clk_dout_sclk_mi2s, AUD_MI2S_FREQ);
dev_info(dev, "sclk_mi2s clock rate = %lu\n",
clk_get_rate(lpass_cmu.clk_dout_sclk_mi2s));
return 0;
}
/**
* AUD_PLL_USER Mux is defined as USERMUX. Enabling the USERMUX selects
* the underlying PLL as the parent of this MUX and disabling sets the
* oscillator clock as the parent of this clock.
*/
static void lpass_set_mux_pll(void)
{
/* Already taken care in lpass_enable_pll() */
}
static void lpass_set_mux_osc(void)
{
/* Already taken care in lpass_enable_pll() */
}
static void lpass_retention_pad_reg(void)
{
regmap_update_bits(lpass.pmureg,
EXYNOS_GPIO_MODE_AUD_SYS_PWR_REG_OFFSET,
0x1, 1);
}
void lpass_release_pad_reg(void)
{
regmap_update_bits(lpass.pmureg,
EXYNOS_PAD_RETENTION_AUD_OPTION_OFFSET,
0x10000000, 0x10000000);
regmap_update_bits(lpass.pmureg,
EXYNOS_GPIO_MODE_AUD_SYS_PWR_REG_OFFSET,
0x1, 1);
}
static inline bool is_running_only(const char *name)
{
struct subip_info *si;
if (atomic_read(&lpass.use_cnt) != 1)
return false;
list_for_each_entry(si, &subip_list, node) {
if (atomic_read(&si->use_cnt) > 0 &&
!strncmp(name, si->name, strlen(si->name)))
return true;
}
return false;
}
int exynos_check_aud_pwr(void)
{
/* TODO: Implement later */
return 0;
}
void lpass_set_dma_intr(bool on)
{
u32 reg, val;
void __iomem *regs;
spin_lock(&lpass.lock);
regs = lpass.regs;
reg = EXYNOS7570_DISPAUD_CFG;
val = readl(regs + reg);
val |= 0x1;
writel(val, regs + reg);
spin_unlock(&lpass.lock);
}
void lpass_reset(int ip, int op)
{
u32 reg, val, bit;
void __iomem *regs;
spin_lock(&lpass.lock);
regs = lpass.regs;
reg = EXYNOS7570_DISPAUD_CFG;
switch (ip) {
case LPASS_IP_DMA:
bit = DISPAUD_CFG_ADMA_SWRST_BIT;
break;
case LPASS_IP_AMP:
bit = DISPAUD_CFG_AMP_SWRST_BIT;
break;
case LPASS_IP_I2S:
bit = DISPAUD_CFG_MI2S_SWRST_BIT;
break;
case LPASS_IP_MIXER:
bit = DISPAUD_CFG_MIXER_SWRST_BIT;
break;
default:
spin_unlock(&lpass.lock);
pr_err("%s: wrong ip type %d!\n", __func__, ip);
return;
}
val = readl(regs + reg);
switch (op) {
case LPASS_RESET_BIT_UNSET:
val &= ~BIT(bit);
break;
case LPASS_RESET_BIT_SET:
val |= BIT(bit);
break;
default:
spin_unlock(&lpass.lock);
pr_err("%s: wrong op type %d!\n", __func__, op);
return;
}
writel(val, regs + reg);
spin_unlock(&lpass.lock);
}
void lpass_reset_toggle(int ip)
{
lpass_reset(ip, LPASS_RESET_BIT_SET);
udelay(100);
lpass_reset(ip, LPASS_RESET_BIT_UNSET);
}
int lpass_register_subip(struct device *ip_dev, const char *ip_name)
{
struct device *dev = &lpass.pdev->dev;
struct subip_info *si;
si = devm_kzalloc(dev, sizeof(struct subip_info), GFP_KERNEL);
if (!si)
return -1;
si->dev = ip_dev;
si->name = ip_name;
si->cb = NULL;
atomic_set(&si->use_cnt, 0);
list_add(&si->node, &subip_list);
pr_info("%s: %s(%p) registered\n", __func__, ip_name, ip_dev);
return 0;
}
int lpass_set_gpio_cb(struct device *ip_dev, void (*ip_cb)(struct device *dev))
{
struct subip_info *si;
list_for_each_entry(si, &subip_list, node) {
if (si->dev == ip_dev) {
si->cb = ip_cb;
pr_info("%s: %s(cb: %p)\n", __func__,
si->name, si->cb);
return 0;
}
}
return -EINVAL;
}
void lpass_get_sync(struct device *ip_dev)
{
struct subip_info *si;
list_for_each_entry(si, &subip_list, node) {
if (si->dev == ip_dev) {
atomic_inc(&si->use_cnt);
atomic_inc(&lpass.use_cnt);
dev_dbg(ip_dev, "%s: %s (use:%d)\n", __func__,
si->name, atomic_read(&si->use_cnt));
pm_runtime_get_sync(&lpass.pdev->dev);
}
}
lpass_update_qos();
}
void lpass_put_sync(struct device *ip_dev)
{
struct subip_info *si;
list_for_each_entry(si, &subip_list, node) {
if (si->dev == ip_dev) {
atomic_dec(&si->use_cnt);
atomic_dec(&lpass.use_cnt);
dev_dbg(ip_dev, "%s: %s (use:%d)\n", __func__,
si->name, atomic_read(&si->use_cnt));
pm_runtime_put_sync(&lpass.pdev->dev);
}
}
lpass_update_qos();
}
void lpass_add_stream(void)
{
atomic_inc(&lpass.stream_cnt);
lpass_update_qos();
}
void lpass_remove_stream(void)
{
atomic_dec(&lpass.stream_cnt);
lpass_update_qos();
}
static void lpass_reg_save(void)
{
struct aud_reg *ar;
list_for_each_entry(ar, &reg_list, node)
ar->val = readl(ar->reg);
return;
}
static void lpass_reg_restore(void)
{
struct aud_reg *ar;
list_for_each_entry(ar, &reg_list, node)
writel(ar->val, ar->reg);
return;
}
static void lpass_retention_pad(void)
{
struct subip_info *si;
/* Powerdown mode for gpio */
list_for_each_entry(si, &subip_list, node) {
if (si->cb != NULL)
(*si->cb)(si->dev);
}
/* Set PAD retention */
lpass_retention_pad_reg();
}
static void lpass_release_pad(void)
{
struct subip_info *si;
/* Restore gpio */
list_for_each_entry(si, &subip_list, node) {
if (si->cb != NULL)
(*si->cb)(si->dev);
}
/* Release PAD retention */
lpass_release_pad_reg();
}
static void lpass_enable(struct device *dev)
{
static unsigned int count;
dev_dbg(dev, "%s (count = %d)\n", __func__, ++count);
if (!lpass.valid) {
dev_err(dev, "%s: LPASS is not available", __func__);
return;
}
/* Enable PLL */
lpass_enable_pll(true);
lpass_reg_restore();
/* PLL path */
lpass_set_mux_pll();
/* Reset blocks inside audio sub-system as they are just powered-on */
lpass_reset_toggle(LPASS_IP_MIXER);
lpass_reset_toggle(LPASS_IP_AMP);
lpass_reset_toggle(LPASS_IP_I2S);
lpass_reset_toggle(LPASS_IP_DMA);
/* PAD */
lpass_release_pad();
/* ToDo: Check With AP Dev. */
//lpass_set_dma_intr(true);
lpass.enabled = true;
}
static void lpass_disable(struct device *dev)
{
static unsigned int count;
dev_dbg(dev, "%s (count = %d)\n", __func__, ++count);
if (!lpass.valid) {
dev_err(dev, "%s: LPASS is not available", __func__);
return;
}
lpass.enabled = false;
/* PAD */
lpass_retention_pad();
lpass_reg_save();
/* OSC path */
lpass_set_mux_osc();
/* Disable PLL */
lpass_enable_pll(false);
}
void lpass_dma_enable(bool on)
{
}
static void lpass_add_suspend_reg(void __iomem *reg)
{
struct device *dev = &lpass.pdev->dev;
struct aud_reg *ar;
ar = devm_kzalloc(dev, sizeof(struct aud_reg), GFP_KERNEL);
if (!ar)
return;
ar->reg = reg;
list_add(&ar->node, &reg_list);
}
static void lpass_init_reg_list(void)
{
int n = 0;
do {
if (lpass_cmu_save[n] == NULL)
break;
lpass_add_suspend_reg(lpass_cmu_save[n]);
} while (++n);
}
static int lpass_proc_show(struct seq_file *m, void *v)
{
struct subip_info *si;
int pmode = exynos_check_aud_pwr();
seq_printf(m, "power: %s\n", lpass.enabled ? "on" : "off");
seq_printf(m, "canbe: %s\n",
(pmode == AUD_PWR_SLEEP) ? "sleep" :
(pmode == AUD_PWR_LPA) ? "lpa" :
(pmode == AUD_PWR_ALPA) ? "alpa" :
(pmode == AUD_PWR_AFTR) ? "aftr" : "unknown");
list_for_each_entry(si, &subip_list, node) {
seq_printf(m, "subip: %s (%d)\n",
si->name, atomic_read(&si->use_cnt));
}
seq_printf(m, "strm: %d\n", atomic_read(&lpass.stream_cnt));
seq_printf(m, "uhqa: %s\n", lpass.uhqa_on ? "on" : "off");
return 0;
}
static int lpass_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, lpass_proc_show, NULL);
}
static const struct file_operations lpass_proc_fops = {
.owner = THIS_MODULE,
.open = lpass_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#ifdef CONFIG_PM_SLEEP
static int lpass_suspend(struct device *dev)
{
/*
* LPASS should be on during call, need to supply clocks to
* codec and mixer, so if call is in progress exit from suspend.
*/
if (is_cp_aud_enabled()) {
dev_info(dev, "Audio block active, don't suspend\n");
return 0;
}
#ifdef CONFIG_PM_RUNTIME
if (atomic_read(&lpass.use_cnt) > 0)
lpass_disable(dev);
#else
lpass_disable(dev);
#endif
exynos_update_ip_idle_status(lpass.idle_ip_index, 1);
return 0;
}
static int lpass_resume(struct device *dev)
{
/*
* LPASS was left enabled during CP call. If it is already on, no need
* to do anything here.
*/
if (lpass.enabled)
return 0;
exynos_update_ip_idle_status(lpass.idle_ip_index, 0);
#ifdef CONFIG_PM_RUNTIME
if (atomic_read(&lpass.use_cnt) > 0)
lpass_enable(dev);
#else
lpass_enable(dev);
#endif
return 0;
}
#else
#define lpass_suspend NULL
#define lpass_resume NULL
#endif
static void lpass_update_qos(void)
{
}
static char banner[] = KERN_INFO "Samsung Audio Subsystem driver\n";
static int lpass_probe(struct platform_device *pdev)
{
struct resource *res;
struct device *dev = &pdev->dev;
int ret = 0;
printk(banner);
lpass.pdev = pdev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "Unable to get LPASS SFRs\n");
return -ENXIO;
}
lpass.regs = ioremap(res->start, res->end);
if (!lpass.regs) {
dev_err(dev, "SFR ioremap failed\n");
return -ENOMEM;
}
ret = lpass_set_clk_hierarchy(&pdev->dev);
if (ret) {
dev_err(dev, "failed to set clock hierachy\n");
return -ENXIO;
}
lpass.proc_file = proc_create("driver/lpass", 0,
NULL, &lpass_proc_fops);
if (!lpass.proc_file)
pr_info("Failed to register /proc/driver/lpadd\n");
spin_lock_init(&lpass.lock);
atomic_set(&lpass.use_cnt, 0);
atomic_set(&lpass.stream_cnt, 0);
lpass_init_reg_list();
lpass.idle_ip_index = exynos_get_idle_ip_index(dev_name(&pdev->dev));
if (lpass.idle_ip_index < 0)
dev_err(dev, "Idle ip index is not provided for Audio.\n");
#ifdef CONFIG_PM_RUNTIME
pm_runtime_enable(&lpass.pdev->dev);
pm_runtime_get_sync(&lpass.pdev->dev);
#else
exynos_update_ip_idle_status(lpass.idle_ip_index, 0);
lpass_enable(&lpass.pdev->dev);
#endif
lpass_reg_save();
lpass.valid = true;
lpass.display_on = true;
lpass.pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
"samsung,syscon-phandle");
if (IS_ERR(lpass.pmureg)) {
dev_err(&pdev->dev, "syscon regmap lookup failed.\n");
return PTR_ERR(lpass.pmureg);
}
dev_dbg(dev, "%s Completed\n", __func__);
return 0;
}
static int lpass_remove(struct platform_device *pdev)
{
#ifdef CONFIG_PM_RUNTIME
pm_runtime_disable(&pdev->dev);
#else
lpass_disable(&pdev->dev);
exynos_update_ip_idle_status(lpass.idle_ip_index, 1);
#endif
iounmap(lpass.regs);
return 0;
}
#ifdef CONFIG_PM_RUNTIME
static int lpass_runtime_suspend(struct device *dev)
{
lpass_disable(dev);
exynos_update_ip_idle_status(lpass.idle_ip_index, 1);
return 0;
}
static int lpass_runtime_resume(struct device *dev)
{
exynos_update_ip_idle_status(lpass.idle_ip_index, 0);
lpass_enable(dev);
return 0;
}
#endif
static const int lpass_ver_data[] = {
[LPASS_VER_7570] = LPASS_VER_7570,
};
static struct platform_device_id lpass_driver_ids[] = {
{
.name = "samsung-lpass",
}, {},
};
MODULE_DEVICE_TABLE(platform, lpass_driver_ids);
#ifdef CONFIG_OF
static const struct of_device_id exynos_lpass_match[] = {
{
.compatible = "samsung,exynos7570-lpass",
.data = &lpass_ver_data[LPASS_VER_7570],
}, {},
};
MODULE_DEVICE_TABLE(of, exynos_lpass_match);
#endif
static const struct dev_pm_ops lpass_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(
lpass_suspend,
lpass_resume
)
SET_RUNTIME_PM_OPS(
lpass_runtime_suspend,
lpass_runtime_resume,
NULL
)
};
static struct platform_driver lpass_driver = {
.probe = lpass_probe,
.remove = lpass_remove,
.id_table = lpass_driver_ids,
.driver = {
.name = "samsung-lpass",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(exynos_lpass_match),
.pm = &lpass_pmops,
},
};
static int __init lpass_driver_init(void)
{
return platform_driver_register(&lpass_driver);
}
subsys_initcall(lpass_driver_init);
#ifdef CONFIG_PM_RUNTIME
static int lpass_driver_rpm_begin(void)
{
pr_debug("%s entered\n", __func__);
#ifndef CONFIG_SND_SOC_SAMSUNG_DUMMY_CODEC
pm_runtime_put_sync(&lpass.pdev->dev);
#endif
return 0;
}
late_initcall(lpass_driver_rpm_begin);
#endif
/* Module information */
MODULE_AUTHOR("Divya Jaiswal <divya.jswl@samsung.com>");
MODULE_AUTHOR("Chandrasekar R <rcsekar@samsung.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,751 @@
/*
* Audio SubSystem driver for Samsung Exynos7870
*
* Copyright (c) 2015 Samsung Electronics Co. Ltd.
* Tushar Behera <tushar.b@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/proc_fs.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <soc/samsung/exynos-powermode.h>
#include <sound/exynos.h>
#include <sound/exynos-audmixer.h>
#ifdef CONFIG_SND_SOC_SAMSUNG_VERBOSE_DEBUG
#ifdef dev_dbg
#undef dev_dbg
#endif
#define dev_dbg dev_err
#endif
#define AUD_PLL_FREQ (98304000U)
#define AUD_MI2S_FREQ (110000000U + 100)
#define AUD_MIXER_FREQ (49152000U + 100)
#define EXYNOS7870_DISPAUD_CFG 0x1000
#define DISPAUD_CFG_ADMA_SWRST_BIT 3
#define DISPAUD_CFG_AMP_SWRST_BIT 2
#define DISPAUD_CFG_MI2S_SWRST_BIT 1
#define DISPAUD_CFG_MIXER_SWRST_BIT 0
#define EXYNOS_GPIO_MODE_AUD_SYS_PWR_REG_OFFSET 0x1340
#define EXYNOS_PAD_RETENTION_AUD_OPTION_OFFSET 0x3028
static struct lpass_cmu_info {
struct clk *clk_fout_aud_pll;
struct clk *clk_dout_sclk_mi2s;
struct clk *clk_dout_sclk_mixer;
struct clk *clk_mi2s_aud_bclk;
struct clk *clk_aclk_aud;
struct clk *clk_dispaud_decon;
} lpass_cmu;
void __iomem *lpass_cmu_save[] = {
NULL, /* endmark */
};
/* Audio subsystem version */
enum {
LPASS_VER_7870, /* Joshua */
LPASS_VER_MAX
};
static struct lpass_info {
spinlock_t lock;
bool valid;
bool enabled;
struct platform_device *pdev;
void __iomem *regs;
struct proc_dir_entry *proc_file;
atomic_t use_cnt;
atomic_t stream_cnt;
struct regmap *pmureg;
bool display_on;
bool uhqa_on;
int idle_ip_index;
} lpass;
struct aud_reg {
void __iomem *reg;
u32 val;
struct list_head node;
};
struct subip_info {
struct device *dev;
const char *name;
void (*cb)(struct device *dev);
atomic_t use_cnt;
struct list_head node;
};
static LIST_HEAD(reg_list);
static LIST_HEAD(subip_list);
static void lpass_update_qos(void);
static void lpass_enable_pll(bool on)
{
if (on) {
clk_prepare_enable(lpass_cmu.clk_dispaud_decon);
clk_prepare_enable(lpass_cmu.clk_fout_aud_pll);
clk_prepare_enable(lpass_cmu.clk_dout_sclk_mi2s);
} else {
clk_disable_unprepare(lpass_cmu.clk_fout_aud_pll);
clk_disable_unprepare(lpass_cmu.clk_dout_sclk_mi2s);
clk_disable_unprepare(lpass_cmu.clk_dispaud_decon);
}
}
void set_ip_idle(bool value)
{
if (value)
exynos_update_ip_idle_status(lpass.idle_ip_index, 1);
else
exynos_update_ip_idle_status(lpass.idle_ip_index, 0);
}
EXPORT_SYMBOL(set_ip_idle);
/*
* lpass_set_clk_hierarchy(): Define clock settings for audio
*
* This configures the default state of the MUX/DIV clocks used in Audio
* sub-system. Since this called from driver probe function only, it is safe to
* use devm_clk_get() APIs and remove the goto statements.
*
* Arguments:
* 1. dev: 'struct device *', pointer to LPASS device
*
* Return value:
* 0 on success, error code on failure.
*/
static int lpass_set_clk_hierarchy(struct device *dev)
{
lpass_cmu.clk_fout_aud_pll = devm_clk_get(dev, "fout_aud_pll");
if (IS_ERR(lpass_cmu.clk_fout_aud_pll)) {
dev_err(dev, "fout_aud_pll clk not found\n");
return PTR_ERR(lpass_cmu.clk_fout_aud_pll);
}
lpass_cmu.clk_dout_sclk_mi2s = devm_clk_get(dev, "dout_sclk_mi2s");
if (IS_ERR(lpass_cmu.clk_dout_sclk_mi2s)) {
dev_err(dev, "dout_sclk_mi2s clk not found\n");
return PTR_ERR(lpass_cmu.clk_dout_sclk_mi2s);
}
lpass_cmu.clk_dispaud_decon = devm_clk_get(dev, "dispaud_decon");
if (IS_ERR(lpass_cmu.clk_dispaud_decon)) {
dev_err(dev, "clk_dispaud_decon clk not found\n");
return PTR_ERR(lpass_cmu.clk_dispaud_decon);
}
lpass_enable_pll(true);
clk_set_rate(lpass_cmu.clk_fout_aud_pll, AUD_PLL_FREQ);
dev_info(dev, "PLL rate = %lu\n",
clk_get_rate(lpass_cmu.clk_fout_aud_pll));
clk_set_rate(lpass_cmu.clk_dout_sclk_mi2s, AUD_MI2S_FREQ);
dev_info(dev, "sclk_mi2s clock rate = %lu\n",
clk_get_rate(lpass_cmu.clk_dout_sclk_mi2s));
return 0;
}
/**
* AUD_PLL_USER Mux is defined as USERMUX. Enabling the USERMUX selects
* the underlying PLL as the parent of this MUX and disabling sets the
* oscillator clock as the parent of this clock.
*/
static void lpass_set_mux_pll(void)
{
/* Already taken care in lpass_enable_pll() */
}
static void lpass_set_mux_osc(void)
{
/* Already taken care in lpass_enable_pll() */
}
static void lpass_retention_pad_reg(void)
{
regmap_update_bits(lpass.pmureg,
EXYNOS_GPIO_MODE_AUD_SYS_PWR_REG_OFFSET,
0x1, 1);
}
void lpass_release_pad_reg(void)
{
regmap_update_bits(lpass.pmureg,
EXYNOS_PAD_RETENTION_AUD_OPTION_OFFSET,
0x10000000, 1);
regmap_update_bits(lpass.pmureg,
EXYNOS_GPIO_MODE_AUD_SYS_PWR_REG_OFFSET,
0x1, 1);
}
static inline bool is_running_only(const char *name)
{
struct subip_info *si;
if (atomic_read(&lpass.use_cnt) != 1)
return false;
list_for_each_entry(si, &subip_list, node) {
if (atomic_read(&si->use_cnt) > 0 &&
!strncmp(name, si->name, strlen(si->name)))
return true;
}
return false;
}
int exynos_check_aud_pwr(void)
{
/* TODO: Implement later */
return 0;
}
void lpass_set_dma_intr(bool on)
{
}
void lpass_dma_enable(bool on)
{
}
void lpass_reset(int ip, int op)
{
u32 reg, val, bit;
void __iomem *regs;
spin_lock(&lpass.lock);
regs = lpass.regs;
reg = EXYNOS7870_DISPAUD_CFG;
switch (ip) {
case LPASS_IP_DMA:
bit = DISPAUD_CFG_ADMA_SWRST_BIT;
break;
case LPASS_IP_AMP:
bit = DISPAUD_CFG_AMP_SWRST_BIT;
break;
case LPASS_IP_I2S:
bit = DISPAUD_CFG_MI2S_SWRST_BIT;
break;
case LPASS_IP_MIXER:
bit = DISPAUD_CFG_MIXER_SWRST_BIT;
break;
default:
spin_unlock(&lpass.lock);
pr_err("%s: wrong ip type %d!\n", __func__, ip);
return;
}
val = readl(regs + reg);
switch (op) {
case LPASS_RESET_BIT_UNSET:
val &= ~BIT(bit);
break;
case LPASS_RESET_BIT_SET:
val |= BIT(bit);
break;
default:
spin_unlock(&lpass.lock);
pr_err("%s: wrong op type %d!\n", __func__, op);
return;
}
writel(val, regs + reg);
spin_unlock(&lpass.lock);
}
void lpass_reset_toggle(int ip)
{
lpass_reset(ip, LPASS_RESET_BIT_SET);
udelay(100);
lpass_reset(ip, LPASS_RESET_BIT_UNSET);
}
int lpass_register_subip(struct device *ip_dev, const char *ip_name)
{
struct device *dev = &lpass.pdev->dev;
struct subip_info *si;
si = devm_kzalloc(dev, sizeof(struct subip_info), GFP_KERNEL);
if (!si)
return -1;
si->dev = ip_dev;
si->name = ip_name;
si->cb = NULL;
atomic_set(&si->use_cnt, 0);
list_add(&si->node, &subip_list);
pr_info("%s: %s(%p) registered\n", __func__, ip_name, ip_dev);
return 0;
}
int lpass_set_gpio_cb(struct device *ip_dev, void (*ip_cb)(struct device *dev))
{
struct subip_info *si;
list_for_each_entry(si, &subip_list, node) {
if (si->dev == ip_dev) {
si->cb = ip_cb;
pr_info("%s: %s(cb: %p)\n", __func__,
si->name, si->cb);
return 0;
}
}
return -EINVAL;
}
void lpass_get_sync(struct device *ip_dev)
{
struct subip_info *si;
list_for_each_entry(si, &subip_list, node) {
if (si->dev == ip_dev) {
atomic_inc(&si->use_cnt);
atomic_inc(&lpass.use_cnt);
dev_dbg(ip_dev, "%s: %s (use:%d)\n", __func__,
si->name, atomic_read(&si->use_cnt));
pm_runtime_get_sync(&lpass.pdev->dev);
}
}
lpass_update_qos();
}
void lpass_put_sync(struct device *ip_dev)
{
struct subip_info *si;
list_for_each_entry(si, &subip_list, node) {
if (si->dev == ip_dev) {
atomic_dec(&si->use_cnt);
atomic_dec(&lpass.use_cnt);
dev_dbg(ip_dev, "%s: %s (use:%d)\n", __func__,
si->name, atomic_read(&si->use_cnt));
pm_runtime_put_sync(&lpass.pdev->dev);
}
}
lpass_update_qos();
}
void lpass_add_stream(void)
{
atomic_inc(&lpass.stream_cnt);
lpass_update_qos();
}
void lpass_remove_stream(void)
{
atomic_dec(&lpass.stream_cnt);
lpass_update_qos();
}
static void lpass_reg_save(void)
{
struct aud_reg *ar;
list_for_each_entry(ar, &reg_list, node)
ar->val = readl(ar->reg);
return;
}
static void lpass_reg_restore(void)
{
struct aud_reg *ar;
list_for_each_entry(ar, &reg_list, node)
writel(ar->val, ar->reg);
return;
}
static void lpass_retention_pad(void)
{
struct subip_info *si;
/* Powerdown mode for gpio */
list_for_each_entry(si, &subip_list, node) {
if (si->cb != NULL)
(*si->cb)(si->dev);
}
/* Set PAD retention */
lpass_retention_pad_reg();
}
static void lpass_release_pad(void)
{
struct subip_info *si;
/* Restore gpio */
list_for_each_entry(si, &subip_list, node) {
if (si->cb != NULL)
(*si->cb)(si->dev);
}
/* Release PAD retention */
lpass_release_pad_reg();
}
static void lpass_enable(struct device *dev)
{
static unsigned int count;
dev_dbg(dev, "%s (count = %d)\n", __func__, ++count);
if (!lpass.valid) {
dev_err(dev, "%s: LPASS is not available", __func__);
return;
}
/* Enable PLL */
lpass_enable_pll(true);
lpass_reg_restore();
/* PLL path */
lpass_set_mux_pll();
/* Reset blocks inside audio sub-system as they are just powered-on */
lpass_reset_toggle(LPASS_IP_MIXER);
lpass_reset_toggle(LPASS_IP_AMP);
lpass_reset_toggle(LPASS_IP_I2S);
lpass_reset_toggle(LPASS_IP_DMA);
/* PAD */
lpass_release_pad();
lpass.enabled = true;
}
static void lpass_disable(struct device *dev)
{
static unsigned int count;
dev_dbg(dev, "%s (count = %d)\n", __func__, ++count);
if (!lpass.valid) {
dev_err(dev, "%s: LPASS is not available", __func__);
return;
}
lpass.enabled = false;
/* PAD */
lpass_retention_pad();
lpass_reg_save();
/* OSC path */
lpass_set_mux_osc();
/* Disable PLL */
lpass_enable_pll(false);
}
static void lpass_add_suspend_reg(void __iomem *reg)
{
struct device *dev = &lpass.pdev->dev;
struct aud_reg *ar;
ar = devm_kzalloc(dev, sizeof(struct aud_reg), GFP_KERNEL);
if (!ar)
return;
ar->reg = reg;
list_add(&ar->node, &reg_list);
}
static void lpass_init_reg_list(void)
{
int n = 0;
do {
if (lpass_cmu_save[n] == NULL)
break;
lpass_add_suspend_reg(lpass_cmu_save[n]);
} while (++n);
}
static int lpass_proc_show(struct seq_file *m, void *v)
{
struct subip_info *si;
int pmode = exynos_check_aud_pwr();
seq_printf(m, "power: %s\n", lpass.enabled ? "on" : "off");
seq_printf(m, "canbe: %s\n",
(pmode == AUD_PWR_SLEEP) ? "sleep" :
(pmode == AUD_PWR_LPA) ? "lpa" :
(pmode == AUD_PWR_ALPA) ? "alpa" :
(pmode == AUD_PWR_AFTR) ? "aftr" : "unknown");
list_for_each_entry(si, &subip_list, node) {
seq_printf(m, "subip: %s (%d)\n",
si->name, atomic_read(&si->use_cnt));
}
seq_printf(m, "strm: %d\n", atomic_read(&lpass.stream_cnt));
seq_printf(m, "uhqa: %s\n", lpass.uhqa_on ? "on" : "off");
return 0;
}
static int lpass_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, lpass_proc_show, NULL);
}
static const struct file_operations lpass_proc_fops = {
.owner = THIS_MODULE,
.open = lpass_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#ifdef CONFIG_PM_SLEEP
static int lpass_suspend(struct device *dev)
{
/*
* LPASS should be on during call, need to supply clocks to
* codec and mixer, so if call is in progress exit from suspend.
*/
if (is_cp_aud_enabled()) {
dev_info(dev, "Audio block active, don't suspend\n");
return 0;
}
#ifdef CONFIG_PM_RUNTIME
if (atomic_read(&lpass.use_cnt) > 0)
lpass_disable(dev);
#else
lpass_disable(dev);
#endif
exynos_update_ip_idle_status(lpass.idle_ip_index, 1);
return 0;
}
static int lpass_resume(struct device *dev)
{
/*
* LPASS was left enabled during CP call. If it is already on, no need
* to do anything here.
*/
if (lpass.enabled)
return 0;
exynos_update_ip_idle_status(lpass.idle_ip_index, 0);
#ifdef CONFIG_PM_RUNTIME
if (atomic_read(&lpass.use_cnt) > 0)
lpass_enable(dev);
#else
lpass_enable(dev);
#endif
return 0;
}
#else
#define lpass_suspend NULL
#define lpass_resume NULL
#endif
static void lpass_update_qos(void)
{
}
static char banner[] = KERN_INFO "Samsung Audio Subsystem driver\n";
static int lpass_probe(struct platform_device *pdev)
{
struct resource *res;
struct device *dev = &pdev->dev;
int ret = 0;
printk(banner);
lpass.pdev = pdev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "Unable to get LPASS SFRs\n");
return -ENXIO;
}
lpass.regs = ioremap(res->start, res->end);
if (!lpass.regs) {
dev_err(dev, "SFR ioremap failed\n");
return -ENOMEM;
}
ret = lpass_set_clk_hierarchy(&pdev->dev);
if (ret) {
dev_err(dev, "failed to set clock hierachy\n");
return -ENXIO;
}
lpass.proc_file = proc_create("driver/lpass", 0,
NULL, &lpass_proc_fops);
if (!lpass.proc_file)
pr_info("Failed to register /proc/driver/lpadd\n");
spin_lock_init(&lpass.lock);
atomic_set(&lpass.use_cnt, 0);
atomic_set(&lpass.stream_cnt, 0);
lpass_init_reg_list();
lpass.idle_ip_index = exynos_get_idle_ip_index(dev_name(&pdev->dev));
if (lpass.idle_ip_index < 0)
dev_err(dev, "Idle ip index is not provided for Audio.\n");
#ifdef CONFIG_PM_RUNTIME
pm_runtime_enable(&lpass.pdev->dev);
pm_runtime_get_sync(&lpass.pdev->dev);
#else
exynos_update_ip_idle_status(lpass.idle_ip_index, 0);
lpass_enable(&lpass.pdev->dev);
#endif
lpass_reg_save();
lpass.valid = true;
lpass.display_on = true;
lpass.pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
"samsung,syscon-phandle");
if (IS_ERR(lpass.pmureg)) {
dev_err(&pdev->dev, "syscon regmap lookup failed.\n");
return PTR_ERR(lpass.pmureg);
}
dev_dbg(dev, "%s Completed\n", __func__);
return 0;
}
static int lpass_remove(struct platform_device *pdev)
{
#ifdef CONFIG_PM_RUNTIME
pm_runtime_disable(&pdev->dev);
#else
lpass_disable(&pdev->dev);
exynos_update_ip_idle_status(lpass.idle_ip_index, 1);
#endif
iounmap(lpass.regs);
return 0;
}
#ifdef CONFIG_PM_RUNTIME
static int lpass_runtime_suspend(struct device *dev)
{
lpass_disable(dev);
exynos_update_ip_idle_status(lpass.idle_ip_index, 1);
return 0;
}
static int lpass_runtime_resume(struct device *dev)
{
exynos_update_ip_idle_status(lpass.idle_ip_index, 0);
lpass_enable(dev);
return 0;
}
#endif
static const int lpass_ver_data[] = {
[LPASS_VER_7870] = LPASS_VER_7870,
};
static struct platform_device_id lpass_driver_ids[] = {
{
.name = "samsung-lpass",
}, {},
};
MODULE_DEVICE_TABLE(platform, lpass_driver_ids);
#ifdef CONFIG_OF
static const struct of_device_id exynos_lpass_match[] = {
{
.compatible = "samsung,exynos7870-lpass",
.data = &lpass_ver_data[LPASS_VER_7870],
}, {},
};
MODULE_DEVICE_TABLE(of, exynos_lpass_match);
#endif
static const struct dev_pm_ops lpass_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(
lpass_suspend,
lpass_resume
)
SET_RUNTIME_PM_OPS(
lpass_runtime_suspend,
lpass_runtime_resume,
NULL
)
};
static struct platform_driver lpass_driver = {
.probe = lpass_probe,
.remove = lpass_remove,
.id_table = lpass_driver_ids,
.driver = {
.name = "samsung-lpass",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(exynos_lpass_match),
.pm = &lpass_pmops,
},
};
static int __init lpass_driver_init(void)
{
return platform_driver_register(&lpass_driver);
}
subsys_initcall(lpass_driver_init);
#ifdef CONFIG_PM_RUNTIME
static int lpass_driver_rpm_begin(void)
{
pr_debug("%s entered\n", __func__);
pm_runtime_put_sync(&lpass.pdev->dev);
return 0;
}
late_initcall(lpass_driver_rpm_begin);
#endif
/* Module information */
MODULE_AUTHOR("Divya Jaiswal <divya.jswl@samsung.com>");
MODULE_AUTHOR("Chandrasekar R <rcsekar@samsung.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,144 @@
/* sound/soc/samsung/lpass-exynos7420.c
*
* Low Power Audio SubSystem driver for Samsung Exynos
*
* Copyright (c) 2013 Samsung Electronics Co. Ltd.
* Yeongman Seo <yman.seo@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/exynos.h>
#if 0
#include <mach/map.h>
#include <mach/regs-pmu.h>
#endif
#include "lpass.h"
#include "i2s.h"
/* Default ACLK gate for
aclk_dmac, aclk_sramc */
#define INIT_ACLK_GATE_MASK (1 << 31 | 1 << 30)
/* Default PCLK gate for
pclk_wdt0, pclk_wdt1, pclk_slimbus,
pclk_pcm, pclk_i2s, pclk_timer */
#define INIT_PCLK_GATE_MASK (1 << 22 | 1 << 23 | 1 << 24 | \
1 << 26 | 1 << 27 | 1 << 28)
/* Default SCLK gate for
sclk_ca5, sclk_slimbus, sclk_uart,
sclk_i2s, sclk_pcm, sclk_slimbus_clkin */
#define INIT_SCLK_GATE_MASK (1 << 31 | 1 << 30 | 1 << 29 | \
1 << 28 | 1 << 27 | 1 << 26)
static struct lpass_cmu_info {
struct clk *aud_lpass;
struct clk *aud_pll;
} lpass_cmu;
void __iomem *lpass_cmu_save[] = {
NULL, /* endmark */
};
void lpass_init_clk_gate(void)
{
return;
}
void lpass_reset_clk_default(void)
{
return;
}
int lpass_get_clk(struct device *dev, struct lpass_info *lpass)
{
return 0;
}
void lpass_put_clk(struct lpass_info *lpass)
{
return;
}
void lpass_set_mux_osc(void)
{
return;
}
void lpass_set_mux_pll(void)
{
return;
}
int lpass_set_clk_heirachy(struct device *dev)
{
lpass_cmu.aud_lpass = clk_get(dev, "gate_aud_lpass");
if (IS_ERR(lpass_cmu.aud_lpass)) {
dev_err(dev, "aud_lpass clk not found\n");
return -1;
}
lpass_cmu.aud_pll = clk_get(dev, "sclk_aud_pll");
if (IS_ERR(lpass_cmu.aud_pll)) {
dev_err(dev, "sclk_aud_pll not found\n");
goto err;
}
return 0;
err:
clk_put(lpass_cmu.aud_lpass);
return -1;
}
void lpass_enable_pll(bool on)
{
if (on) {
clk_prepare_enable(lpass_cmu.aud_pll);
clk_set_rate(lpass_cmu.aud_pll, 492000000);
#if 0
if (lpass_i2s_master_mode()) {
void *sfr;
u32 cfg;
clk_set_rate(lpass_cmu.aud_pll, 491520000);
/* AUDIO DIVIDER sfrs */
sfr = ioremap(0x114C0000, SZ_4K);
clk_set_rate(lpass_cmu.aud_pll, 491520000);
cfg = readl(sfr + 0x400);
cfg |= 0x9; /* Divider for AUD_CA5 = 10 */
writel(cfg, sfr + 0x400);
cfg = readl(sfr + 0x404);
cfg |= 0xf; /* Divider for AUD_ACLK = 16 */
writel(cfg, sfr + 0x404);
iounmap(sfr);
} else {
clk_set_rate(lpass_cmu.aud_pll, 492000000);
}
#endif
clk_prepare_enable(lpass_cmu.aud_lpass);
} else {
clk_disable_unprepare(lpass_cmu.aud_lpass);
clk_disable_unprepare(lpass_cmu.aud_pll);
}
}
/* Module information */
MODULE_AUTHOR("Hyunwoong Kim, <khw0178.kim@samsung.com>");
MODULE_LICENSE("GPL");

1337
sound/soc/samsung/lpass.c Normal file

File diff suppressed because it is too large Load diff

118
sound/soc/samsung/lpass.h Normal file
View file

@ -0,0 +1,118 @@
/* sound/soc/samsung/lpass.h
*
* ALSA SoC Audio Layer - Samsung Audio Subsystem driver
*
* Copyright (c) 2013 Samsung Electronics Co. Ltd.
* Yeongman Seo <yman.seo@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_SAMSUNG_LPASS_H
#define __SND_SOC_SAMSUNG_LPASS_H
#include <linux/pm_qos.h>
/* SFR */
#define LPASS_VERSION (0x00)
#define LPASS_CA5_SW_RESET (0x04)
#define LPASS_CORE_SW_RESET (0x08)
#define LPASS_MIF_POWER (0x10)
#define LPASS_CA5_BOOTADDR (0x20)
#define LPASS_CA5_DBG (0x30)
#define LPASS_SW_INTR_CA5 (0x40)
#define LPASS_INTR_CA5_STATUS (0x44)
#define LPASS_INTR_CA5_MASK (0x48)
#define LPASS_SW_INTR_CPU (0x50)
#define LPASS_INTR_CPU_STATUS (0x54)
#define LPASS_INTR_CPU_MASK (0x58)
/* SW_RESET */
#define LPASS_SW_RESET_CA5 (1 << 0)
#define LPASS_SW_RESET_SB (1 << 11)
#define LPASS_SW_RESET_UART (1 << 10)
#ifndef CONFIG_SOC_EXYNOS8890
#define LPASS_SW_RESET_PCM (1 << 9)
#define LPASS_SW_RESET_I2S (1 << 8)
#else
#define LPASS_SW_RESET_PCM (1 << 8)
#define LPASS_SW_RESET_I2S (1 << 7)
#endif
#define LPASS_SW_RESET_TIMER (1 << 2)
#define LPASS_SW_RESET_MEM (1 << 1)
#define LPASS_SW_RESET_DMA (1 << 0)
/* Interrupt mask */
#define LPASS_INTR_APM (1 << 9)
#define LPASS_INTR_MIF (1 << 8)
#define LPASS_INTR_TIMER (1 << 7)
#define LPASS_INTR_DMA (1 << 6)
#define LPASS_INTR_GPIO (1 << 5)
#define LPASS_INTR_I2S (1 << 4)
#define LPASS_INTR_PCM (1 << 3)
#define LPASS_INTR_SB (1 << 2)
#define LPASS_INTR_UART (1 << 1)
#define LPASS_INTR_SFR (1 << 0)
struct lpass_info {
spinlock_t lock;
bool valid;
bool enabled;
int ver;
struct platform_device *pdev;
void __iomem *regs;
void __iomem *regs_s;
void __iomem *mem;
int mem_size;
void __iomem *sysmmu;
struct iommu_domain *domain;
struct proc_dir_entry *proc_file;
struct clk *clk_dmac;
struct clk *clk_sramc;
struct clk *clk_intr;
struct clk *clk_timer;
struct regmap *pmureg;
atomic_t dma_use_cnt;
atomic_t use_cnt;
atomic_t stream_cnt;
bool display_on;
bool uhqa_on;
bool i2s_master_mode;
struct pm_qos_request aud_cluster1_qos;
struct pm_qos_request aud_cluster0_qos;
struct pm_qos_request aud_mif_qos;
struct pm_qos_request aud_int_qos;
int cpu_qos;
int kfc_qos;
int mif_qos;
int int_qos;
};
extern void __iomem *lpass_get_regs(void);
extern void __iomem *lpass_get_regs_s(void);
extern void __iomem *lpass_get_mem(void);
extern struct clk *lpass_get_i2s_opclk(int clk_id);
extern void lpass_reg_dump(void);
extern void __iomem *lpass_cmu_save[];
extern int lpass_get_clk(struct device *dev, struct lpass_info *lpass);
extern void lpass_put_clk(struct lpass_info *lpass);
extern int lpass_set_clk_heirachy(struct device *dev);
extern void lpass_set_mux_pll(void);
extern void lpass_set_mux_osc(void);
extern void lpass_enable_pll(bool on);
extern void lpass_retention_pad_reg(void);
extern void lpass_release_pad_reg(void);
extern void lpass_reset_clk_default(void);
extern void lpass_init_clk_gate(void);
extern void update_cp_available(bool);
extern bool lpass_i2s_master_mode(void);
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
extern void lpass_set_cpu_lock(int level);
#endif
#endif /* __SND_SOC_SAMSUNG_LPASS_H */

View file

@ -0,0 +1,412 @@
/*
* neo1973_wm8753.c -- SoC audio for Openmoko Neo1973 and Freerunner devices
*
* Copyright 2007 Openmoko Inc
* Author: Graeme Gregory <graeme@openmoko.org>
* Copyright 2007 Wolfson Microelectronics PLC.
* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
* Copyright 2009 Wolfson Microelectronics
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <sound/soc.h>
#include <mach/gpio-samsung.h>
#include <asm/mach-types.h>
#include "regs-iis.h"
#include "../codecs/wm8753.h"
#include "s3c24xx-i2s.h"
static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
unsigned int pll_out = 0, bclk = 0;
int ret = 0;
unsigned long iis_clkrate;
iis_clkrate = s3c24xx_i2s_get_clockrate();
switch (params_rate(params)) {
case 8000:
case 16000:
pll_out = 12288000;
break;
case 48000:
bclk = WM8753_BCLK_DIV_4;
pll_out = 12288000;
break;
case 96000:
bclk = WM8753_BCLK_DIV_2;
pll_out = 12288000;
break;
case 11025:
bclk = WM8753_BCLK_DIV_16;
pll_out = 11289600;
break;
case 22050:
bclk = WM8753_BCLK_DIV_8;
pll_out = 11289600;
break;
case 44100:
bclk = WM8753_BCLK_DIV_4;
pll_out = 11289600;
break;
case 88200:
bclk = WM8753_BCLK_DIV_2;
pll_out = 11289600;
break;
}
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai,
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
/* set cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai,
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
/* set the codec system clock for DAC and ADC */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* set MCLK division for sample rate */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
S3C2410_IISMOD_32FS);
if (ret < 0)
return ret;
/* set codec BCLK division for sample rate */
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk);
if (ret < 0)
return ret;
/* set prescaler division for sample rate */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
S3C24XX_PRESCALE(4, 4));
if (ret < 0)
return ret;
/* codec PLL input is PCLK/4 */
ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0,
iis_clkrate / 4, pll_out);
if (ret < 0)
return ret;
return 0;
}
static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
/* disable the PLL */
return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0);
}
/*
* Neo1973 WM8753 HiFi DAI opserations.
*/
static struct snd_soc_ops neo1973_hifi_ops = {
.hw_params = neo1973_hifi_hw_params,
.hw_free = neo1973_hifi_hw_free,
};
static int neo1973_voice_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
unsigned int pcmdiv = 0;
int ret = 0;
unsigned long iis_clkrate;
iis_clkrate = s3c24xx_i2s_get_clockrate();
if (params_rate(params) != 8000)
return -EINVAL;
if (params_channels(params) != 1)
return -EINVAL;
pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */
/* todo: gg check mode (DSP_B) against CSR datasheet */
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* set the codec system clock for DAC and ADC */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, 12288000,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* set codec PCM division for sample rate */
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv);
if (ret < 0)
return ret;
/* configure and enable PLL for 12.288MHz output */
ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0,
iis_clkrate / 4, 12288000);
if (ret < 0)
return ret;
return 0;
}
static int neo1973_voice_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
/* disable the PLL */
return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0);
}
static struct snd_soc_ops neo1973_voice_ops = {
.hw_params = neo1973_voice_hw_params,
.hw_free = neo1973_voice_hw_free,
};
static int gta02_speaker_enabled;
static int lm4853_set_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
gta02_speaker_enabled = ucontrol->value.integer.value[0];
gpio_set_value(S3C2410_GPJ(2), !gta02_speaker_enabled);
return 0;
}
static int lm4853_get_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = gta02_speaker_enabled;
return 0;
}
static int lm4853_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
gpio_set_value(S3C2410_GPJ(1), SND_SOC_DAPM_EVENT_OFF(event));
return 0;
}
static const struct snd_soc_dapm_widget neo1973_wm8753_dapm_widgets[] = {
SND_SOC_DAPM_LINE("GSM Line Out", NULL),
SND_SOC_DAPM_LINE("GSM Line In", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Handset Mic", NULL),
SND_SOC_DAPM_SPK("Handset Spk", NULL),
SND_SOC_DAPM_SPK("Stereo Out", lm4853_event),
};
static const struct snd_soc_dapm_route neo1973_wm8753_routes[] = {
/* Connections to the GSM Module */
{"GSM Line Out", NULL, "MONO1"},
{"GSM Line Out", NULL, "MONO2"},
{"RXP", NULL, "GSM Line In"},
{"RXN", NULL, "GSM Line In"},
/* Connections to Headset */
{"MIC1", NULL, "Mic Bias"},
{"Mic Bias", NULL, "Headset Mic"},
/* Call Mic */
{"MIC2", NULL, "Mic Bias"},
{"MIC2N", NULL, "Mic Bias"},
{"Mic Bias", NULL, "Handset Mic"},
/* Connect the ALC pins */
{"ACIN", NULL, "ACOP"},
/* Connections to the amp */
{"Stereo Out", NULL, "LOUT1"},
{"Stereo Out", NULL, "ROUT1"},
/* Call Speaker */
{"Handset Spk", NULL, "LOUT2"},
{"Handset Spk", NULL, "ROUT2"},
};
static const struct snd_kcontrol_new neo1973_wm8753_controls[] = {
SOC_DAPM_PIN_SWITCH("GSM Line Out"),
SOC_DAPM_PIN_SWITCH("GSM Line In"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Handset Mic"),
SOC_DAPM_PIN_SWITCH("Handset Spk"),
SOC_DAPM_PIN_SWITCH("Stereo Out"),
SOC_SINGLE_BOOL_EXT("Amp Spk Switch", 0,
lm4853_get_spk,
lm4853_set_spk),
};
static int neo1973_wm8753_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
/* set endpoints to default off mode */
snd_soc_dapm_disable_pin(&card->dapm, "GSM Line Out");
snd_soc_dapm_disable_pin(&card->dapm, "GSM Line In");
snd_soc_dapm_disable_pin(&card->dapm, "Headset Mic");
snd_soc_dapm_disable_pin(&card->dapm, "Handset Mic");
snd_soc_dapm_disable_pin(&card->dapm, "Stereo Out");
snd_soc_dapm_disable_pin(&card->dapm, "Handset Spk");
/* allow audio paths from the GSM modem to run during suspend */
snd_soc_dapm_ignore_suspend(&card->dapm, "GSM Line Out");
snd_soc_dapm_ignore_suspend(&card->dapm, "GSM Line In");
snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic");
snd_soc_dapm_ignore_suspend(&card->dapm, "Handset Mic");
snd_soc_dapm_ignore_suspend(&card->dapm, "Stereo Out");
snd_soc_dapm_ignore_suspend(&card->dapm, "Handset Spk");
return 0;
}
static struct snd_soc_dai_link neo1973_dai[] = {
{ /* Hifi Playback - for similatious use with voice below */
.name = "WM8753",
.stream_name = "WM8753 HiFi",
.platform_name = "s3c24xx-iis",
.cpu_dai_name = "s3c24xx-iis",
.codec_dai_name = "wm8753-hifi",
.codec_name = "wm8753.0-001a",
.init = neo1973_wm8753_init,
.ops = &neo1973_hifi_ops,
},
{ /* Voice via BT */
.name = "Bluetooth",
.stream_name = "Voice",
.cpu_dai_name = "bt-sco-pcm",
.codec_dai_name = "wm8753-voice",
.codec_name = "wm8753.0-001a",
.ops = &neo1973_voice_ops,
},
};
static struct snd_soc_aux_dev neo1973_aux_devs[] = {
{
.name = "dfbmcs320",
.codec_name = "dfbmcs320.0",
},
};
static struct snd_soc_codec_conf neo1973_codec_conf[] = {
{
.dev_name = "lm4857.0-007c",
.name_prefix = "Amp",
},
};
static const struct gpio neo1973_gta02_gpios[] = {
{ S3C2410_GPJ(2), GPIOF_OUT_INIT_HIGH, "GTA02_HP_IN" },
{ S3C2410_GPJ(1), GPIOF_OUT_INIT_HIGH, "GTA02_AMP_SHUT" },
};
static struct snd_soc_card neo1973 = {
.name = "neo1973",
.owner = THIS_MODULE,
.dai_link = neo1973_dai,
.num_links = ARRAY_SIZE(neo1973_dai),
.aux_dev = neo1973_aux_devs,
.num_aux_devs = ARRAY_SIZE(neo1973_aux_devs),
.codec_conf = neo1973_codec_conf,
.num_configs = ARRAY_SIZE(neo1973_codec_conf),
.controls = neo1973_wm8753_controls,
.num_controls = ARRAY_SIZE(neo1973_wm8753_controls),
.dapm_widgets = neo1973_wm8753_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(neo1973_wm8753_dapm_widgets),
.dapm_routes = neo1973_wm8753_routes,
.num_dapm_routes = ARRAY_SIZE(neo1973_wm8753_routes),
.fully_routed = true,
};
static struct platform_device *neo1973_snd_device;
static int __init neo1973_init(void)
{
int ret;
if (!machine_is_neo1973_gta02())
return -ENODEV;
if (machine_is_neo1973_gta02()) {
neo1973.name = "neo1973gta02";
neo1973.num_aux_devs = 1;
ret = gpio_request_array(neo1973_gta02_gpios,
ARRAY_SIZE(neo1973_gta02_gpios));
if (ret)
return ret;
}
neo1973_snd_device = platform_device_alloc("soc-audio", -1);
if (!neo1973_snd_device) {
ret = -ENOMEM;
goto err_gpio_free;
}
platform_set_drvdata(neo1973_snd_device, &neo1973);
ret = platform_device_add(neo1973_snd_device);
if (ret)
goto err_put_device;
return 0;
err_put_device:
platform_device_put(neo1973_snd_device);
err_gpio_free:
if (machine_is_neo1973_gta02()) {
gpio_free_array(neo1973_gta02_gpios,
ARRAY_SIZE(neo1973_gta02_gpios));
}
return ret;
}
module_init(neo1973_init);
static void __exit neo1973_exit(void)
{
platform_device_unregister(neo1973_snd_device);
if (machine_is_neo1973_gta02()) {
gpio_free_array(neo1973_gta02_gpios,
ARRAY_SIZE(neo1973_gta02_gpios));
}
}
module_exit(neo1973_exit);
/* Module information */
MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org");
MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 and Frerunner");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,177 @@
/*
* Copyright (C) 2014 Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/of.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "i2s.h"
struct odroidx2_drv_data {
const struct snd_soc_dapm_widget *dapm_widgets;
unsigned int num_dapm_widgets;
};
/* The I2S CDCLK output clock frequency for the MAX98090 codec */
#define MAX98090_MCLK 19200000
static int odroidx2_late_probe(struct snd_soc_card *card)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
struct snd_soc_dai *cpu_dai = card->rtd[0].cpu_dai;
int ret;
ret = snd_soc_dai_set_sysclk(codec_dai, 0, MAX98090_MCLK,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* Set the cpu DAI configuration in order to use CDCLK */
return snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
0, SND_SOC_CLOCK_OUT);
}
static const struct snd_soc_dapm_widget odroidx2_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_MIC("DMIC", NULL),
};
static const struct snd_soc_dapm_widget odroidu3_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Speakers", NULL),
};
static struct snd_soc_dai_link odroidx2_dai[] = {
{
.name = "MAX98090",
.stream_name = "MAX98090 PCM",
.codec_dai_name = "HiFi",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
}
};
static struct snd_soc_card odroidx2 = {
.owner = THIS_MODULE,
.dai_link = odroidx2_dai,
.num_links = ARRAY_SIZE(odroidx2_dai),
.fully_routed = true,
.late_probe = odroidx2_late_probe,
};
static const struct odroidx2_drv_data odroidx2_drvdata = {
.dapm_widgets = odroidx2_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(odroidx2_dapm_widgets),
};
static const struct odroidx2_drv_data odroidu3_drvdata = {
.dapm_widgets = odroidu3_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(odroidu3_dapm_widgets),
};
static const struct of_device_id odroidx2_audio_of_match[] = {
{
.compatible = "samsung,odroidx2-audio",
.data = &odroidx2_drvdata,
}, {
.compatible = "samsung,odroidu3-audio",
.data = &odroidu3_drvdata,
},
{ },
};
MODULE_DEVICE_TABLE(of, odroidx2_audio_of_match);
static int odroidx2_audio_probe(struct platform_device *pdev)
{
struct device_node *snd_node = pdev->dev.of_node;
struct snd_soc_card *card = &odroidx2;
struct device_node *i2s_node, *codec_node;
struct odroidx2_drv_data *dd;
const struct of_device_id *of_id;
int ret;
of_id = of_match_node(odroidx2_audio_of_match, snd_node);
dd = (struct odroidx2_drv_data *)of_id->data;
card->num_dapm_widgets = dd->num_dapm_widgets;
card->dapm_widgets = dd->dapm_widgets;
card->dev = &pdev->dev;
ret = snd_soc_of_parse_card_name(card, "samsung,model");
if (ret < 0)
return ret;
ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
if (ret < 0)
return ret;
codec_node = of_parse_phandle(snd_node, "samsung,audio-codec", 0);
if (!codec_node) {
dev_err(&pdev->dev,
"Failed parsing samsung,i2s-codec property\n");
return -EINVAL;
}
i2s_node = of_parse_phandle(snd_node, "samsung,i2s-controller", 0);
if (!i2s_node) {
dev_err(&pdev->dev,
"Failed parsing samsung,i2s-controller property\n");
ret = -EINVAL;
goto err_put_codec_n;
}
odroidx2_dai[0].codec_of_node = codec_node;
odroidx2_dai[0].cpu_of_node = i2s_node;
odroidx2_dai[0].platform_of_node = i2s_node;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
goto err_put_i2s_n;
}
return 0;
err_put_i2s_n:
of_node_put(i2s_node);
err_put_codec_n:
of_node_put(codec_node);
return ret;
}
static int odroidx2_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
of_node_put((struct device_node *)odroidx2_dai[0].cpu_of_node);
of_node_put((struct device_node *)odroidx2_dai[0].codec_of_node);
return 0;
}
static struct platform_driver odroidx2_audio_driver = {
.driver = {
.name = "odroidx2-audio",
.owner = THIS_MODULE,
.of_match_table = odroidx2_audio_of_match,
.pm = &snd_soc_pm_ops,
},
.probe = odroidx2_audio_probe,
.remove = odroidx2_audio_remove,
};
module_platform_driver(odroidx2_audio_driver);
MODULE_AUTHOR("Chen Zhen <zhen1.chen@samsung.com>");
MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC Odroid X2/U3 Audio Support");
MODULE_LICENSE("GPL v2");

639
sound/soc/samsung/pcm.c Normal file
View file

@ -0,0 +1,639 @@
/* sound/soc/samsung/pcm.c
*
* ALSA SoC Audio Layer - S3C PCM-Controller driver
*
* Copyright (c) 2009 Samsung Electronics Co. Ltd
* Author: Jaswinder Singh <jassisinghbrar@gmail.com>
* based upon I2S drivers by Ben Dooks.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <linux/platform_data/asoc-s3c.h>
#include "dma.h"
#include "pcm.h"
/*Register Offsets */
#define S3C_PCM_CTL 0x00
#define S3C_PCM_CLKCTL 0x04
#define S3C_PCM_TXFIFO 0x08
#define S3C_PCM_RXFIFO 0x0C
#define S3C_PCM_IRQCTL 0x10
#define S3C_PCM_IRQSTAT 0x14
#define S3C_PCM_FIFOSTAT 0x18
#define S3C_PCM_CLRINT 0x20
/* PCM_CTL Bit-Fields */
#define S3C_PCM_CTL_TXDIPSTICK_MASK 0x3f
#define S3C_PCM_CTL_TXDIPSTICK_SHIFT 13
#define S3C_PCM_CTL_RXDIPSTICK_MASK 0x3f
#define S3C_PCM_CTL_RXDIPSTICK_SHIFT 7
#define S3C_PCM_CTL_TXDMA_EN (0x1 << 6)
#define S3C_PCM_CTL_RXDMA_EN (0x1 << 5)
#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1 << 4)
#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1 << 3)
#define S3C_PCM_CTL_TXFIFO_EN (0x1 << 2)
#define S3C_PCM_CTL_RXFIFO_EN (0x1 << 1)
#define S3C_PCM_CTL_ENABLE (0x1 << 0)
/* PCM_CLKCTL Bit-Fields */
#define S3C_PCM_CLKCTL_SERCLK_EN (0x1 << 19)
#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1 << 18)
#define S3C_PCM_CLKCTL_SCLKDIV_MASK 0x1ff
#define S3C_PCM_CLKCTL_SYNCDIV_MASK 0x1ff
#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT 9
#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT 0
/* PCM_TXFIFO Bit-Fields */
#define S3C_PCM_TXFIFO_DVALID (0x1 << 16)
#define S3C_PCM_TXFIFO_DATA_MSK (0xffff << 0)
/* PCM_RXFIFO Bit-Fields */
#define S3C_PCM_RXFIFO_DVALID (0x1 << 16)
#define S3C_PCM_RXFIFO_DATA_MSK (0xffff << 0)
/* PCM_IRQCTL Bit-Fields */
#define S3C_PCM_IRQCTL_IRQEN (0x1 << 14)
#define S3C_PCM_IRQCTL_WRDEN (0x1 << 12)
#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1 << 11)
#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1 << 10)
#define S3C_PCM_IRQCTL_TXFULLEN (0x1 << 9)
#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1 << 8)
#define S3C_PCM_IRQCTL_TXSTARVEN (0x1 << 7)
#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1 << 6)
#define S3C_PCM_IRQCTL_RXEMPTEN (0x1 << 5)
#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1 << 4)
#define S3C_PCM_IRQCTL_RXFULLEN (0x1 << 3)
#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1 << 2)
#define S3C_PCM_IRQCTL_RXSTARVEN (0x1 << 1)
#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1 << 0)
/* PCM_IRQSTAT Bit-Fields */
#define S3C_PCM_IRQSTAT_IRQPND (0x1 << 13)
#define S3C_PCM_IRQSTAT_WRD_XFER (0x1 << 12)
#define S3C_PCM_IRQSTAT_TXEMPTY (0x1 << 11)
#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1 << 10)
#define S3C_PCM_IRQSTAT_TXFULL (0x1 << 9)
#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1 << 8)
#define S3C_PCM_IRQSTAT_TXSTARV (0x1 << 7)
#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1 << 6)
#define S3C_PCM_IRQSTAT_RXEMPT (0x1 << 5)
#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1 << 4)
#define S3C_PCM_IRQSTAT_RXFULL (0x1 << 3)
#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1 << 2)
#define S3C_PCM_IRQSTAT_RXSTARV (0x1 << 1)
#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1 << 0)
/* PCM_FIFOSTAT Bit-Fields */
#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f << 14)
#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1 << 13)
#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1 << 12)
#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1 << 11)
#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1 << 10)
#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f << 4)
#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1 << 3)
#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1 << 2)
#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1 << 1)
#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1 << 0)
/**
* struct s3c_pcm_info - S3C PCM Controller information
* @dev: The parent device passed to use from the probe.
* @regs: The pointer to the device register block.
* @dma_playback: DMA information for playback channel.
* @dma_capture: DMA information for capture channel.
*/
struct s3c_pcm_info {
spinlock_t lock;
struct device *dev;
void __iomem *regs;
unsigned int sclk_per_fs;
/* Whether to keep PCMSCLK enabled even when idle(no active xfer) */
unsigned int idleclk;
struct clk *pclk;
struct clk *cclk;
struct s3c_dma_params *dma_playback;
struct s3c_dma_params *dma_capture;
};
static struct s3c_dma_params s3c_pcm_stereo_out[] = {
[0] = {
.dma_size = 4,
},
[1] = {
.dma_size = 4,
},
};
static struct s3c_dma_params s3c_pcm_stereo_in[] = {
[0] = {
.dma_size = 4,
},
[1] = {
.dma_size = 4,
},
};
static struct s3c_pcm_info s3c_pcm[2];
static void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on)
{
void __iomem *regs = pcm->regs;
u32 ctl, clkctl;
clkctl = readl(regs + S3C_PCM_CLKCTL);
ctl = readl(regs + S3C_PCM_CTL);
ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK
<< S3C_PCM_CTL_TXDIPSTICK_SHIFT);
if (on) {
ctl |= S3C_PCM_CTL_TXDMA_EN;
ctl |= S3C_PCM_CTL_TXFIFO_EN;
ctl |= S3C_PCM_CTL_ENABLE;
ctl |= (0x4<<S3C_PCM_CTL_TXDIPSTICK_SHIFT);
clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
} else {
ctl &= ~S3C_PCM_CTL_TXDMA_EN;
ctl &= ~S3C_PCM_CTL_TXFIFO_EN;
if (!(ctl & S3C_PCM_CTL_RXFIFO_EN)) {
ctl &= ~S3C_PCM_CTL_ENABLE;
if (!pcm->idleclk)
clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
}
}
writel(clkctl, regs + S3C_PCM_CLKCTL);
writel(ctl, regs + S3C_PCM_CTL);
}
static void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on)
{
void __iomem *regs = pcm->regs;
u32 ctl, clkctl;
ctl = readl(regs + S3C_PCM_CTL);
clkctl = readl(regs + S3C_PCM_CLKCTL);
ctl &= ~(S3C_PCM_CTL_RXDIPSTICK_MASK
<< S3C_PCM_CTL_RXDIPSTICK_SHIFT);
if (on) {
ctl |= S3C_PCM_CTL_RXDMA_EN;
ctl |= S3C_PCM_CTL_RXFIFO_EN;
ctl |= S3C_PCM_CTL_ENABLE;
ctl |= (0x20<<S3C_PCM_CTL_RXDIPSTICK_SHIFT);
clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
} else {
ctl &= ~S3C_PCM_CTL_RXDMA_EN;
ctl &= ~S3C_PCM_CTL_RXFIFO_EN;
if (!(ctl & S3C_PCM_CTL_TXFIFO_EN)) {
ctl &= ~S3C_PCM_CTL_ENABLE;
if (!pcm->idleclk)
clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
}
}
writel(clkctl, regs + S3C_PCM_CLKCTL);
writel(ctl, regs + S3C_PCM_CTL);
}
static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai);
unsigned long flags;
dev_dbg(pcm->dev, "Entered %s\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
spin_lock_irqsave(&pcm->lock, flags);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c_pcm_snd_rxctrl(pcm, 1);
else
s3c_pcm_snd_txctrl(pcm, 1);
spin_unlock_irqrestore(&pcm->lock, flags);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
spin_lock_irqsave(&pcm->lock, flags);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c_pcm_snd_rxctrl(pcm, 0);
else
s3c_pcm_snd_txctrl(pcm, 0);
spin_unlock_irqrestore(&pcm->lock, flags);
break;
default:
return -EINVAL;
}
return 0;
}
static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *socdai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai);
void __iomem *regs = pcm->regs;
struct clk *clk;
int sclk_div, sync_div;
unsigned long flags;
u32 clkctl;
dev_dbg(pcm->dev, "Entered %s\n", __func__);
/* Strictly check for sample size */
switch (params_width(params)) {
case 16:
break;
default:
return -EINVAL;
}
spin_lock_irqsave(&pcm->lock, flags);
/* Get hold of the PCMSOURCE_CLK */
clkctl = readl(regs + S3C_PCM_CLKCTL);
if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK)
clk = pcm->pclk;
else
clk = pcm->cclk;
/* Set the SCLK divider */
sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs /
params_rate(params) / 2 - 1;
clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK
<< S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK)
<< S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
/* Set the SYNC divider */
sync_div = pcm->sclk_per_fs - 1;
clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK
<< S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK)
<< S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
writel(clkctl, regs + S3C_PCM_CLKCTL);
spin_unlock_irqrestore(&pcm->lock, flags);
dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n",
clk_get_rate(clk), pcm->sclk_per_fs,
sclk_div, sync_div);
return 0;
}
static int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
void __iomem *regs = pcm->regs;
unsigned long flags;
int ret = 0;
u32 ctl;
dev_dbg(pcm->dev, "Entered %s\n", __func__);
spin_lock_irqsave(&pcm->lock, flags);
ctl = readl(regs + S3C_PCM_CTL);
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_IB_NF:
/* Nothing to do, IB_NF by default */
break;
default:
dev_err(pcm->dev, "Unsupported clock inversion!\n");
ret = -EINVAL;
goto exit;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
/* Nothing to do, Master by default */
break;
default:
dev_err(pcm->dev, "Unsupported master/slave format!\n");
ret = -EINVAL;
goto exit;
}
switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
case SND_SOC_DAIFMT_CONT:
pcm->idleclk = 1;
break;
case SND_SOC_DAIFMT_GATED:
pcm->idleclk = 0;
break;
default:
dev_err(pcm->dev, "Invalid Clock gating request!\n");
ret = -EINVAL;
goto exit;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_A:
ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
break;
case SND_SOC_DAIFMT_DSP_B:
ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
break;
default:
dev_err(pcm->dev, "Unsupported data format!\n");
ret = -EINVAL;
goto exit;
}
writel(ctl, regs + S3C_PCM_CTL);
exit:
spin_unlock_irqrestore(&pcm->lock, flags);
return ret;
}
static int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
switch (div_id) {
case S3C_PCM_SCLK_PER_FS:
pcm->sclk_per_fs = div;
break;
default:
return -EINVAL;
}
return 0;
}
static int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
void __iomem *regs = pcm->regs;
u32 clkctl = readl(regs + S3C_PCM_CLKCTL);
switch (clk_id) {
case S3C_PCM_CLKSRC_PCLK:
clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
break;
case S3C_PCM_CLKSRC_MUX:
clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
if (clk_get_rate(pcm->cclk) != freq)
clk_set_rate(pcm->cclk, freq);
break;
default:
return -EINVAL;
}
writel(clkctl, regs + S3C_PCM_CLKCTL);
return 0;
}
static const struct snd_soc_dai_ops s3c_pcm_dai_ops = {
.set_sysclk = s3c_pcm_set_sysclk,
.set_clkdiv = s3c_pcm_set_clkdiv,
.trigger = s3c_pcm_trigger,
.hw_params = s3c_pcm_hw_params,
.set_fmt = s3c_pcm_set_fmt,
};
static int s3c_pcm_dai_probe(struct snd_soc_dai *dai)
{
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, pcm->dma_playback, pcm->dma_capture);
return 0;
}
#define S3C_PCM_RATES SNDRV_PCM_RATE_8000_96000
#define S3C_PCM_DAI_DECLARE \
.symmetric_rates = 1, \
.probe = s3c_pcm_dai_probe, \
.ops = &s3c_pcm_dai_ops, \
.playback = { \
.channels_min = 2, \
.channels_max = 2, \
.rates = S3C_PCM_RATES, \
.formats = SNDRV_PCM_FMTBIT_S16_LE, \
}, \
.capture = { \
.channels_min = 2, \
.channels_max = 2, \
.rates = S3C_PCM_RATES, \
.formats = SNDRV_PCM_FMTBIT_S16_LE, \
}
static struct snd_soc_dai_driver s3c_pcm_dai[] = {
[0] = {
.name = "samsung-pcm.0",
S3C_PCM_DAI_DECLARE,
},
[1] = {
.name = "samsung-pcm.1",
S3C_PCM_DAI_DECLARE,
},
};
static const struct snd_soc_component_driver s3c_pcm_component = {
.name = "s3c-pcm",
};
static int s3c_pcm_dev_probe(struct platform_device *pdev)
{
struct s3c_pcm_info *pcm;
struct resource *mem_res, *dmatx_res, *dmarx_res;
struct s3c_audio_pdata *pcm_pdata;
int ret;
/* Check for valid device index */
if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) {
dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
return -EINVAL;
}
pcm_pdata = pdev->dev.platform_data;
/* Check for availability of necessary resource */
dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dmatx_res) {
dev_err(&pdev->dev, "Unable to get PCM-TX dma resource\n");
return -ENXIO;
}
dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!dmarx_res) {
dev_err(&pdev->dev, "Unable to get PCM-RX dma resource\n");
return -ENXIO;
}
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_res) {
dev_err(&pdev->dev, "Unable to get register resource\n");
return -ENXIO;
}
if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) {
dev_err(&pdev->dev, "Unable to configure gpio\n");
return -EINVAL;
}
pcm = &s3c_pcm[pdev->id];
pcm->dev = &pdev->dev;
spin_lock_init(&pcm->lock);
/* Default is 128fs */
pcm->sclk_per_fs = 128;
pcm->cclk = devm_clk_get(&pdev->dev, "audio-bus");
if (IS_ERR(pcm->cclk)) {
dev_err(&pdev->dev, "failed to get audio-bus\n");
ret = PTR_ERR(pcm->cclk);
goto err1;
}
clk_prepare_enable(pcm->cclk);
/* record our pcm structure for later use in the callbacks */
dev_set_drvdata(&pdev->dev, pcm);
if (!request_mem_region(mem_res->start,
resource_size(mem_res), "samsung-pcm")) {
dev_err(&pdev->dev, "Unable to request register region\n");
ret = -EBUSY;
goto err2;
}
pcm->regs = ioremap(mem_res->start, 0x100);
if (pcm->regs == NULL) {
dev_err(&pdev->dev, "cannot ioremap registers\n");
ret = -ENXIO;
goto err3;
}
pcm->pclk = devm_clk_get(&pdev->dev, "pcm");
if (IS_ERR(pcm->pclk)) {
dev_err(&pdev->dev, "failed to get pcm_clock\n");
ret = -ENOENT;
goto err4;
}
clk_prepare_enable(pcm->pclk);
s3c_pcm_stereo_in[pdev->id].dma_addr = mem_res->start
+ S3C_PCM_RXFIFO;
s3c_pcm_stereo_out[pdev->id].dma_addr = mem_res->start
+ S3C_PCM_TXFIFO;
s3c_pcm_stereo_in[pdev->id].channel = dmarx_res->start;
s3c_pcm_stereo_out[pdev->id].channel = dmatx_res->start;
pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id];
pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id];
pm_runtime_enable(&pdev->dev);
ret = devm_snd_soc_register_component(&pdev->dev, &s3c_pcm_component,
&s3c_pcm_dai[pdev->id], 1);
if (ret != 0) {
dev_err(&pdev->dev, "failed to get register DAI: %d\n", ret);
goto err5;
}
ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
goto err5;
}
return 0;
err5:
clk_disable_unprepare(pcm->pclk);
err4:
iounmap(pcm->regs);
err3:
release_mem_region(mem_res->start, resource_size(mem_res));
err2:
clk_disable_unprepare(pcm->cclk);
err1:
return ret;
}
static int s3c_pcm_dev_remove(struct platform_device *pdev)
{
struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id];
struct resource *mem_res;
pm_runtime_disable(&pdev->dev);
iounmap(pcm->regs);
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
release_mem_region(mem_res->start, resource_size(mem_res));
clk_disable_unprepare(pcm->cclk);
clk_disable_unprepare(pcm->pclk);
return 0;
}
static struct platform_driver s3c_pcm_driver = {
.probe = s3c_pcm_dev_probe,
.remove = s3c_pcm_dev_remove,
.driver = {
.name = "samsung-pcm",
.owner = THIS_MODULE,
},
};
module_platform_driver(s3c_pcm_driver);
/* Module information */
MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
MODULE_DESCRIPTION("S3C PCM Controller Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-pcm");

17
sound/soc/samsung/pcm.h Normal file
View file

@ -0,0 +1,17 @@
/* sound/soc/samsung/pcm.h
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#ifndef __S3C_PCM_H
#define __S3C_PCM_H __FILE__
#define S3C_PCM_CLKSRC_PCLK 0
#define S3C_PCM_CLKSRC_MUX 1
#define S3C_PCM_SCLK_PER_FS 0
#endif /* __S3C_PCM_H */

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2006 Simtec Electronics <linux@simtec.co.uk>
* http://www.simtec.co.uk/products/SWLINUX/
*
* 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.
*
* S3C2440 AC97 Controller
*/
#ifndef __SAMSUNG_REGS_AC97_H__
#define __SAMSUNG_REGS_AC97_H__
#define S3C_AC97_GLBCTRL (0x00)
#define S3C_AC97_GLBCTRL_CODECREADYIE (1<<22)
#define S3C_AC97_GLBCTRL_PCMOUTURIE (1<<21)
#define S3C_AC97_GLBCTRL_PCMINORIE (1<<20)
#define S3C_AC97_GLBCTRL_MICINORIE (1<<19)
#define S3C_AC97_GLBCTRL_PCMOUTTIE (1<<18)
#define S3C_AC97_GLBCTRL_PCMINTIE (1<<17)
#define S3C_AC97_GLBCTRL_MICINTIE (1<<16)
#define S3C_AC97_GLBCTRL_PCMOUTTM_OFF (0<<12)
#define S3C_AC97_GLBCTRL_PCMOUTTM_PIO (1<<12)
#define S3C_AC97_GLBCTRL_PCMOUTTM_DMA (2<<12)
#define S3C_AC97_GLBCTRL_PCMOUTTM_MASK (3<<12)
#define S3C_AC97_GLBCTRL_PCMINTM_OFF (0<<10)
#define S3C_AC97_GLBCTRL_PCMINTM_PIO (1<<10)
#define S3C_AC97_GLBCTRL_PCMINTM_DMA (2<<10)
#define S3C_AC97_GLBCTRL_PCMINTM_MASK (3<<10)
#define S3C_AC97_GLBCTRL_MICINTM_OFF (0<<8)
#define S3C_AC97_GLBCTRL_MICINTM_PIO (1<<8)
#define S3C_AC97_GLBCTRL_MICINTM_DMA (2<<8)
#define S3C_AC97_GLBCTRL_MICINTM_MASK (3<<8)
#define S3C_AC97_GLBCTRL_TRANSFERDATAENABLE (1<<3)
#define S3C_AC97_GLBCTRL_ACLINKON (1<<2)
#define S3C_AC97_GLBCTRL_WARMRESET (1<<1)
#define S3C_AC97_GLBCTRL_COLDRESET (1<<0)
#define S3C_AC97_GLBSTAT (0x04)
#define S3C_AC97_GLBSTAT_CODECREADY (1<<22)
#define S3C_AC97_GLBSTAT_PCMOUTUR (1<<21)
#define S3C_AC97_GLBSTAT_PCMINORI (1<<20)
#define S3C_AC97_GLBSTAT_MICINORI (1<<19)
#define S3C_AC97_GLBSTAT_PCMOUTTI (1<<18)
#define S3C_AC97_GLBSTAT_PCMINTI (1<<17)
#define S3C_AC97_GLBSTAT_MICINTI (1<<16)
#define S3C_AC97_GLBSTAT_MAINSTATE_IDLE (0<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_INIT (1<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_READY (2<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE (3<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_LP (4<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_WARM (5<<0)
#define S3C_AC97_CODEC_CMD (0x08)
#define S3C_AC97_CODEC_CMD_READ (1<<23)
#define S3C_AC97_STAT (0x0c)
#define S3C_AC97_PCM_ADDR (0x10)
#define S3C_AC97_PCM_DATA (0x18)
#define S3C_AC97_MIC_DATA (0x1C)
#endif /* __SAMSUNG_REGS_AC97_H__ */

View file

@ -0,0 +1,115 @@
/* linux/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h
*
* Copyright 2007 Simtec Electronics <linux@simtec.co.uk>
* http://armlinux.simtec.co.uk/
*
* 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.
*
* S3C2412 IIS register definition
*/
#ifndef __ASM_ARCH_REGS_S3C2412_IIS_H
#define __ASM_ARCH_REGS_S3C2412_IIS_H
#define S3C2412_IISCON (0x00)
#define S3C2412_IISMOD (0x04)
#define S3C2412_IISFIC (0x08)
#define S3C2412_IISPSR (0x0C)
#define S3C2412_IISTXD (0x10)
#define S3C2412_IISRXD (0x14)
#define S5PC1XX_IISFICS 0x18
#define S5PC1XX_IISTXDS 0x1C
#define S5PC1XX_IISCON_SW_RST (1 << 31)
#define S5PC1XX_IISCON_FRXOFSTATUS (1 << 26)
#define S5PC1XX_IISCON_FRXORINTEN (1 << 25)
#define S5PC1XX_IISCON_FTXSURSTAT (1 << 24)
#define S5PC1XX_IISCON_FTXSURINTEN (1 << 23)
#define S5PC1XX_IISCON_TXSDMAPAUSE (1 << 20)
#define S5PC1XX_IISCON_TXSDMACTIVE (1 << 18)
#define S3C64XX_IISCON_FTXURSTATUS (1 << 17)
#define S3C64XX_IISCON_FTXURINTEN (1 << 16)
#define S3C64XX_IISCON_TXFIFO2_EMPTY (1 << 15)
#define S3C64XX_IISCON_TXFIFO1_EMPTY (1 << 14)
#define S3C64XX_IISCON_TXFIFO2_FULL (1 << 13)
#define S3C64XX_IISCON_TXFIFO1_FULL (1 << 12)
#define S3C2412_IISCON_LRINDEX (1 << 11)
#define S3C2412_IISCON_TXFIFO_EMPTY (1 << 10)
#define S3C2412_IISCON_RXFIFO_EMPTY (1 << 9)
#define S3C2412_IISCON_TXFIFO_FULL (1 << 8)
#define S3C2412_IISCON_RXFIFO_FULL (1 << 7)
#define S3C2412_IISCON_TXDMA_PAUSE (1 << 6)
#define S3C2412_IISCON_RXDMA_PAUSE (1 << 5)
#define S3C2412_IISCON_TXCH_PAUSE (1 << 4)
#define S3C2412_IISCON_RXCH_PAUSE (1 << 3)
#define S3C2412_IISCON_TXDMA_ACTIVE (1 << 2)
#define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1)
#define S3C2412_IISCON_IIS_ACTIVE (1 << 0)
#define S5PC1XX_IISMOD_OPCLK_CDCLK_OUT (0 << 30)
#define S5PC1XX_IISMOD_OPCLK_CDCLK_IN (1 << 30)
#define S5PC1XX_IISMOD_OPCLK_BCLK_OUT (2 << 30)
#define S5PC1XX_IISMOD_OPCLK_PCLK (3 << 30)
#define S5PC1XX_IISMOD_OPCLK_MASK (3 << 30)
#define S5PC1XX_IISMOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
#define S5PC1XX_IISMOD_BLCS_MASK 0x3
#define S5PC1XX_IISMOD_BLCS_SHIFT 26
#define S5PC1XX_IISMOD_BLCP_MASK 0x3
#define S5PC1XX_IISMOD_BLCP_SHIFT 24
#define S3C64XX_IISMOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */
#define S3C64XX_IISMOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */
#define S3C64XX_IISMOD_C1DD_HHALF (1 << 19)
#define S3C64XX_IISMOD_C1DD_LHALF (1 << 18)
#define S3C64XX_IISMOD_DC2_EN (1 << 17)
#define S3C64XX_IISMOD_DC1_EN (1 << 16)
#define S3C64XX_IISMOD_BLC_16BIT (0 << 13)
#define S3C64XX_IISMOD_BLC_8BIT (1 << 13)
#define S3C64XX_IISMOD_BLC_24BIT (2 << 13)
#define S3C64XX_IISMOD_BLC_MASK (3 << 13)
#define S3C2412_IISMOD_IMS_SYSMUX (1 << 10)
#define S3C2412_IISMOD_SLAVE (1 << 11)
#define S3C2412_IISMOD_MODE_TXONLY (0 << 8)
#define S3C2412_IISMOD_MODE_RXONLY (1 << 8)
#define S3C2412_IISMOD_MODE_TXRX (2 << 8)
#define S3C2412_IISMOD_MODE_MASK (3 << 8)
#define S3C2412_IISMOD_LR_LLOW (0 << 7)
#define S3C2412_IISMOD_LR_RLOW (1 << 7)
#define S3C2412_IISMOD_SDF_IIS (0 << 5)
#define S3C2412_IISMOD_SDF_MSB (1 << 5)
#define S3C2412_IISMOD_SDF_LSB (2 << 5)
#define S3C2412_IISMOD_SDF_MASK (3 << 5)
#define S3C2412_IISMOD_RCLK_256FS (0 << 3)
#define S3C2412_IISMOD_RCLK_512FS (1 << 3)
#define S3C2412_IISMOD_RCLK_384FS (2 << 3)
#define S3C2412_IISMOD_RCLK_768FS (3 << 3)
#define S3C2412_IISMOD_RCLK_MASK (3 << 3)
#define S3C2412_IISMOD_BCLK_32FS (0 << 1)
#define S3C2412_IISMOD_BCLK_48FS (1 << 1)
#define S3C2412_IISMOD_BCLK_16FS (2 << 1)
#define S3C2412_IISMOD_BCLK_24FS (3 << 1)
#define S3C2412_IISMOD_BCLK_MASK (3 << 1)
#define S3C2412_IISMOD_8BIT (1 << 0)
#define S3C64XX_IISMOD_CDCLKCON (1 << 12)
#define S3C2412_IISPSR_PSREN (1 << 15)
#define S3C64XX_IISFIC_TX2COUNT(x) (((x) >> 24) & 0xf)
#define S3C64XX_IISFIC_TX1COUNT(x) (((x) >> 16) & 0xf)
#define S3C2412_IISFIC_TXFLUSH (1 << 15)
#define S3C2412_IISFIC_RXFLUSH (1 << 7)
#define S3C2412_IISFIC_TXCOUNT(x) (((x) >> 8) & 0xf)
#define S3C2412_IISFIC_RXCOUNT(x) (((x) >> 0) & 0xf)
#define S5PC1XX_IISFICS_TXFLUSH (1 << 15)
#define S5PC1XX_IISFICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
#endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2003 Simtec Electronics <linux@simtec.co.uk>
* http://www.simtec.co.uk/products/SWLINUX/
*
* 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.
*
* S3C2410 IIS register definition
*/
#ifndef __SAMSUNG_REGS_IIS_H__
#define __SAMSUNG_REGS_IIS_H__
#define S3C2410_IISCON (0x00)
#define S3C2410_IISCON_LRINDEX (1 << 8)
#define S3C2410_IISCON_TXFIFORDY (1 << 7)
#define S3C2410_IISCON_RXFIFORDY (1 << 6)
#define S3C2410_IISCON_TXDMAEN (1 << 5)
#define S3C2410_IISCON_RXDMAEN (1 << 4)
#define S3C2410_IISCON_TXIDLE (1 << 3)
#define S3C2410_IISCON_RXIDLE (1 << 2)
#define S3C2410_IISCON_PSCEN (1 << 1)
#define S3C2410_IISCON_IISEN (1 << 0)
#define S3C2410_IISMOD (0x04)
#define S3C2440_IISMOD_MPLL (1 << 9)
#define S3C2410_IISMOD_SLAVE (1 << 8)
#define S3C2410_IISMOD_NOXFER (0 << 6)
#define S3C2410_IISMOD_RXMODE (1 << 6)
#define S3C2410_IISMOD_TXMODE (2 << 6)
#define S3C2410_IISMOD_TXRXMODE (3 << 6)
#define S3C2410_IISMOD_LR_LLOW (0 << 5)
#define S3C2410_IISMOD_LR_RLOW (1 << 5)
#define S3C2410_IISMOD_IIS (0 << 4)
#define S3C2410_IISMOD_MSB (1 << 4)
#define S3C2410_IISMOD_8BIT (0 << 3)
#define S3C2410_IISMOD_16BIT (1 << 3)
#define S3C2410_IISMOD_BITMASK (1 << 3)
#define S3C2410_IISMOD_256FS (0 << 2)
#define S3C2410_IISMOD_384FS (1 << 2)
#define S3C2410_IISMOD_16FS (0 << 0)
#define S3C2410_IISMOD_32FS (1 << 0)
#define S3C2410_IISMOD_48FS (2 << 0)
#define S3C2410_IISMOD_FS_MASK (3 << 0)
#define S3C2410_IISPSR (0x08)
#define S3C2410_IISPSR_INTMASK (31 << 5)
#define S3C2410_IISPSR_INTSHIFT (5)
#define S3C2410_IISPSR_EXTMASK (31 << 0)
#define S3C2410_IISPSR_EXTSHFIT (0)
#define S3C2410_IISFCON (0x0c)
#define S3C2410_IISFCON_TXDMA (1 << 15)
#define S3C2410_IISFCON_RXDMA (1 << 14)
#define S3C2410_IISFCON_TXENABLE (1 << 13)
#define S3C2410_IISFCON_RXENABLE (1 << 12)
#define S3C2410_IISFCON_TXMASK (0x3f << 6)
#define S3C2410_IISFCON_TXSHIFT (6)
#define S3C2410_IISFCON_RXMASK (0x3f)
#define S3C2410_IISFCON_RXSHIFT (0)
#define S3C2410_IISFIFO (0x10)
#endif /* __SAMSUNG_REGS_IIS_H__ */

View file

@ -0,0 +1,300 @@
/*
* rx1950.c -- ALSA Soc Audio Layer
*
* Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com>
*
* Based on smdk2440.c and magician.c
*
* Authors: Graeme Gregory graeme.gregory@wolfsonmicro.com
* Philipp Zabel <philipp.zabel@gmail.com>
* Denis Grigoriev <dgreenday@gmail.com>
* Vasily Khoruzhick <anarsoul@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <mach/gpio-samsung.h>
#include "regs-iis.h"
#include <asm/mach-types.h>
#include "s3c24xx-i2s.h"
static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd);
static int rx1950_uda1380_card_remove(struct snd_soc_card *card);
static int rx1950_startup(struct snd_pcm_substream *substream);
static int rx1950_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
static int rx1950_spk_power(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
static unsigned int rates[] = {
16000,
44100,
48000,
};
static struct snd_pcm_hw_constraint_list hw_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static struct snd_soc_jack hp_jack;
static struct snd_soc_jack_pin hp_jack_pins[] = {
{
.pin = "Headphone Jack",
.mask = SND_JACK_HEADPHONE,
},
{
.pin = "Speaker",
.mask = SND_JACK_HEADPHONE,
.invert = 1,
},
};
static struct snd_soc_jack_gpio hp_jack_gpios[] = {
[0] = {
.gpio = S3C2410_GPG(12),
.name = "hp-gpio",
.report = SND_JACK_HEADPHONE,
.invert = 1,
.debounce_time = 200,
},
};
static struct snd_soc_ops rx1950_ops = {
.startup = rx1950_startup,
.hw_params = rx1950_hw_params,
};
/* s3c24xx digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link rx1950_uda1380_dai[] = {
{
.name = "uda1380",
.stream_name = "UDA1380 Duplex",
.cpu_dai_name = "s3c24xx-iis",
.codec_dai_name = "uda1380-hifi",
.init = rx1950_uda1380_init,
.platform_name = "s3c24xx-iis",
.codec_name = "uda1380-codec.0-001a",
.ops = &rx1950_ops,
},
};
/* rx1950 machine dapm widgets */
static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_SPK("Speaker", rx1950_spk_power),
};
/* rx1950 machine audio_map */
static const struct snd_soc_dapm_route audio_map[] = {
/* headphone connected to VOUTLHP, VOUTRHP */
{"Headphone Jack", NULL, "VOUTLHP"},
{"Headphone Jack", NULL, "VOUTRHP"},
/* ext speaker connected to VOUTL, VOUTR */
{"Speaker", NULL, "VOUTL"},
{"Speaker", NULL, "VOUTR"},
/* mic is connected to VINM */
{"VINM", NULL, "Mic Jack"},
};
static struct snd_soc_card rx1950_asoc = {
.name = "rx1950",
.owner = THIS_MODULE,
.remove = rx1950_uda1380_card_remove,
.dai_link = rx1950_uda1380_dai,
.num_links = ARRAY_SIZE(rx1950_uda1380_dai),
.dapm_widgets = uda1380_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
};
static struct platform_device *s3c24xx_snd_device;
static int rx1950_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
return snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&hw_rates);
}
static int rx1950_spk_power(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
if (SND_SOC_DAPM_EVENT_ON(event))
gpio_set_value(S3C2410_GPA(1), 1);
else
gpio_set_value(S3C2410_GPA(1), 0);
return 0;
}
static int rx1950_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int div;
int ret;
unsigned int rate = params_rate(params);
int clk_source, fs_mode;
switch (rate) {
case 16000:
case 48000:
clk_source = S3C24XX_CLKSRC_PCLK;
fs_mode = S3C2410_IISMOD_256FS;
div = s3c24xx_i2s_get_clockrate() / (256 * rate);
if (s3c24xx_i2s_get_clockrate() % (256 * rate) > (128 * rate))
div++;
break;
case 44100:
case 88200:
clk_source = S3C24XX_CLKSRC_MPLL;
fs_mode = S3C2410_IISMOD_384FS;
div = 1;
break;
default:
printk(KERN_ERR "%s: rate %d is not supported\n",
__func__, rate);
return -EINVAL;
}
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* set cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* select clock source */
ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source, rate,
SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
/* set MCLK division for sample rate */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
fs_mode);
if (ret < 0)
return ret;
/* set BCLK division for sample rate */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
S3C2410_IISMOD_32FS);
if (ret < 0)
return ret;
/* set prescaler division for sample rate */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
S3C24XX_PRESCALE(div, div));
if (ret < 0)
return ret;
return 0;
}
static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
&hp_jack);
snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
hp_jack_pins);
snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
hp_jack_gpios);
return 0;
}
static int rx1950_uda1380_card_remove(struct snd_soc_card *card)
{
snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
hp_jack_gpios);
return 0;
}
static int __init rx1950_init(void)
{
int ret;
if (!machine_is_rx1950())
return -ENODEV;
/* configure some gpios */
ret = gpio_request(S3C2410_GPA(1), "speaker-power");
if (ret)
goto err_gpio;
ret = gpio_direction_output(S3C2410_GPA(1), 0);
if (ret)
goto err_gpio_conf;
s3c24xx_snd_device = platform_device_alloc("soc-audio", -1);
if (!s3c24xx_snd_device) {
ret = -ENOMEM;
goto err_plat_alloc;
}
platform_set_drvdata(s3c24xx_snd_device, &rx1950_asoc);
ret = platform_device_add(s3c24xx_snd_device);
if (ret) {
platform_device_put(s3c24xx_snd_device);
goto err_plat_add;
}
return 0;
err_plat_add:
err_plat_alloc:
err_gpio_conf:
gpio_free(S3C2410_GPA(1));
err_gpio:
return ret;
}
static void __exit rx1950_exit(void)
{
platform_device_unregister(s3c24xx_snd_device);
gpio_free(S3C2410_GPA(1));
}
module_init(rx1950_init);
module_exit(rx1950_exit);
/* Module information */
MODULE_AUTHOR("Vasily Khoruzhick");
MODULE_DESCRIPTION("ALSA SoC RX1950");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,735 @@
/* ALSA Soc Audio Layer - I2S core for newer Samsung SoCs.
*
* Copyright (c) 2006 Wolfson Microelectronics PLC.
* Graeme Gregory graeme.gregory@wolfsonmicro.com
* linux@wolfsonmicro.com
*
* Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "regs-i2s-v2.h"
#include "s3c-i2s-v2.h"
#include "dma.h"
#undef S3C_IIS_V2_SUPPORTED
#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) \
|| defined(CONFIG_ARCH_S3C64XX) || defined(CONFIG_CPU_S5PV210)
#define S3C_IIS_V2_SUPPORTED
#endif
#ifndef S3C_IIS_V2_SUPPORTED
#error Unsupported CPU model
#endif
#define S3C2412_I2S_DEBUG_CON 0
static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai)
{
return snd_soc_dai_get_drvdata(cpu_dai);
}
#define bit_set(v, b) (((v) & (b)) ? 1 : 0)
#if S3C2412_I2S_DEBUG_CON
static void dbg_showcon(const char *fn, u32 con)
{
printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn,
bit_set(con, S3C2412_IISCON_LRINDEX),
bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY),
bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY),
bit_set(con, S3C2412_IISCON_TXFIFO_FULL),
bit_set(con, S3C2412_IISCON_RXFIFO_FULL));
printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n",
fn,
bit_set(con, S3C2412_IISCON_TXDMA_PAUSE),
bit_set(con, S3C2412_IISCON_RXDMA_PAUSE),
bit_set(con, S3C2412_IISCON_TXCH_PAUSE),
bit_set(con, S3C2412_IISCON_RXCH_PAUSE));
printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn,
bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE),
bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE),
bit_set(con, S3C2412_IISCON_IIS_ACTIVE));
}
#else
static inline void dbg_showcon(const char *fn, u32 con)
{
}
#endif
/* Turn on or off the transmission path. */
static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
{
void __iomem *regs = i2s->regs;
u32 fic, con, mod;
pr_debug("%s(%d)\n", __func__, on);
fic = readl(regs + S3C2412_IISFIC);
con = readl(regs + S3C2412_IISCON);
mod = readl(regs + S3C2412_IISMOD);
pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
if (on) {
con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
con &= ~S3C2412_IISCON_TXDMA_PAUSE;
con &= ~S3C2412_IISCON_TXCH_PAUSE;
switch (mod & S3C2412_IISMOD_MODE_MASK) {
case S3C2412_IISMOD_MODE_TXONLY:
case S3C2412_IISMOD_MODE_TXRX:
/* do nothing, we are in the right mode */
break;
case S3C2412_IISMOD_MODE_RXONLY:
mod &= ~S3C2412_IISMOD_MODE_MASK;
mod |= S3C2412_IISMOD_MODE_TXRX;
break;
default:
dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n",
mod & S3C2412_IISMOD_MODE_MASK);
break;
}
writel(con, regs + S3C2412_IISCON);
writel(mod, regs + S3C2412_IISMOD);
} else {
/* Note, we do not have any indication that the FIFO problems
* tha the S3C2410/2440 had apply here, so we should be able
* to disable the DMA and TX without resetting the FIFOS.
*/
con |= S3C2412_IISCON_TXDMA_PAUSE;
con |= S3C2412_IISCON_TXCH_PAUSE;
con &= ~S3C2412_IISCON_TXDMA_ACTIVE;
switch (mod & S3C2412_IISMOD_MODE_MASK) {
case S3C2412_IISMOD_MODE_TXRX:
mod &= ~S3C2412_IISMOD_MODE_MASK;
mod |= S3C2412_IISMOD_MODE_RXONLY;
break;
case S3C2412_IISMOD_MODE_TXONLY:
mod &= ~S3C2412_IISMOD_MODE_MASK;
con &= ~S3C2412_IISCON_IIS_ACTIVE;
break;
default:
dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n",
mod & S3C2412_IISMOD_MODE_MASK);
break;
}
writel(mod, regs + S3C2412_IISMOD);
writel(con, regs + S3C2412_IISCON);
}
fic = readl(regs + S3C2412_IISFIC);
dbg_showcon(__func__, con);
pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
}
static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
{
void __iomem *regs = i2s->regs;
u32 fic, con, mod;
pr_debug("%s(%d)\n", __func__, on);
fic = readl(regs + S3C2412_IISFIC);
con = readl(regs + S3C2412_IISCON);
mod = readl(regs + S3C2412_IISMOD);
pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
if (on) {
con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
con &= ~S3C2412_IISCON_RXDMA_PAUSE;
con &= ~S3C2412_IISCON_RXCH_PAUSE;
switch (mod & S3C2412_IISMOD_MODE_MASK) {
case S3C2412_IISMOD_MODE_TXRX:
case S3C2412_IISMOD_MODE_RXONLY:
/* do nothing, we are in the right mode */
break;
case S3C2412_IISMOD_MODE_TXONLY:
mod &= ~S3C2412_IISMOD_MODE_MASK;
mod |= S3C2412_IISMOD_MODE_TXRX;
break;
default:
dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n",
mod & S3C2412_IISMOD_MODE_MASK);
}
writel(mod, regs + S3C2412_IISMOD);
writel(con, regs + S3C2412_IISCON);
} else {
/* See txctrl notes on FIFOs. */
con &= ~S3C2412_IISCON_RXDMA_ACTIVE;
con |= S3C2412_IISCON_RXDMA_PAUSE;
con |= S3C2412_IISCON_RXCH_PAUSE;
switch (mod & S3C2412_IISMOD_MODE_MASK) {
case S3C2412_IISMOD_MODE_RXONLY:
con &= ~S3C2412_IISCON_IIS_ACTIVE;
mod &= ~S3C2412_IISMOD_MODE_MASK;
break;
case S3C2412_IISMOD_MODE_TXRX:
mod &= ~S3C2412_IISMOD_MODE_MASK;
mod |= S3C2412_IISMOD_MODE_TXONLY;
break;
default:
dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n",
mod & S3C2412_IISMOD_MODE_MASK);
}
writel(con, regs + S3C2412_IISCON);
writel(mod, regs + S3C2412_IISMOD);
}
fic = readl(regs + S3C2412_IISFIC);
pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
}
#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
/*
* Wait for the LR signal to allow synchronisation to the L/R clock
* from the codec. May only be needed for slave mode.
*/
static int s3c2412_snd_lrsync(struct s3c_i2sv2_info *i2s)
{
u32 iiscon;
unsigned long loops = msecs_to_loops(5);
pr_debug("Entered %s\n", __func__);
while (--loops) {
iiscon = readl(i2s->regs + S3C2412_IISCON);
if (iiscon & S3C2412_IISCON_LRINDEX)
break;
cpu_relax();
}
if (!loops) {
printk(KERN_ERR "%s: timeout\n", __func__);
return -ETIMEDOUT;
}
return 0;
}
/*
* Set S3C2412 I2S DAI format
*/
static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
u32 iismod;
pr_debug("Entered %s\n", __func__);
iismod = readl(i2s->regs + S3C2412_IISMOD);
pr_debug("hw_params r: IISMOD: %x \n", iismod);
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
i2s->master = 0;
iismod |= S3C2412_IISMOD_SLAVE;
break;
case SND_SOC_DAIFMT_CBS_CFS:
i2s->master = 1;
iismod &= ~S3C2412_IISMOD_SLAVE;
break;
default:
pr_err("unknwon master/slave format\n");
return -EINVAL;
}
iismod &= ~S3C2412_IISMOD_SDF_MASK;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_RIGHT_J:
iismod |= S3C2412_IISMOD_LR_RLOW;
iismod |= S3C2412_IISMOD_SDF_MSB;
break;
case SND_SOC_DAIFMT_LEFT_J:
iismod |= S3C2412_IISMOD_LR_RLOW;
iismod |= S3C2412_IISMOD_SDF_LSB;
break;
case SND_SOC_DAIFMT_I2S:
iismod &= ~S3C2412_IISMOD_LR_RLOW;
iismod |= S3C2412_IISMOD_SDF_IIS;
break;
default:
pr_err("Unknown data format\n");
return -EINVAL;
}
writel(iismod, i2s->regs + S3C2412_IISMOD);
pr_debug("hw_params w: IISMOD: %x \n", iismod);
return 0;
}
static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct s3c_i2sv2_info *i2s = to_info(dai);
struct s3c_dma_params *dma_data;
u32 iismod;
pr_debug("Entered %s\n", __func__);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dma_data = i2s->dma_playback;
else
dma_data = i2s->dma_capture;
snd_soc_dai_set_dma_data(dai, substream, dma_data);
/* Working copies of register */
iismod = readl(i2s->regs + S3C2412_IISMOD);
pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
iismod &= ~S3C64XX_IISMOD_BLC_MASK;
/* Sample size */
switch (params_width(params)) {
case 8:
iismod |= S3C64XX_IISMOD_BLC_8BIT;
break;
case 16:
break;
case 24:
iismod |= S3C64XX_IISMOD_BLC_24BIT;
break;
}
writel(iismod, i2s->regs + S3C2412_IISMOD);
pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
return 0;
}
static int s3c_i2sv2_set_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
pr_debug("Entered %s\n", __func__);
pr_debug("%s r: IISMOD: %x\n", __func__, iismod);
switch (clk_id) {
case S3C_I2SV2_CLKSRC_PCLK:
iismod &= ~S3C2412_IISMOD_IMS_SYSMUX;
break;
case S3C_I2SV2_CLKSRC_AUDIOBUS:
iismod |= S3C2412_IISMOD_IMS_SYSMUX;
break;
case S3C_I2SV2_CLKSRC_CDCLK:
/* Error if controller doesn't have the CDCLKCON bit */
if (!(i2s->feature & S3C_FEATURE_CDCLKCON))
return -EINVAL;
switch (dir) {
case SND_SOC_CLOCK_IN:
iismod |= S3C64XX_IISMOD_CDCLKCON;
break;
case SND_SOC_CLOCK_OUT:
iismod &= ~S3C64XX_IISMOD_CDCLKCON;
break;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
writel(iismod, i2s->regs + S3C2412_IISMOD);
pr_debug("%s w: IISMOD: %x\n", __func__, iismod);
return 0;
}
static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct s3c_i2sv2_info *i2s = to_info(rtd->cpu_dai);
int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
unsigned long irqs;
int ret = 0;
pr_debug("Entered %s\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/* On start, ensure that the FIFOs are cleared and reset. */
writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH,
i2s->regs + S3C2412_IISFIC);
/* clear again, just in case */
writel(0x0, i2s->regs + S3C2412_IISFIC);
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (!i2s->master) {
ret = s3c2412_snd_lrsync(i2s);
if (ret)
goto exit_err;
}
local_irq_save(irqs);
if (capture)
s3c2412_snd_rxctrl(i2s, 1);
else
s3c2412_snd_txctrl(i2s, 1);
local_irq_restore(irqs);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
local_irq_save(irqs);
if (capture)
s3c2412_snd_rxctrl(i2s, 0);
else
s3c2412_snd_txctrl(i2s, 0);
local_irq_restore(irqs);
break;
default:
ret = -EINVAL;
break;
}
exit_err:
return ret;
}
/*
* Set S3C2412 Clock dividers
*/
static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
u32 reg;
pr_debug("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div);
switch (div_id) {
case S3C_I2SV2_DIV_BCLK:
switch (div) {
case 16:
div = S3C2412_IISMOD_BCLK_16FS;
break;
case 32:
div = S3C2412_IISMOD_BCLK_32FS;
break;
case 24:
div = S3C2412_IISMOD_BCLK_24FS;
break;
case 48:
div = S3C2412_IISMOD_BCLK_48FS;
break;
default:
return -EINVAL;
}
reg = readl(i2s->regs + S3C2412_IISMOD);
reg &= ~S3C2412_IISMOD_BCLK_MASK;
writel(reg | div, i2s->regs + S3C2412_IISMOD);
pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
break;
case S3C_I2SV2_DIV_RCLK:
switch (div) {
case 256:
div = S3C2412_IISMOD_RCLK_256FS;
break;
case 384:
div = S3C2412_IISMOD_RCLK_384FS;
break;
case 512:
div = S3C2412_IISMOD_RCLK_512FS;
break;
case 768:
div = S3C2412_IISMOD_RCLK_768FS;
break;
default:
return -EINVAL;
}
reg = readl(i2s->regs + S3C2412_IISMOD);
reg &= ~S3C2412_IISMOD_RCLK_MASK;
writel(reg | div, i2s->regs + S3C2412_IISMOD);
pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
break;
case S3C_I2SV2_DIV_PRESCALER:
if (div >= 0) {
writel((div << 8) | S3C2412_IISPSR_PSREN,
i2s->regs + S3C2412_IISPSR);
} else {
writel(0x0, i2s->regs + S3C2412_IISPSR);
}
pr_debug("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR));
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct s3c_i2sv2_info *i2s = to_info(dai);
u32 reg = readl(i2s->regs + S3C2412_IISFIC);
snd_pcm_sframes_t delay;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
delay = S3C2412_IISFIC_TXCOUNT(reg);
else
delay = S3C2412_IISFIC_RXCOUNT(reg);
return delay;
}
struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai)
{
struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
if (iismod & S3C2412_IISMOD_IMS_SYSMUX)
return i2s->iis_cclk;
else
return i2s->iis_pclk;
}
EXPORT_SYMBOL_GPL(s3c_i2sv2_get_clock);
/* default table of all avaialable root fs divisors */
static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 };
int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
unsigned int *fstab,
unsigned int rate, struct clk *clk)
{
unsigned long clkrate = clk_get_rate(clk);
unsigned int div;
unsigned int fsclk;
unsigned int actual;
unsigned int fs;
unsigned int fsdiv;
signed int deviation = 0;
unsigned int best_fs = 0;
unsigned int best_div = 0;
unsigned int best_rate = 0;
unsigned int best_deviation = INT_MAX;
pr_debug("Input clock rate %ldHz\n", clkrate);
if (fstab == NULL)
fstab = iis_fs_tab;
for (fs = 0; fs < ARRAY_SIZE(iis_fs_tab); fs++) {
fsdiv = iis_fs_tab[fs];
fsclk = clkrate / fsdiv;
div = fsclk / rate;
if ((fsclk % rate) > (rate / 2))
div++;
if (div <= 1)
continue;
actual = clkrate / (fsdiv * div);
deviation = actual - rate;
printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n",
fsdiv, div, actual, deviation);
deviation = abs(deviation);
if (deviation < best_deviation) {
best_fs = fsdiv;
best_div = div;
best_rate = actual;
best_deviation = deviation;
}
if (deviation == 0)
break;
}
printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n",
best_fs, best_div, best_rate);
info->fs_div = best_fs;
info->clk_div = best_div;
return 0;
}
EXPORT_SYMBOL_GPL(s3c_i2sv2_iis_calc_rate);
int s3c_i2sv2_probe(struct snd_soc_dai *dai,
struct s3c_i2sv2_info *i2s,
unsigned long base)
{
struct device *dev = dai->dev;
unsigned int iismod;
i2s->dev = dev;
/* record our i2s structure for later use in the callbacks */
snd_soc_dai_set_drvdata(dai, i2s);
i2s->iis_pclk = clk_get(dev, "iis");
if (IS_ERR(i2s->iis_pclk)) {
dev_err(dev, "failed to get iis_clock\n");
iounmap(i2s->regs);
return -ENOENT;
}
clk_enable(i2s->iis_pclk);
/* Mark ourselves as in TXRX mode so we can run through our cleanup
* process without warnings. */
iismod = readl(i2s->regs + S3C2412_IISMOD);
iismod |= S3C2412_IISMOD_MODE_TXRX;
writel(iismod, i2s->regs + S3C2412_IISMOD);
s3c2412_snd_txctrl(i2s, 0);
s3c2412_snd_rxctrl(i2s, 0);
return 0;
}
EXPORT_SYMBOL_GPL(s3c_i2sv2_probe);
#ifdef CONFIG_PM
static int s3c2412_i2s_suspend(struct snd_soc_dai *dai)
{
struct s3c_i2sv2_info *i2s = to_info(dai);
u32 iismod;
if (dai->active) {
i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD);
i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON);
i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR);
/* some basic suspend checks */
iismod = readl(i2s->regs + S3C2412_IISMOD);
if (iismod & S3C2412_IISCON_RXDMA_ACTIVE)
pr_warning("%s: RXDMA active?\n", __func__);
if (iismod & S3C2412_IISCON_TXDMA_ACTIVE)
pr_warning("%s: TXDMA active?\n", __func__);
if (iismod & S3C2412_IISCON_IIS_ACTIVE)
pr_warning("%s: IIS active\n", __func__);
}
return 0;
}
static int s3c2412_i2s_resume(struct snd_soc_dai *dai)
{
struct s3c_i2sv2_info *i2s = to_info(dai);
pr_info("dai_active %d, IISMOD %08x, IISCON %08x\n",
dai->active, i2s->suspend_iismod, i2s->suspend_iiscon);
if (dai->active) {
writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON);
writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD);
writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR);
writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH,
i2s->regs + S3C2412_IISFIC);
ndelay(250);
writel(0x0, i2s->regs + S3C2412_IISFIC);
}
return 0;
}
#else
#define s3c2412_i2s_suspend NULL
#define s3c2412_i2s_resume NULL
#endif
int s3c_i2sv2_register_component(struct device *dev, int id,
struct snd_soc_component_driver *cmp_drv,
struct snd_soc_dai_driver *dai_drv)
{
struct snd_soc_dai_ops *ops = (struct snd_soc_dai_ops *)dai_drv->ops;
ops->trigger = s3c2412_i2s_trigger;
if (!ops->hw_params)
ops->hw_params = s3c_i2sv2_hw_params;
ops->set_fmt = s3c2412_i2s_set_fmt;
ops->set_clkdiv = s3c2412_i2s_set_clkdiv;
ops->set_sysclk = s3c_i2sv2_set_sysclk;
/* Allow overriding by (for example) IISv4 */
if (!ops->delay)
ops->delay = s3c2412_i2s_delay;
dai_drv->suspend = s3c2412_i2s_suspend;
dai_drv->resume = s3c2412_i2s_resume;
return devm_snd_soc_register_component(dev, cmp_drv, dai_drv, 1);
}
EXPORT_SYMBOL_GPL(s3c_i2sv2_register_component);
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,107 @@
/* sound/soc/samsung/s3c-i2s-v2.h
*
* ALSA Soc Audio Layer - S3C_I2SV2 I2S driver
*
* Copyright (c) 2007 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
/* This code is the core support for the I2S block found in a number of
* Samsung SoC devices which is unofficially named I2S-V2. Currently the
* S3C2412 and the S3C64XX series use this block to provide 1 or 2 I2S
* channels via configurable GPIO.
*/
#ifndef __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H
#define __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H __FILE__
#define S3C_I2SV2_DIV_BCLK (1)
#define S3C_I2SV2_DIV_RCLK (2)
#define S3C_I2SV2_DIV_PRESCALER (3)
#define S3C_I2SV2_CLKSRC_PCLK 0
#define S3C_I2SV2_CLKSRC_AUDIOBUS 1
#define S3C_I2SV2_CLKSRC_CDCLK 2
/* Set this flag for I2S controllers that have the bit IISMOD[12]
* bridge/break RCLK signal and external Xi2sCDCLK pin.
*/
#define S3C_FEATURE_CDCLKCON (1 << 0)
/**
* struct s3c_i2sv2_info - S3C I2S-V2 information
* @dev: The parent device passed to use from the probe.
* @regs: The pointer to the device registe block.
* @feature: Set of bit-flags indicating features of the controller.
* @master: True if the I2S core is the I2S bit clock master.
* @dma_playback: DMA information for playback channel.
* @dma_capture: DMA information for capture channel.
* @suspend_iismod: PM save for the IISMOD register.
* @suspend_iiscon: PM save for the IISCON register.
* @suspend_iispsr: PM save for the IISPSR register.
*
* This is the private codec state for the hardware associated with an
* I2S channel such as the register mappings and clock sources.
*/
struct s3c_i2sv2_info {
struct device *dev;
void __iomem *regs;
u32 feature;
struct clk *iis_pclk;
struct clk *iis_cclk;
unsigned char master;
struct s3c_dma_params *dma_playback;
struct s3c_dma_params *dma_capture;
u32 suspend_iismod;
u32 suspend_iiscon;
u32 suspend_iispsr;
unsigned long base;
};
extern struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai);
struct s3c_i2sv2_rate_calc {
unsigned int clk_div; /* for prescaler */
unsigned int fs_div; /* for root frame clock */
};
extern int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
unsigned int *fstab,
unsigned int rate, struct clk *clk);
/**
* s3c_i2sv2_probe - probe for i2s device helper
* @dai: The ASoC DAI structure supplied to the original probe.
* @i2s: Our local i2s structure to fill in.
* @base: The base address for the registers.
*/
extern int s3c_i2sv2_probe(struct snd_soc_dai *dai,
struct s3c_i2sv2_info *i2s,
unsigned long base);
/**
* s3c_i2sv2_register_component - register component and dai with soc core
* @dev: DAI device
* @id: DAI ID
* @drv: The driver structure to register
*
* Fill in any missing fields and then register the given dai with the
* soc core.
*/
extern int s3c_i2sv2_register_component(struct device *dev, int id,
struct snd_soc_component_driver *cmp_drv,
struct snd_soc_dai_driver *dai_drv);
#endif /* __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H */

View file

@ -0,0 +1,193 @@
/* sound/soc/samsung/s3c2412-i2s.c
*
* ALSA Soc Audio Layer - S3C2412 I2S driver
*
* Copyright (c) 2006 Wolfson Microelectronics PLC.
* Graeme Gregory graeme.gregory@wolfsonmicro.com
* linux@wolfsonmicro.com
*
* Copyright (c) 2007, 2004-2005 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <mach/dma.h>
#include <mach/gpio-samsung.h>
#include <plat/gpio-cfg.h>
#include "dma.h"
#include "regs-i2s-v2.h"
#include "s3c2412-i2s.h"
static struct s3c_dma_params s3c2412_i2s_pcm_stereo_out = {
.channel = DMACH_I2S_OUT,
.ch_name = "tx",
.dma_size = 4,
};
static struct s3c_dma_params s3c2412_i2s_pcm_stereo_in = {
.channel = DMACH_I2S_IN,
.ch_name = "rx",
.dma_size = 4,
};
static struct s3c_i2sv2_info s3c2412_i2s;
static int s3c2412_i2s_probe(struct snd_soc_dai *dai)
{
int ret;
pr_debug("Entered %s\n", __func__);
samsung_asoc_init_dma_data(dai, &s3c2412_i2s_pcm_stereo_out,
&s3c2412_i2s_pcm_stereo_in);
ret = s3c_i2sv2_probe(dai, &s3c2412_i2s, S3C2410_PA_IIS);
if (ret)
return ret;
s3c2412_i2s.dma_capture = &s3c2412_i2s_pcm_stereo_in;
s3c2412_i2s.dma_playback = &s3c2412_i2s_pcm_stereo_out;
s3c2412_i2s.iis_cclk = devm_clk_get(dai->dev, "i2sclk");
if (IS_ERR(s3c2412_i2s.iis_cclk)) {
pr_err("failed to get i2sclk clock\n");
return PTR_ERR(s3c2412_i2s.iis_cclk);
}
/* Set MPLL as the source for IIS CLK */
clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll"));
clk_prepare_enable(s3c2412_i2s.iis_cclk);
s3c2412_i2s.iis_cclk = s3c2412_i2s.iis_pclk;
/* Configure the I2S pins (GPE0...GPE4) in correct mode */
s3c_gpio_cfgall_range(S3C2410_GPE(0), 5, S3C_GPIO_SFN(2),
S3C_GPIO_PULL_NONE);
return 0;
}
static int s3c2412_i2s_remove(struct snd_soc_dai *dai)
{
clk_disable_unprepare(s3c2412_i2s.iis_cclk);
return 0;
}
static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai)
{
struct s3c_i2sv2_info *i2s = snd_soc_dai_get_drvdata(cpu_dai);
u32 iismod;
pr_debug("Entered %s\n", __func__);
iismod = readl(i2s->regs + S3C2412_IISMOD);
pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
switch (params_width(params)) {
case 8:
iismod |= S3C2412_IISMOD_8BIT;
break;
case 16:
iismod &= ~S3C2412_IISMOD_8BIT;
break;
}
writel(iismod, i2s->regs + S3C2412_IISMOD);
pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
return 0;
}
#define S3C2412_I2S_RATES \
(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
static const struct snd_soc_dai_ops s3c2412_i2s_dai_ops = {
.hw_params = s3c2412_i2s_hw_params,
};
static struct snd_soc_dai_driver s3c2412_i2s_dai = {
.probe = s3c2412_i2s_probe,
.remove = s3c2412_i2s_remove,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C2412_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C2412_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &s3c2412_i2s_dai_ops,
};
static const struct snd_soc_component_driver s3c2412_i2s_component = {
.name = "s3c2412-i2s",
};
static int s3c2412_iis_dev_probe(struct platform_device *pdev)
{
int ret = 0;
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
s3c2412_i2s.regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(s3c2412_i2s.regs))
return PTR_ERR(s3c2412_i2s.regs);
s3c2412_i2s_pcm_stereo_out.dma_addr = res->start + S3C2412_IISTXD;
s3c2412_i2s_pcm_stereo_in.dma_addr = res->start + S3C2412_IISRXD;
ret = s3c_i2sv2_register_component(&pdev->dev, -1,
&s3c2412_i2s_component,
&s3c2412_i2s_dai);
if (ret) {
pr_err("failed to register the dai\n");
return ret;
}
ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret)
pr_err("failed to register the DMA: %d\n", ret);
return ret;
}
static struct platform_driver s3c2412_iis_driver = {
.probe = s3c2412_iis_dev_probe,
.driver = {
.name = "s3c2412-iis",
.owner = THIS_MODULE,
},
};
module_platform_driver(s3c2412_iis_driver);
/* Module information */
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("S3C2412 I2S SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s3c2412-iis");

View file

@ -0,0 +1,27 @@
/* sound/soc/samsung/s3c2412-i2s.c
*
* ALSA Soc Audio Layer - S3C2412 I2S driver
*
* Copyright (c) 2007 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H
#define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__
#include "s3c-i2s-v2.h"
#define S3C2412_DIV_BCLK S3C_I2SV2_DIV_BCLK
#define S3C2412_DIV_RCLK S3C_I2SV2_DIV_RCLK
#define S3C2412_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER
#define S3C2412_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK
#define S3C2412_CLKSRC_I2SCLK S3C_I2SV2_CLKSRC_AUDIOBUS
#endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */

View file

@ -0,0 +1,498 @@
/*
* s3c24xx-i2s.c -- ALSA Soc Audio Layer
*
* (c) 2006 Wolfson Microelectronics PLC.
* Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*
* Copyright 2004-2005 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <mach/dma.h>
#include <mach/gpio-samsung.h>
#include <plat/gpio-cfg.h>
#include "regs-iis.h"
#include "dma.h"
#include "s3c24xx-i2s.h"
static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_out = {
.channel = DMACH_I2S_OUT,
.ch_name = "tx",
.dma_size = 2,
};
static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_in = {
.channel = DMACH_I2S_IN,
.ch_name = "rx",
.dma_size = 2,
};
struct s3c24xx_i2s_info {
void __iomem *regs;
struct clk *iis_clk;
u32 iiscon;
u32 iismod;
u32 iisfcon;
u32 iispsr;
};
static struct s3c24xx_i2s_info s3c24xx_i2s;
static void s3c24xx_snd_txctrl(int on)
{
u32 iisfcon;
u32 iiscon;
u32 iismod;
pr_debug("Entered %s\n", __func__);
iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
if (on) {
iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
iiscon &= ~S3C2410_IISCON_TXIDLE;
iismod |= S3C2410_IISMOD_TXMODE;
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
} else {
/* note, we have to disable the FIFOs otherwise bad things
* seem to happen when the DMA stops. According to the
* Samsung supplied kernel, this should allow the DMA
* engine and FIFOs to reset. If this isn't allowed, the
* DMA engine will simply freeze randomly.
*/
iisfcon &= ~S3C2410_IISFCON_TXENABLE;
iisfcon &= ~S3C2410_IISFCON_TXDMA;
iiscon |= S3C2410_IISCON_TXIDLE;
iiscon &= ~S3C2410_IISCON_TXDMAEN;
iismod &= ~S3C2410_IISMOD_TXMODE;
writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
}
pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
}
static void s3c24xx_snd_rxctrl(int on)
{
u32 iisfcon;
u32 iiscon;
u32 iismod;
pr_debug("Entered %s\n", __func__);
iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
if (on) {
iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
iiscon &= ~S3C2410_IISCON_RXIDLE;
iismod |= S3C2410_IISMOD_RXMODE;
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
} else {
/* note, we have to disable the FIFOs otherwise bad things
* seem to happen when the DMA stops. According to the
* Samsung supplied kernel, this should allow the DMA
* engine and FIFOs to reset. If this isn't allowed, the
* DMA engine will simply freeze randomly.
*/
iisfcon &= ~S3C2410_IISFCON_RXENABLE;
iisfcon &= ~S3C2410_IISFCON_RXDMA;
iiscon |= S3C2410_IISCON_RXIDLE;
iiscon &= ~S3C2410_IISCON_RXDMAEN;
iismod &= ~S3C2410_IISMOD_RXMODE;
writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
}
pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
}
/*
* Wait for the LR signal to allow synchronisation to the L/R clock
* from the codec. May only be needed for slave mode.
*/
static int s3c24xx_snd_lrsync(void)
{
u32 iiscon;
int timeout = 50; /* 5ms */
pr_debug("Entered %s\n", __func__);
while (1) {
iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
if (iiscon & S3C2410_IISCON_LRINDEX)
break;
if (!timeout--)
return -ETIMEDOUT;
udelay(100);
}
return 0;
}
/*
* Check whether CPU is the master or slave
*/
static inline int s3c24xx_snd_is_clkmaster(void)
{
pr_debug("Entered %s\n", __func__);
return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
}
/*
* Set S3C24xx I2S DAI format
*/
static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
u32 iismod;
pr_debug("Entered %s\n", __func__);
iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
pr_debug("hw_params r: IISMOD: %x \n", iismod);
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
iismod |= S3C2410_IISMOD_SLAVE;
break;
case SND_SOC_DAIFMT_CBS_CFS:
iismod &= ~S3C2410_IISMOD_SLAVE;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_LEFT_J:
iismod |= S3C2410_IISMOD_MSB;
break;
case SND_SOC_DAIFMT_I2S:
iismod &= ~S3C2410_IISMOD_MSB;
break;
default:
return -EINVAL;
}
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
pr_debug("hw_params w: IISMOD: %x \n", iismod);
return 0;
}
static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_dmaengine_dai_dma_data *dma_data;
u32 iismod;
pr_debug("Entered %s\n", __func__);
dma_data = snd_soc_dai_get_dma_data(dai, substream);
/* Working copies of register */
iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
pr_debug("hw_params r: IISMOD: %x\n", iismod);
switch (params_width(params)) {
case 8:
iismod &= ~S3C2410_IISMOD_16BIT;
dma_data->addr_width = 1;
break;
case 16:
iismod |= S3C2410_IISMOD_16BIT;
dma_data->addr_width = 2;
break;
default:
return -EINVAL;
}
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
pr_debug("hw_params w: IISMOD: %x\n", iismod);
return 0;
}
static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
int ret = 0;
pr_debug("Entered %s\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (!s3c24xx_snd_is_clkmaster()) {
ret = s3c24xx_snd_lrsync();
if (ret)
goto exit_err;
}
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c24xx_snd_rxctrl(1);
else
s3c24xx_snd_txctrl(1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c24xx_snd_rxctrl(0);
else
s3c24xx_snd_txctrl(0);
break;
default:
ret = -EINVAL;
break;
}
exit_err:
return ret;
}
/*
* Set S3C24xx Clock source
*/
static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
pr_debug("Entered %s\n", __func__);
iismod &= ~S3C2440_IISMOD_MPLL;
switch (clk_id) {
case S3C24XX_CLKSRC_PCLK:
break;
case S3C24XX_CLKSRC_MPLL:
iismod |= S3C2440_IISMOD_MPLL;
break;
default:
return -EINVAL;
}
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
return 0;
}
/*
* Set S3C24xx Clock dividers
*/
static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
u32 reg;
pr_debug("Entered %s\n", __func__);
switch (div_id) {
case S3C24XX_DIV_BCLK:
reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
break;
case S3C24XX_DIV_MCLK:
reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
break;
case S3C24XX_DIV_PRESCALER:
writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
break;
default:
return -EINVAL;
}
return 0;
}
/*
* To avoid duplicating clock code, allow machine driver to
* get the clockrate from here.
*/
u32 s3c24xx_i2s_get_clockrate(void)
{
return clk_get_rate(s3c24xx_i2s.iis_clk);
}
EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);
static int s3c24xx_i2s_probe(struct snd_soc_dai *dai)
{
pr_debug("Entered %s\n", __func__);
samsung_asoc_init_dma_data(dai, &s3c24xx_i2s_pcm_stereo_out,
&s3c24xx_i2s_pcm_stereo_in);
s3c24xx_i2s.iis_clk = devm_clk_get(dai->dev, "iis");
if (IS_ERR(s3c24xx_i2s.iis_clk)) {
pr_err("failed to get iis_clock\n");
return PTR_ERR(s3c24xx_i2s.iis_clk);
}
clk_prepare_enable(s3c24xx_i2s.iis_clk);
/* Configure the I2S pins (GPE0...GPE4) in correct mode */
s3c_gpio_cfgall_range(S3C2410_GPE(0), 5, S3C_GPIO_SFN(2),
S3C_GPIO_PULL_NONE);
writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
s3c24xx_snd_txctrl(0);
s3c24xx_snd_rxctrl(0);
return 0;
}
#ifdef CONFIG_PM
static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai)
{
pr_debug("Entered %s\n", __func__);
s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);
clk_disable_unprepare(s3c24xx_i2s.iis_clk);
return 0;
}
static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai)
{
pr_debug("Entered %s\n", __func__);
clk_prepare_enable(s3c24xx_i2s.iis_clk);
writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);
return 0;
}
#else
#define s3c24xx_i2s_suspend NULL
#define s3c24xx_i2s_resume NULL
#endif
#define S3C24XX_I2S_RATES \
(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
.trigger = s3c24xx_i2s_trigger,
.hw_params = s3c24xx_i2s_hw_params,
.set_fmt = s3c24xx_i2s_set_fmt,
.set_clkdiv = s3c24xx_i2s_set_clkdiv,
.set_sysclk = s3c24xx_i2s_set_sysclk,
};
static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
.probe = s3c24xx_i2s_probe,
.suspend = s3c24xx_i2s_suspend,
.resume = s3c24xx_i2s_resume,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &s3c24xx_i2s_dai_ops,
};
static const struct snd_soc_component_driver s3c24xx_i2s_component = {
.name = "s3c24xx-i2s",
};
static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
{
int ret = 0;
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Can't get IO resource.\n");
return -ENOENT;
}
s3c24xx_i2s.regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(s3c24xx_i2s.regs))
return PTR_ERR(s3c24xx_i2s.regs);
s3c24xx_i2s_pcm_stereo_out.dma_addr = res->start + S3C2410_IISFIFO;
s3c24xx_i2s_pcm_stereo_in.dma_addr = res->start + S3C2410_IISFIFO;
ret = devm_snd_soc_register_component(&pdev->dev,
&s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1);
if (ret) {
pr_err("failed to register the dai\n");
return ret;
}
ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret)
pr_err("failed to register the dma: %d\n", ret);
return ret;
}
static struct platform_driver s3c24xx_iis_driver = {
.probe = s3c24xx_iis_dev_probe,
.driver = {
.name = "s3c24xx-iis",
.owner = THIS_MODULE,
},
};
module_platform_driver(s3c24xx_iis_driver);
/* Module information */
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s3c24xx-iis");

View file

@ -0,0 +1,35 @@
/*
* s3c24xx-i2s.c -- ALSA Soc Audio Layer
*
* Copyright 2005 Wolfson Microelectronics PLC.
* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* Revision history
* 10th Nov 2006 Initial version.
*/
#ifndef S3C24XXI2S_H_
#define S3C24XXI2S_H_
/* clock sources */
#define S3C24XX_CLKSRC_PCLK 0
#define S3C24XX_CLKSRC_MPLL 1
/* Clock dividers */
#define S3C24XX_DIV_MCLK 0
#define S3C24XX_DIV_BCLK 1
#define S3C24XX_DIV_PRESCALER 2
/* prescaler */
#define S3C24XX_PRESCALE(a,b) \
(((a - 1) << S3C2410_IISPSR_INTSHIFT) | ((b - 1) << S3C2410_IISPSR_EXTSHFIT))
u32 s3c24xx_i2s_get_clockrate(void);
#endif /*S3C24XXI2S_H_*/

View file

@ -0,0 +1,388 @@
/* sound/soc/samsung/s3c24xx_simtec.c
*
* Copyright 2009 Simtec Electronics
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/gpio.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <linux/platform_data/asoc-s3c24xx_simtec.h>
#include "s3c24xx-i2s.h"
#include "s3c24xx_simtec.h"
static struct s3c24xx_audio_simtec_pdata *pdata;
static struct clk *xtal_clk;
static int spk_gain;
static int spk_unmute;
/**
* speaker_gain_get - read the speaker gain setting.
* @kcontrol: The control for the speaker gain.
* @ucontrol: The value that needs to be updated.
*
* Read the value for the AMP gain control.
*/
static int speaker_gain_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = spk_gain;
return 0;
}
/**
* speaker_gain_set - set the value of the speaker amp gain
* @value: The value to write.
*/
static void speaker_gain_set(int value)
{
gpio_set_value_cansleep(pdata->amp_gain[0], value & 1);
gpio_set_value_cansleep(pdata->amp_gain[1], value >> 1);
}
/**
* speaker_gain_put - set the speaker gain setting.
* @kcontrol: The control for the speaker gain.
* @ucontrol: The value that needs to be set.
*
* Set the value of the speaker gain from the specified
* @ucontrol setting.
*
* Note, if the speaker amp is muted, then we do not set a gain value
* as at-least one of the ICs that is fitted will try and power up even
* if the main control is set to off.
*/
static int speaker_gain_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int value = ucontrol->value.integer.value[0];
spk_gain = value;
if (!spk_unmute)
speaker_gain_set(value);
return 0;
}
static const struct snd_kcontrol_new amp_gain_controls[] = {
SOC_SINGLE_EXT("Speaker Gain", 0, 0, 3, 0,
speaker_gain_get, speaker_gain_put),
};
/**
* spk_unmute_state - set the unmute state of the speaker
* @to: zero to unmute, non-zero to ununmute.
*/
static void spk_unmute_state(int to)
{
pr_debug("%s: to=%d\n", __func__, to);
spk_unmute = to;
gpio_set_value(pdata->amp_gpio, to);
/* if we're umuting, also re-set the gain */
if (to && pdata->amp_gain[0] > 0)
speaker_gain_set(spk_gain);
}
/**
* speaker_unmute_get - read the speaker unmute setting.
* @kcontrol: The control for the speaker gain.
* @ucontrol: The value that needs to be updated.
*
* Read the value for the AMP gain control.
*/
static int speaker_unmute_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = spk_unmute;
return 0;
}
/**
* speaker_unmute_put - set the speaker unmute setting.
* @kcontrol: The control for the speaker gain.
* @ucontrol: The value that needs to be set.
*
* Set the value of the speaker gain from the specified
* @ucontrol setting.
*/
static int speaker_unmute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
spk_unmute_state(ucontrol->value.integer.value[0]);
return 0;
}
/* This is added as a manual control as the speaker amps create clicks
* when their power state is changed, which are far more noticeable than
* anything produced by the CODEC itself.
*/
static const struct snd_kcontrol_new amp_unmute_controls[] = {
SOC_SINGLE_EXT("Speaker Switch", 0, 0, 1, 0,
speaker_unmute_get, speaker_unmute_put),
};
void simtec_audio_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
if (pdata->amp_gpio > 0) {
pr_debug("%s: adding amp routes\n", __func__);
snd_soc_add_card_controls(card, amp_unmute_controls,
ARRAY_SIZE(amp_unmute_controls));
}
if (pdata->amp_gain[0] > 0) {
pr_debug("%s: adding amp controls\n", __func__);
snd_soc_add_card_controls(card, amp_gain_controls,
ARRAY_SIZE(amp_gain_controls));
}
}
EXPORT_SYMBOL_GPL(simtec_audio_init);
#define CODEC_CLOCK 12000000
/**
* simtec_hw_params - update hardware parameters
* @substream: The audio substream instance.
* @params: The parameters requested.
*
* Update the codec data routing and configuration settings
* from the supplied data.
*/
static int simtec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int ret;
/* Set the CODEC as the bus clock master, I2S */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (ret) {
pr_err("%s: failed set cpu dai format\n", __func__);
return ret;
}
/* Set the CODEC as the bus clock master */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (ret) {
pr_err("%s: failed set codec dai format\n", __func__);
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
CODEC_CLOCK, SND_SOC_CLOCK_IN);
if (ret) {
pr_err( "%s: failed setting codec sysclk\n", __func__);
return ret;
}
if (pdata->use_mpllin) {
ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL,
0, SND_SOC_CLOCK_OUT);
if (ret) {
pr_err("%s: failed to set MPLLin as clksrc\n",
__func__);
return ret;
}
}
if (pdata->output_cdclk) {
int cdclk_scale;
cdclk_scale = clk_get_rate(xtal_clk) / CODEC_CLOCK;
cdclk_scale--;
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
cdclk_scale);
}
return 0;
}
static int simtec_call_startup(struct s3c24xx_audio_simtec_pdata *pd)
{
/* call any board supplied startup code, this currently only
* covers the bast/vr1000 which have a CPLD in the way of the
* LRCLK */
if (pd->startup)
pd->startup();
return 0;
}
static struct snd_soc_ops simtec_snd_ops = {
.hw_params = simtec_hw_params,
};
/**
* attach_gpio_amp - get and configure the necessary gpios
* @dev: The device we're probing.
* @pd: The platform data supplied by the board.
*
* If there is a GPIO based amplifier attached to the board, claim
* the necessary GPIO lines for it, and set default values.
*/
static int attach_gpio_amp(struct device *dev,
struct s3c24xx_audio_simtec_pdata *pd)
{
int ret;
/* attach gpio amp gain (if any) */
if (pdata->amp_gain[0] > 0) {
ret = gpio_request(pd->amp_gain[0], "gpio-amp-gain0");
if (ret) {
dev_err(dev, "cannot get amp gpio gain0\n");
return ret;
}
ret = gpio_request(pd->amp_gain[1], "gpio-amp-gain1");
if (ret) {
dev_err(dev, "cannot get amp gpio gain1\n");
gpio_free(pdata->amp_gain[0]);
return ret;
}
gpio_direction_output(pd->amp_gain[0], 0);
gpio_direction_output(pd->amp_gain[1], 0);
}
/* note, currently we assume GPA0 isn't valid amp */
if (pdata->amp_gpio > 0) {
ret = gpio_request(pd->amp_gpio, "gpio-amp");
if (ret) {
dev_err(dev, "cannot get amp gpio %d (%d)\n",
pd->amp_gpio, ret);
goto err_amp;
}
/* set the amp off at startup */
spk_unmute_state(0);
}
return 0;
err_amp:
if (pd->amp_gain[0] > 0) {
gpio_free(pd->amp_gain[0]);
gpio_free(pd->amp_gain[1]);
}
return ret;
}
static void detach_gpio_amp(struct s3c24xx_audio_simtec_pdata *pd)
{
if (pd->amp_gain[0] > 0) {
gpio_free(pd->amp_gain[0]);
gpio_free(pd->amp_gain[1]);
}
if (pd->amp_gpio > 0)
gpio_free(pd->amp_gpio);
}
#ifdef CONFIG_PM
static int simtec_audio_resume(struct device *dev)
{
simtec_call_startup(pdata);
return 0;
}
const struct dev_pm_ops simtec_audio_pmops = {
.resume = simtec_audio_resume,
};
EXPORT_SYMBOL_GPL(simtec_audio_pmops);
#endif
int simtec_audio_core_probe(struct platform_device *pdev,
struct snd_soc_card *card)
{
struct platform_device *snd_dev;
int ret;
card->dai_link->ops = &simtec_snd_ops;
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "no platform data supplied\n");
return -EINVAL;
}
simtec_call_startup(pdata);
xtal_clk = clk_get(&pdev->dev, "xtal");
if (IS_ERR(xtal_clk)) {
dev_err(&pdev->dev, "could not get clkout0\n");
return -EINVAL;
}
dev_info(&pdev->dev, "xtal rate is %ld\n", clk_get_rate(xtal_clk));
ret = attach_gpio_amp(&pdev->dev, pdata);
if (ret)
goto err_clk;
snd_dev = platform_device_alloc("soc-audio", -1);
if (!snd_dev) {
dev_err(&pdev->dev, "failed to alloc soc-audio devicec\n");
ret = -ENOMEM;
goto err_gpio;
}
platform_set_drvdata(snd_dev, card);
ret = platform_device_add(snd_dev);
if (ret) {
dev_err(&pdev->dev, "failed to add soc-audio dev\n");
goto err_pdev;
}
platform_set_drvdata(pdev, snd_dev);
return 0;
err_pdev:
platform_device_put(snd_dev);
err_gpio:
detach_gpio_amp(pdata);
err_clk:
clk_put(xtal_clk);
return ret;
}
EXPORT_SYMBOL_GPL(simtec_audio_core_probe);
int simtec_audio_remove(struct platform_device *pdev)
{
struct platform_device *snd_dev = platform_get_drvdata(pdev);
platform_device_unregister(snd_dev);
detach_gpio_amp(pdata);
clk_put(xtal_clk);
return 0;
}
EXPORT_SYMBOL_GPL(simtec_audio_remove);
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
MODULE_DESCRIPTION("ALSA SoC Simtec Audio common support");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,22 @@
/* sound/soc/samsung/s3c24xx_simtec.h
*
* Copyright 2009 Simtec Electronics
*
* 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.
*/
extern void simtec_audio_init(struct snd_soc_pcm_runtime *rtd);
extern int simtec_audio_core_probe(struct platform_device *pdev,
struct snd_soc_card *card);
extern int simtec_audio_remove(struct platform_device *pdev);
#ifdef CONFIG_PM
extern const struct dev_pm_ops simtec_audio_pmops;
#define simtec_audio_pm &simtec_audio_pmops
#else
#define simtec_audio_pm NULL
#endif

View file

@ -0,0 +1,115 @@
/* sound/soc/samsung/s3c24xx_simtec_hermes.c
*
* Copyright 2009 Simtec Electronics
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include "s3c24xx_simtec.h"
static const struct snd_soc_dapm_widget dapm_widgets[] = {
SND_SOC_DAPM_LINE("GSM Out", NULL),
SND_SOC_DAPM_LINE("GSM In", NULL),
SND_SOC_DAPM_LINE("Line In", NULL),
SND_SOC_DAPM_LINE("Line Out", NULL),
SND_SOC_DAPM_LINE("ZV", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_HP("Headphone Jack", NULL),
};
static const struct snd_soc_dapm_route base_map[] = {
/* Headphone connected to HP{L,R}OUT and HP{L,R}COM */
{ "Headphone Jack", NULL, "HPLOUT" },
{ "Headphone Jack", NULL, "HPLCOM" },
{ "Headphone Jack", NULL, "HPROUT" },
{ "Headphone Jack", NULL, "HPRCOM" },
/* ZV connected to Line1 */
{ "LINE1L", NULL, "ZV" },
{ "LINE1R", NULL, "ZV" },
/* Line In connected to Line2 */
{ "LINE2L", NULL, "Line In" },
{ "LINE2R", NULL, "Line In" },
/* Microphone connected to MIC3R and MIC_BIAS */
{ "MIC3L", NULL, "Mic Jack" },
/* GSM connected to MONO_LOUT and MIC3L (in) */
{ "GSM Out", NULL, "MONO_LOUT" },
{ "MIC3L", NULL, "GSM In" },
/* Speaker is connected to LINEOUT{LN,LP,RN,RP}, however we are
* not using the DAPM to power it up and down as there it makes
* a click when powering up. */
};
/**
* simtec_hermes_init - initialise and add controls
* @codec; The codec instance to attach to.
*
* Attach our controls and configure the necessary codec
* mappings for our sound card instance.
*/
static int simtec_hermes_init(struct snd_soc_pcm_runtime *rtd)
{
simtec_audio_init(rtd);
return 0;
}
static struct snd_soc_dai_link simtec_dai_aic33 = {
.name = "tlv320aic33",
.stream_name = "TLV320AIC33",
.codec_name = "tlv320aic3x-codec.0-001a",
.cpu_dai_name = "s3c24xx-iis",
.codec_dai_name = "tlv320aic3x-hifi",
.platform_name = "s3c24xx-iis",
.init = simtec_hermes_init,
};
/* simtec audio machine driver */
static struct snd_soc_card snd_soc_machine_simtec_aic33 = {
.name = "Simtec-Hermes",
.owner = THIS_MODULE,
.dai_link = &simtec_dai_aic33,
.num_links = 1,
.dapm_widgets = dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
.dapm_routes = base_map,
.num_dapm_routes = ARRAY_SIZE(base_map),
};
static int simtec_audio_hermes_probe(struct platform_device *pd)
{
dev_info(&pd->dev, "probing....\n");
return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic33);
}
static struct platform_driver simtec_audio_hermes_platdrv = {
.driver = {
.owner = THIS_MODULE,
.name = "s3c24xx-simtec-hermes-snd",
.pm = simtec_audio_pm,
},
.probe = simtec_audio_hermes_probe,
.remove = simtec_audio_remove,
};
module_platform_driver(simtec_audio_hermes_platdrv);
MODULE_ALIAS("platform:s3c24xx-simtec-hermes-snd");
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,103 @@
/* sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
*
* Copyright 2009 Simtec Electronics
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include "s3c24xx_simtec.h"
/* supported machines:
*
* Machine Connections AMP
* ------- ----------- ---
* BAST MIC, HPOUT, LOUT, LIN TPA2001D1 (HPOUTL,R) (gain hardwired)
* VR1000 HPOUT, LIN None
* VR2000 LIN, LOUT, MIC, HP LM4871 (HPOUTL,R)
* DePicture LIN, LOUT, MIC, HP LM4871 (HPOUTL,R)
* Anubis LIN, LOUT, MIC, HP TPA2001D1 (HPOUTL,R)
*/
static const struct snd_soc_dapm_widget dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_LINE("Line In", NULL),
SND_SOC_DAPM_LINE("Line Out", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
};
static const struct snd_soc_dapm_route base_map[] = {
{ "Headphone Jack", NULL, "LHPOUT"},
{ "Headphone Jack", NULL, "RHPOUT"},
{ "Line Out", NULL, "LOUT" },
{ "Line Out", NULL, "ROUT" },
{ "LLINEIN", NULL, "Line In"},
{ "RLINEIN", NULL, "Line In"},
{ "MICIN", NULL, "Mic Jack"},
};
/**
* simtec_tlv320aic23_init - initialise and add controls
* @codec; The codec instance to attach to.
*
* Attach our controls and configure the necessary codec
* mappings for our sound card instance.
*/
static int simtec_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
{
simtec_audio_init(rtd);
return 0;
}
static struct snd_soc_dai_link simtec_dai_aic23 = {
.name = "tlv320aic23",
.stream_name = "TLV320AIC23",
.codec_name = "tlv320aic3x-codec.0-001a",
.cpu_dai_name = "s3c24xx-iis",
.codec_dai_name = "tlv320aic3x-hifi",
.platform_name = "s3c24xx-iis",
.init = simtec_tlv320aic23_init,
};
/* simtec audio machine driver */
static struct snd_soc_card snd_soc_machine_simtec_aic23 = {
.name = "Simtec",
.owner = THIS_MODULE,
.dai_link = &simtec_dai_aic23,
.num_links = 1,
.dapm_widgets = dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
.dapm_routes = base_map,
.num_dapm_routes = ARRAY_SIZE(base_map),
};
static int simtec_audio_tlv320aic23_probe(struct platform_device *pd)
{
return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic23);
}
static struct platform_driver simtec_audio_tlv320aic23_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "s3c24xx-simtec-tlv320aic23",
.pm = simtec_audio_pm,
},
.probe = simtec_audio_tlv320aic23_probe,
.remove = simtec_audio_remove,
};
module_platform_driver(simtec_audio_tlv320aic23_driver);
MODULE_ALIAS("platform:s3c24xx-simtec-tlv320aic23");
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,351 @@
/*
* Modifications by Christian Pellegrin <chripell@evolware.org>
*
* s3c24xx_uda134x.c -- S3C24XX_UDA134X ALSA SoC Audio board driver
*
* Copyright 2007 Dension Audio Systems Ltd.
* Author: Zoltan Devai
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/s3c24xx_uda134x.h>
#include "regs-iis.h"
#include "s3c24xx-i2s.h"
/* #define ENFORCE_RATES 1 */
/*
Unfortunately the S3C24XX in master mode has a limited capacity of
generating the clock for the codec. If you define this only rates
that are really available will be enforced. But be careful, most
user level application just want the usual sampling frequencies (8,
11.025, 22.050, 44.1 kHz) and anyway resampling is a costly
operation for embedded systems. So if you aren't very lucky or your
hardware engineer wasn't very forward-looking it's better to leave
this undefined. If you do so an approximate value for the requested
sampling rate in the range -/+ 5% will be chosen. If this in not
possible an error will be returned.
*/
static struct clk *xtal;
static struct clk *pclk;
/* this is need because we don't have a place where to keep the
* pointers to the clocks in each substream. We get the clocks only
* when we are actually using them so we don't block stuff like
* frequency change or oscillator power-off */
static int clk_users;
static DEFINE_MUTEX(clk_lock);
static unsigned int rates[33 * 2];
#ifdef ENFORCE_RATES
static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
#endif
static struct platform_device *s3c24xx_uda134x_snd_device;
static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream)
{
int ret = 0;
#ifdef ENFORCE_RATES
struct snd_pcm_runtime *runtime = substream->runtime;
#endif
mutex_lock(&clk_lock);
pr_debug("%s %d\n", __func__, clk_users);
if (clk_users == 0) {
xtal = clk_get(&s3c24xx_uda134x_snd_device->dev, "xtal");
if (IS_ERR(xtal)) {
printk(KERN_ERR "%s cannot get xtal\n", __func__);
ret = PTR_ERR(xtal);
} else {
pclk = clk_get(&s3c24xx_uda134x_snd_device->dev,
"pclk");
if (IS_ERR(pclk)) {
printk(KERN_ERR "%s cannot get pclk\n",
__func__);
clk_put(xtal);
ret = PTR_ERR(pclk);
}
}
if (!ret) {
int i, j;
for (i = 0; i < 2; i++) {
int fs = i ? 256 : 384;
rates[i*33] = clk_get_rate(xtal) / fs;
for (j = 1; j < 33; j++)
rates[i*33 + j] = clk_get_rate(pclk) /
(j * fs);
}
}
}
clk_users += 1;
mutex_unlock(&clk_lock);
if (!ret) {
#ifdef ENFORCE_RATES
ret = snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&hw_constraints_rates);
if (ret < 0)
printk(KERN_ERR "%s cannot set constraints\n",
__func__);
#endif
}
return ret;
}
static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream)
{
mutex_lock(&clk_lock);
pr_debug("%s %d\n", __func__, clk_users);
clk_users -= 1;
if (clk_users == 0) {
clk_put(xtal);
xtal = NULL;
clk_put(pclk);
pclk = NULL;
}
mutex_unlock(&clk_lock);
}
static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
unsigned int clk = 0;
int ret = 0;
int clk_source, fs_mode;
unsigned long rate = params_rate(params);
long err, cerr;
unsigned int div;
int i, bi;
err = 999999;
bi = 0;
for (i = 0; i < 2*33; i++) {
cerr = rates[i] - rate;
if (cerr < 0)
cerr = -cerr;
if (cerr < err) {
err = cerr;
bi = i;
}
}
if (bi / 33 == 1)
fs_mode = S3C2410_IISMOD_256FS;
else
fs_mode = S3C2410_IISMOD_384FS;
if (bi % 33 == 0) {
clk_source = S3C24XX_CLKSRC_MPLL;
div = 1;
} else {
clk_source = S3C24XX_CLKSRC_PCLK;
div = bi % 33;
}
pr_debug("%s desired rate %lu, %d\n", __func__, rate, bi);
clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate;
pr_debug("%s will use: %s %s %d sysclk %d err %ld\n", __func__,
fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS",
clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK",
div, clk, err);
if ((err * 100 / rate) > 5) {
printk(KERN_ERR "S3C24XX_UDA134X: effective frequency "
"too different from desired (%ld%%)\n",
err * 100 / rate);
return -EINVAL;
}
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
S3C2410_IISMOD_32FS);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
S3C24XX_PRESCALE(div, div));
if (ret < 0)
return ret;
/* set the codec system clock for DAC and ADC */
ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops s3c24xx_uda134x_ops = {
.startup = s3c24xx_uda134x_startup,
.shutdown = s3c24xx_uda134x_shutdown,
.hw_params = s3c24xx_uda134x_hw_params,
};
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
.name = "UDA134X",
.stream_name = "UDA134X",
.codec_name = "uda134x-codec",
.codec_dai_name = "uda134x-hifi",
.cpu_dai_name = "s3c24xx-iis",
.ops = &s3c24xx_uda134x_ops,
.platform_name = "s3c24xx-iis",
};
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
.name = "S3C24XX_UDA134X",
.owner = THIS_MODULE,
.dai_link = &s3c24xx_uda134x_dai_link,
.num_links = 1,
};
static struct s3c24xx_uda134x_platform_data *s3c24xx_uda134x_l3_pins;
static void setdat(int v)
{
gpio_set_value(s3c24xx_uda134x_l3_pins->l3_data, v > 0);
}
static void setclk(int v)
{
gpio_set_value(s3c24xx_uda134x_l3_pins->l3_clk, v > 0);
}
static void setmode(int v)
{
gpio_set_value(s3c24xx_uda134x_l3_pins->l3_mode, v > 0);
}
/* FIXME - This must be codec platform data but in which board file ?? */
static struct uda134x_platform_data s3c24xx_uda134x = {
.l3 = {
.setdat = setdat,
.setclk = setclk,
.setmode = setmode,
.data_hold = 1,
.data_setup = 1,
.clock_high = 1,
.mode_hold = 1,
.mode = 1,
.mode_setup = 1,
},
};
static int s3c24xx_uda134x_setup_pin(int pin, char *fun)
{
if (gpio_request(pin, "s3c24xx_uda134x") < 0) {
printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
"l3 %s pin already in use", fun);
return -EBUSY;
}
gpio_direction_output(pin, 0);
return 0;
}
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
int ret;
printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver\n");
s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;
if (s3c24xx_uda134x_l3_pins == NULL) {
printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
"unable to find platform data\n");
return -ENODEV;
}
s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;
s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;
if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,
"data") < 0)
return -EBUSY;
if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,
"clk") < 0) {
gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
return -EBUSY;
}
if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,
"mode") < 0) {
gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
return -EBUSY;
}
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
if (!s3c24xx_uda134x_snd_device) {
printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
"Unable to register\n");
return -ENOMEM;
}
platform_set_drvdata(s3c24xx_uda134x_snd_device,
&snd_soc_s3c24xx_uda134x);
platform_device_add_data(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x, sizeof(s3c24xx_uda134x));
ret = platform_device_add(s3c24xx_uda134x_snd_device);
if (ret) {
printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add\n");
platform_device_put(s3c24xx_uda134x_snd_device);
}
return ret;
}
static int s3c24xx_uda134x_remove(struct platform_device *pdev)
{
platform_device_unregister(s3c24xx_uda134x_snd_device);
gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
gpio_free(s3c24xx_uda134x_l3_pins->l3_mode);
return 0;
}
static struct platform_driver s3c24xx_uda134x_driver = {
.probe = s3c24xx_uda134x_probe,
.remove = s3c24xx_uda134x_remove,
.driver = {
.name = "s3c24xx_uda134x",
.owner = THIS_MODULE,
},
};
module_platform_driver(s3c24xx_uda134x_driver);
MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,28 @@
config SND_SAMSUNG_SEIREN
bool "SEIREN Audio support"
depends on SND_SOC_SAMSUNG && SND_SAMSUNG_AUDSS
help
Say Y if you want to support SEIREN AUDIO.
config SND_SAMSUNG_SEIREN_OFFLOAD
bool "Enable Offload for SEIREN Audio"
depends on SND_SAMSUNG_SEIREN && SND_SAMSUNG_I2S
select SND_SAMSUNG_COMPR
select SND_SOC_SAMSUNG_OFFLOAD
default n
help
Say Y if you want to add Offload support for SEIREN Audio.
config SND_SAMSUNG_SEIREN_DEBUG
bool "Enable debug message for SEIREN Audio"
depends on SND_SAMSUNG_SEIREN
default n
help
Say Y if you want to add debug message for SEIREN Audio.
config SND_SAMSUNG_ELPE
bool "Enable Exynos Low Power Effect Function"
depends on SND_SAMSUNG_SEIREN_OFFLOAD
default y
help
Say Y if you want to add support for Low Power Effect Function

View file

@ -0,0 +1,3 @@
# SEIREN Audio Solution Support
obj-$(CONFIG_SND_SAMSUNG_SEIREN) += seiren.o
obj-$(CONFIG_SND_SAMSUNG_ELPE) += lpeffwork.o

View file

@ -0,0 +1,234 @@
/* sound/soc/samsung/lpeffwork.c
*
* Exynos Seiren DMA driver for Exynos Audio Subsystem
*
* Copyright (c) 2015 Samsung Electronics
* http://www.samsungsemi.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/vmalloc.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/io.h>
#include <asm/cacheflush.h>
#include <sound/exynos.h>
#include <sound/soc.h>
#include "../lpass.h"
#include "seiren.h"
#include "seiren_ioctl.h"
#include "lpeffwork.h"
#include "../esa_sa_effect.h"
#define MAX_EFFCMD_NUM 10
#define LPEFF_DEBUG_HEADER "[lpeff]"
#define lpeff_prinfo(s, ...) pr_info( LPEFF_DEBUG_HEADER s, ##__VA_ARGS__ )
#define lpeff_prerr(s, ...) pr_err( LPEFF_DEBUG_HEADER s, ##__VA_ARGS__ )
extern struct mutex esa_mutex;
struct lpeff_workstruct {
struct task_struct *g_th_id;
wait_queue_head_t msg_wait;
spinlock_t slock;
int msg_rdidx;
int msg_wridx;
int msg_count;
enum exynos_effwork_cmd msg_queue[MAX_EFFCMD_NUM];
};
static struct lpeff_workstruct g_worker_data;
void __iomem *g_effect_addr;
char *lpeff_fw_lib_entry = NULL;
static struct seiren_info g_si;
int lpeff_log(char *str)
{
lpeff_prinfo("[LIB] %s", str);
return 0;
}
int lpeff_memcpy(void * dest, const void *src, size_t n)
{
memcpy(dest, src, n);
return 0;
}
int lpeff_alloc(char *str)
{
lpeff_prinfo("[LIB] %s", str);
return 0;
}
static ssize_t lpeff_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
u32 elpe_cmd;
u32 arg0, arg1, arg2, arg3, arg4, arg5, arg6;
u32 arg7, arg8, arg9, arg10, arg11, arg12;
mutex_lock(&esa_mutex);
pm_runtime_get_sync(&g_si.pdev->dev);
elpe_cmd = readl(g_effect_addr + ELPE_BASE + ELPE_CMD);
arg0 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG0);
arg1 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG1);
arg2 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG2);
arg3 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG3);
arg4 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG4);
arg5 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG5);
arg6 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG6);
arg7 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG7);
arg8 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG8);
arg9 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG9);
arg10 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG10);
arg11 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG11);
arg12 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG12);
/* change src, dst address to offset value */
#if 0
src = (src & 0xFFFFFF) - 0x400000;
dst = (dst & 0xFFFFFF) - 0x400000;
#endif
pm_runtime_mark_last_busy(&g_si.pdev->dev);
pm_runtime_put_sync_autosuspend(&g_si.pdev->dev);
mutex_unlock(&esa_mutex);
// flush_cache_all();
return scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
elpe_cmd, arg0, arg1, arg2, arg3, arg4, arg5, arg6,
arg7, arg8, arg9, arg10, arg11, arg12);
}
static DEVICE_ATTR(lpeff, S_IRUGO, lpeff_show, NULL);
int lpeff_init(struct seiren_info si)
{
int ret = 0;
memcpy(&g_si, &si, sizeof(struct seiren_info));
g_effect_addr = si.effect_ram;
printk("g_effect_addr = 0x%p \n", g_effect_addr);
ret = device_create_file(&g_si.pdev->dev, &dev_attr_lpeff);
if (ret) {
lpeff_prerr("failed to create lpeff file\n");
return ret;
}
return 0;
}
void queue_lpeff_cmd(enum exynos_effwork_cmd msg)
{
spin_lock(&g_worker_data.slock);
g_worker_data.msg_queue[g_worker_data.msg_wridx] = msg;
g_worker_data.msg_wridx++;
g_worker_data.msg_wridx = g_worker_data.msg_wridx % MAX_EFFCMD_NUM;
g_worker_data.msg_count++;
spin_unlock(&g_worker_data.slock);
wake_up(&g_worker_data.msg_wait);
}
static enum exynos_effwork_cmd lpeff_worker_dequeue(void)
{
unsigned long flags;
enum exynos_effwork_cmd msg = -1;
spin_lock_irqsave(&g_worker_data.slock, flags);
if(g_worker_data.msg_count == 0) {
spin_unlock_irqrestore(&g_worker_data.slock, flags);
wait_event(g_worker_data.msg_wait,(g_worker_data.msg_count>0));
spin_lock_irqsave(&g_worker_data.slock, flags);
}
msg = g_worker_data.msg_queue[g_worker_data.msg_rdidx];
g_worker_data.msg_rdidx++;
g_worker_data.msg_rdidx = g_worker_data.msg_rdidx % MAX_EFFCMD_NUM;
g_worker_data.msg_count--;
spin_unlock_irqrestore(&g_worker_data.slock, flags);
return msg;
}
void lpeff_send_effect_cmd(void)
{
sysfs_notify(&g_si.pdev->dev.kobj, NULL, "lpeff");
}
static int lpeff_worker_func(void *arg)
{
while(!kthread_should_stop()) {
enum exynos_effwork_cmd msg = lpeff_worker_dequeue();
switch(msg) {
case LPEFF_EFFECT_CMD:
lpeff_send_effect_cmd();
break;
case LPEFF_REVERB_CMD:
break;
case LPEFF_DEBUG_CMD:
break;
case LPEFF_EXIT_CMD:
break;
default:
lpeff_prinfo("[lpeff] invalid command (%d)\n", msg);
}
}
return 0;
}
void exynos_init_lpeffworker(void)
{
memset(&g_worker_data, 0, sizeof(g_worker_data));
g_worker_data.g_th_id = NULL;
g_worker_data.msg_rdidx = 0;
g_worker_data.msg_wridx = 0;
g_worker_data.msg_count = 0;
init_waitqueue_head(&g_worker_data.msg_wait);
spin_lock_init(&g_worker_data.slock);
if(g_worker_data.g_th_id == NULL)
g_worker_data.g_th_id =
(struct task_struct *)kthread_run(lpeff_worker_func,
NULL, "lpeff_worker");
}
void exynos_lpeff_finalize(void)
{
queue_lpeff_cmd(LPEFF_EXIT_CMD);
if(g_worker_data.g_th_id){
kthread_stop(g_worker_data.g_th_id);
g_worker_data.g_th_id = NULL;
}
device_remove_file(&g_si.pdev->dev, &dev_attr_lpeff);
}
MODULE_AUTHOR("Donggyun Ko <donggyun.ko@samsung.com>");
MODULE_AUTHOR("Hyoungmin Seo <hmseo@samsung.com>");
MODULE_DESCRIPTION("Exynos Seiren LPEFF Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,16 @@
#ifndef __SAMSUNG_AUD_EFFWORKER_HEADER__
#define __SAMSUNG_AUD_EFFWORKER_HEADER__ __FILE__
enum exynos_effwork_cmd {
LPEFF_EFFECT_CMD = 2,
LPEFF_REVERB_CMD,
LPEFF_DEBUG_CMD,
LPEFF_EXIT_CMD,
};
int lpeff_init(struct seiren_info si);
void queue_lpeff_cmd(enum exynos_effwork_cmd msg);
void exynos_init_lpeffworker(void);
void exynos_lpeff_finalize(void);
#endif /* __SAMSUNG_AUD_EFFWORKER_HEADER__ */

View file

@ -0,0 +1,414 @@
/* sound/soc/samsung/seiren/seiren-dma.c
*
* Exynos Seiren DMA driver for Exynos5430
*
* Copyright (c) 2014 Samsung Electronics
* http://www.samsungsemi.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/pm_runtime.h>
#include <linux/iommu.h>
#include <linux/dma/dma-pl330.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/exynos.h>
#include <asm/dma.h>
#include "seiren.h"
#include "seiren-dma.h"
#include "../dma.h"
/* DMAC source/destination addr */
#define _SA 0x400
#define SA(n) (_SA + (n)*0x20)
#define _DA 0x404
#define DA(n) (_DA + (n)*0x20)
static struct seiren_dma_info {
struct platform_device *pdev;
spinlock_t lock;
void __iomem *regs;
#ifdef CONFIG_SND_SAMSUNG_IOMMU
struct iommu_domain *domain;
#endif
struct runtime *rtd[DMA_CH_MAX];
} sdi;
struct runtime {
int ch;
u32 peri;
void __iomem *regs;
void __iomem *reg_ack;
void __iomem *reg_sa;
void __iomem *reg_da;
};
static int of_dma_match_channel(struct device_node *np, const char *name,
int index, struct of_phandle_args *dma_spec)
{
const char *s;
if (of_property_read_string_index(np, "dma-names", index, &s))
return -ENODEV;
if (strcmp(name, s))
return -ENODEV;
if (of_parse_phandle_with_args(np, "dmas", "#dma-cells", index,
dma_spec))
return -ENODEV;
return 0;
}
static unsigned long esa_dma_request(enum dma_ch dma_ch,
struct samsung_dma_req *param,
struct device *dev, char *ch_name)
{
struct device_node *np;
struct of_phandle_args dma_spec;
struct runtime *rtd = NULL;
int ch, count, n;
ch = esa_dma_open();
if (ch < 0) {
esa_err("%s: dma ch alloc fails!!!\n", __func__);
return -EIO;
}
rtd = kzalloc(sizeof(struct runtime), GFP_KERNEL);
if (!rtd) {
esa_err("%s: runtime data alloc fails!!!\n", __func__);
esa_dma_close(ch);
return -EIO;
}
pm_runtime_get_sync(&sdi.pdev->dev);
sdi.rtd[ch] = rtd;
rtd->ch = ch;
rtd->regs = esa_dma_get_mem() + (ch * DMA_PARAM_SIZE);
rtd->reg_ack = rtd->regs + DMA_ACK;
rtd->reg_sa = sdi.regs + SA(ch);
rtd->reg_da = sdi.regs + DA(ch);
memset(rtd->regs, 0, DMA_PARAM_SIZE);
np = dev->of_node;
if (!np) {
esa_err("%s: of_node not found!!!\n", __func__);
goto err;
}
count = of_property_count_strings(np, "dma-names");
if (count < 0) {
esa_err("%s: dma-names property missing\n", __func__);
goto err;
}
for (n = 0; n < count; n++) {
if (!of_dma_match_channel(np, ch_name, n, &dma_spec)) {
of_property_read_u32_index(np, "dmas",
n * 2 + 1, &rtd->peri);
esa_debug("%s: ch %d (peri %d)\n",
__func__, rtd->ch, rtd->peri);
writel(rtd->peri, rtd->regs + DMA_PERI);
return (unsigned long)ch;
}
}
esa_err("%s: dmas property of %s missing\n", __func__, ch_name);
err:
esa_dma_close(ch);
kfree(rtd);
sdi.rtd[ch] = NULL;
pm_runtime_put_sync(&sdi.pdev->dev);
return -EIO;
}
static int esa_dma_release(unsigned long ch, void *param)
{
struct runtime *rtd = sdi.rtd[ch];
esa_dma_close(rtd->ch);
kfree(rtd);
sdi.rtd[ch] = NULL;
pm_runtime_put_sync(&sdi.pdev->dev);
return 0;
}
static int esa_dma_config(unsigned long ch, struct samsung_dma_config *param)
{
struct runtime *rtd = sdi.rtd[ch];
void __iomem *regs = rtd->regs;
u32 mode_1 = DMA_MODE_NOP;
u32 src_pa = 0;
u32 dst_pa = 0;
if (param->direction == DMA_MEM_TO_DEV) {
mode_1 = DMA_MODE_MEM2DEV;
dst_pa = param->fifo;
} else if (param->direction == DMA_DEV_TO_MEM) {
mode_1 = DMA_MODE_DEV2MEM;
src_pa = param->fifo;
}
writel(mode_1, regs + DMA_MODE_1);
writel(src_pa, regs + DMA_SRC_PA);
writel(dst_pa, regs + DMA_DST_PA);
return 0;
}
static int esa_dma_prepare(unsigned long ch, struct samsung_dma_prep *param)
{
struct runtime *rtd = sdi.rtd[ch];
void __iomem *regs = rtd->regs;
u32 mode_2;
mode_2 = (param->infiniteloop) ? DMA_MODE_LOOP : DMA_MODE_ONCE;
writel(mode_2, regs + DMA_MODE_2);
if (param->direction == DMA_MEM_TO_DEV)
writel(param->buf, regs + DMA_SRC_PA);
else if (param->direction == DMA_DEV_TO_MEM)
writel(param->buf, regs + DMA_DST_PA);
writel(param->period, regs + DMA_PERIOD);
writel(param->len / param->period, regs + DMA_PERIOD_CNT);
esa_dma_set_callback(param->fp, param->fp_param, rtd->ch);
esa_dma_send_cmd(CMD_DMA_PREPARE, rtd->ch, rtd->reg_ack);
return 0;
}
static int esa_dma_trigger(unsigned long ch)
{
struct runtime *rtd = sdi.rtd[ch];
esa_dma_send_cmd(CMD_DMA_START, rtd->ch, rtd->reg_ack);
return 0;
}
static int esa_dma_getposition(unsigned long ch, dma_addr_t *src, dma_addr_t *dst)
{
struct runtime *rtd = sdi.rtd[ch];
*src = readl(rtd->reg_sa);
*dst = readl(rtd->reg_da);
return 0;
}
static int esa_dma_flush(unsigned long ch)
{
struct runtime *rtd = sdi.rtd[ch];
esa_dma_send_cmd(CMD_DMA_STOP, rtd->ch, rtd->reg_ack);
return 0;
}
static struct samsung_dma_ops esa_dma_ops = {
.request = esa_dma_request,
.release = esa_dma_release,
.config = esa_dma_config,
.prepare = esa_dma_prepare,
.trigger = esa_dma_trigger,
.started = NULL,
.getposition = esa_dma_getposition,
.flush = esa_dma_flush,
.stop = esa_dma_flush,
};
void *samsung_esa_dma_get_ops(void)
{
return &esa_dma_ops;
}
EXPORT_SYMBOL_GPL(samsung_esa_dma_get_ops);
static const char banner[] =
KERN_INFO "Exynos Seiren ADMA driver, (c)2014 Samsung Electronics\n";
static int esa_dma_probe(struct platform_device *pdev)
{
struct resource *res;
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
printk(banner);
spin_lock_init(&sdi.lock);
sdi.pdev = pdev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "Unable to get LPASS SFRs\n");
return -ENXIO;
}
sdi.regs = devm_ioremap_resource(&pdev->dev, res);
if (!sdi.regs) {
dev_err(dev, "SFR ioremap failed\n");
return -ENOMEM;
}
esa_debug("regs_base = %08X (%08X bytes)\n",
(unsigned int)res->start, (unsigned int)resource_size(res));
if (np) {
if (of_find_property(np, "samsung,lpass-subip", NULL))
lpass_register_subip(dev, "dmac");
}
#ifdef CONFIG_SND_SAMSUNG_IOMMU
sdi.domain = lpass_get_iommu_domain();
if (!sdi.domain) {
dev_err(dev, "iommu not available\n");
goto err;
}
#else
dev_err(dev, "iommu not available\n");
goto err;
#endif
#ifdef CONFIG_PM_RUNTIME
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
pm_runtime_put_sync(dev);
#else
esa_dma_do_resume(dev);
#endif
return 0;
err:
return -ENODEV;
}
static int esa_dma_remove(struct platform_device *pdev)
{
int ret = 0;
#ifndef CONFIG_PM_RUNTIME
lpass_put_sync(&pdev->dev);
#endif
return ret;
}
static int esa_dma_do_suspend(struct device *dev)
{
lpass_put_sync(dev);
return 0;
}
static int esa_dma_do_resume(struct device *dev)
{
lpass_get_sync(dev);
return 0;
}
#if !defined(CONFIG_PM_RUNTIME) && defined(CONFIG_PM_SLEEP)
static int esa_dma_suspend(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_dma_do_suspend(dev);
}
static int esa_dma_resume(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_dma_do_resume(dev);
}
#else
#define esa_dma_suspend NULL
#define esa_dma_resume NULL
#endif
#ifdef CONFIG_PM_RUNTIME
static int esa_dma_runtime_suspend(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_dma_do_suspend(dev);
}
static int esa_dma_runtime_resume(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_dma_do_resume(dev);
}
#endif
static struct platform_device_id esa_dma_driver_ids[] = {
{
.name = "samsung-seiren-dma",
},
{},
};
MODULE_DEVICE_TABLE(platform, esa_dma_driver_ids);
#ifdef CONFIG_OF
static const struct of_device_id exynos_esa_dma_match[] = {
{
.compatible = "samsung,exynos5430-seiren-dma",
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_esa_dma_match);
#endif
static const struct dev_pm_ops esa_dma_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(
esa_dma_suspend,
esa_dma_resume
)
SET_RUNTIME_PM_OPS(
esa_dma_runtime_suspend,
esa_dma_runtime_resume,
NULL
)
};
static struct platform_driver esa_dma_driver = {
.probe = esa_dma_probe,
.remove = esa_dma_remove,
.id_table = esa_dma_driver_ids,
.driver = {
.name = "samsung-seiren-dma",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(exynos_esa_dma_match),
.pm = &esa_dma_pmops,
},
};
module_platform_driver(esa_dma_driver);
MODULE_AUTHOR("Yeongman Seo <yman.seo@samsung.com>");
MODULE_DESCRIPTION("Exynos Seiren DMA Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,31 @@
#ifndef __SEIREN_DMA_H
#define __SEIREN_DMA_H
/* Param */
#define DMA_PARAM_SIZE (0x0100)
/* Mailbox for firmware */
#define DMA_PERI (0x0000)
#define DMA_ACK (0x0004)
#define DMA_STATE (0x0008)
#define DMA_MODE_1 (0x0010)
#define DMA_MODE_2 (0x0014)
#define DMA_SRC_PA (0x0018)
#define DMA_DST_PA (0x001C)
#define DMA_PERIOD (0x0020)
#define DMA_PERIOD_CNT (0x0024)
enum SEIREN_DMA_MODE_1 {
DMA_MODE_NOP = 0,
DMA_MODE_MEM2DEV,
DMA_MODE_DEV2MEM,
};
enum SEIREN_DMA_MODE_2 {
DMA_MODE_ONCE = 0,
DMA_MODE_LOOP,
};
extern void *samsung_esa_dma_get_ops(void);
#endif /* __SEIREN_DMA_H */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,453 @@
#ifndef __SEIREN_H
#define __SEIREN_H
/* Register offset */
#define CA5_BOOTADDR (0x0020)
#define CA5_WAKEUP (0x0028)
#define CA5_STATUS (0x002C)
#define CA5_DBG (0x0030)
#define SW_INTR_CA5 (0x0040)
#define INTR_CA5_STATUS (0x0044)
#define INTR_CA5_MASK (0x0048)
#define SW_INTR_CPU (0x0050)
#define INTR_CPU_STATUS (0x0054)
#define INTR_CPU_MASK (0x0058)
#define CA5_STATUS_WFI (1 << 2)
#define CA5_STATUS_WFE (1 << 1)
/* Mailbox between driver and firmware */
#define VIRSION_ID (0x0000)
#define CMD_CODE (0x0004)
#define HANDLE_ID (0x0008)
#define IP_TYPE (0x000C)
#define PHY_ADDR_INBUF (0x000C)
#define PORT_TYPE (0x000C)
#define PARAM_VAL1 (0x000C)
#define PHY_ADDR_DYNAMIC_MEM (0x0010)
#define SIZE_OF_INBUF (0x0010)
#define PARAM_VAL2 (0x0010)
#define SIZE_DYNAMIC_MEM (0x0014)
#define SIZE_OF_INDATA (0x0014)
#define PHY_ADDR_OUTBUF (0x0018)
#define SIZE_OF_OUTBUF (0x001C)
#define RETURN_CMD (0x0040)
#define IP_ID (0x0044)
#define SIZE_OUT_DATA (0x0048)
#define CONSUMED_BYTE_IN (0x004C)
#define BIT_DEC_SIZE (0x0050)
#define FRAME_NUM (0x0054)
#define CH_NUM (0x0058)
#define FREQ_SAMPLE (0x005C)
#define PARAMS_CNT (0x0060)
#define PARAMS_VAL1 (0x0064)
#define PARAMS_VAL2 (0x0068)
#define FW_LOG_VAL1 (0x0078)
#define FW_LOG_VAL2 (0x007C)
/* Mailbox between driver and firmware for offload */
#define COMPR_CMD_CODE (0x0004)
#define COMPR_HANDLE_ID (0x0008)
#define COMPR_IP_TYPE (0x000C)
#define COMPR_SIZE_OF_FRAGMENT (0x0010)
#define COMPR_PHY_ADDR_INBUF (0x0014)
#define COMPR_SIZE_OF_INBUF (0x0018)
#define COMPR_LEFT_VOL (0x001C)
#define COMPR_RIGHT_VOL (0x0020)
#define EFFECT_EXT_ON (0x0024)
#define COMPR_ALPA_NOTI (0x0028)
#define COMPR_PARAM_RATE (0x0034)
#define COMPR_PARAM_SAMPLE (0x0038)
#define COMPR_PARAM_CH (0x003C)
#define COMPR_RENDERED_PCM_SIZE (0x004C)
#define COMPR_RETURN_CMD (0x0040)
#define COMPR_IP_ID (0x0044)
#define COMPR_SIZE_OUT_DATA (0x0048)
#define COMPR_CPU_LOCK_LV (0x0054)
#define COMPR_CHECK_CMD (0x0058)
#define COMPR_CHECK_RUNNING (0x005C)
#define COMPR_ACK (0x0060)
#define COMPR_INTR_ACK (0x0064)
#define COMPR_INTR_DMA_ACK (0x0068)
/* Interrupt type */
#define INTR_WAKEUP (0x0)
#define INTR_READY (0x1000)
#define INTR_DMA (0x2000)
#define INTR_CREATED (0x3000)
#define INTR_DECODED (0x4000)
#define INTR_RENDERED (0x5000)
#define INTR_FLUSH (0x6000)
#define INTR_PAUSED (0x6001)
#define INTR_EOS (0x7000)
#define INTR_DESTROY (0x8000)
#define INTR_FX_EXT (0x9000)
#define INTR_EFF_REQUEST (0xA000)
#define INTR_SET_CPU_LOCK (0xC000)
#define INTR_FW_LOG (0xFFFF)
/* Memory size */
#define FWMEM_SIZE (0x3DC000)
#define BASEMEM_OFFSET (0x300000)
#define FWAREA_SIZE (0x400000)
#define FWAREA_NUM (3)
#define FWAREA_IOVA (0x50000000)
/* Buffer Information - decode */
#define DEC_IBUF_SIZE (4096)
#define DEC_OBUF_SIZE (36864)
#define DEC_AAC_IBUF_SIZE (0x10000)
#define DEC_AAC_OBUF_SIZE (0x18000)
#define DEC_FLAC_IBUF_SIZE (0x10000)
#define DEC_FLAC_OBUF_SIZE (0x18000)
#define DEC_IBUF_NUM (0x2)
#define DEC_OBUF_NUM (0x2)
/* Buffer Information - sound process */
#define SP_IBUF_SIZE (0x20000)
#define SP_OBUF_SIZE (0x20000)
#define SP_IBUF_NUM (0x1)
#define SP_OBUF_NUM (0x1)
/* External effect */
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
#define FX_BUF_OFFSET (0x1C000)
#else
#define FX_BUF_OFFSET (0x30000)
#endif
#define FX_BUF_SIZE (0x02000)
#define INSTANCE_MAX (20)
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
#define SRAM_FW_MAX (0x24000)
#else
#define SRAM_FW_MAX (0x3B000)
#endif
#define SRAM_IO_BUF (0x31000)
#define SRAM_IBUF_OFFSET (0)
#define SRAM_OBUF_OFFSET (DEC_IBUF_SIZE)
#define BUF_SIZE_MAX (0x50000)
#define SAMPLE_RATE_MIN (8000)
#define CH_NUM_MIN (1)
#define BAND_NUM_MAX (16)
#define FW_LOG_ADDR (0x1B000)
#define FW_LOG_LINE (30)
#define FW_LOG_MAX (80)
#define FW_ZERO_SET_BASE (0x14000)
#define FW_ZERO_SET_SIZE (0x1F00)
#define FW_SRAM_NAME "seiren_fw_sram.bin"
#define FW_DRAM_NAME "seiren_fw_dram.bin"
#ifdef CONFIG_SND_ESA_SA_EFFECT
#define EFFECT_OFFSET (0x1A000)
#endif
/* For Debugging */
#define esa_info(x...) pr_info("SEIREN: " x)
#define esa_err(x...) pr_err("SEIREN: ERR: " x)
#ifdef CONFIG_SND_SAMSUNG_SEIREN_DEBUG
#define esa_debug(x...) pr_debug("SEIREN: " x)
#else
#define esa_debug(x...)
#endif
enum SEIREN_CMDTYPE {
CMD_CREATE = 0x01,
CMD_DESTROY,
CMD_SET_PARAMS,
CMD_GET_PARAMS,
CMD_RESET,
CMD_FLUSH,
CMD_EXE,
/* ADMA */
CMD_DMA_PREPARE = 0x40,
CMD_DMA_START,
CMD_DMA_STOP,
/* OFFLOAD */
CMD_COMPR_CREATE = 0x50,
CMD_COMPR_DESTROY,
CMD_COMPR_SET_PARAM,
CMD_COMPR_WRITE,
CMD_COMPR_READ,
CMD_COMPR_START,
CMD_COMPR_STOP,
CMD_COMPR_PAUSE,
CMD_COMPR_EOS,
CMD_COMPR_GET_VOLUME,
CMD_COMPR_SET_VOLUME,
CMD_COMPR_CA5_WAKEUP,
CMD_COMPR_HPDET_NOTIFY,
SYS_RESET = 0x80,
SYS_RESTAR,
SYS_RESUME,
SYS_SUSPEND,
SYS_GET_STATUS,
};
enum SEIREN_IPTYPE {
ADEC_MP3 = 0x0,
ADEC_AAC,
ADEC_FLAC,
SOUND_EQ = 0x9,
SOUND_BASS,
AENC_AMR,
AENC_AAC,
};
enum SEIREN_PORTTYPE {
PORT_IN = 0x1,
PORT_OUT,
};
enum SEIREN_EOSSTATE {
EOS_NO = 0x0,
EOS_YET,
EOS_FINAL,
};
enum SEIREN_PARAMCMD {
/* PCM parameters */
PCM_PARAM_MAX_SAMPLE_RATE = 0x0,
PCM_PARAM_MAX_NUM_OF_CH,
PCM_PARAM_MAX_BIT_PER_SAMPLE,
PCM_PARAM_SAMPLE_RATE,
PCM_PARAM_NUM_OF_CH,
PCM_PARAM_BIT_PER_SAMPLE,
PCM_MAX_CONFIG_INFO,
PCM_CONFIG_INFO,
/* EQ parameters */
EQ_PARAM_NUM_OF_PRESETS = 0x10,
EQ_PARAM_MAX_NUM_OF_BANDS ,
EQ_PARAM_RANGE_OF_BANDLEVEL,
EQ_PARAM_RANGE_OF_FREQ,
EQ_PARAM_PRESET_ID,
EQ_PARAM_NUM_OF_BANDS,
EQ_PARAM_CENTER_FREQ,
EQ_PARAM_BANDLEVEL,
EQ_PARAM_BANDWIDTH,
EQ_MAX_CONFIG_INFO,
EQ_CONFIG_INFO,
EQ_BAND_INFO,
/* BASS parameters */
/* Codec Dec parameters */
ADEC_PARAM_SET_EOS = 0x30,
ADEC_PARAM_GET_OUTPUT_STATUS,
/* MP3 Dec parameters */
/* AAC Dec parameters */
/* FLAC Dec parameters */
/* Codec Enc parameters */
/* AMR Enc parameters */
/* AAC Enc parameters */
/* Buffer info */
GET_IBUF_POOL_INFO = 0xA0,
GET_OBUF_POOL_INFO,
SET_IBUF_POOL_INFO,
SET_OBUF_POOL_INFO,
};
struct audio_mem_info_t {
u32 virt_addr;
u32 phy_addr;
u32 mem_size;
u32 data_size;
u32 block_count;
};
struct audio_pcm_config_info_t {
u32 direction; /* 0: input, 1:output */
u32 sample_rate;
u32 bit_per_sample;
u32 num_of_channel;
};
struct audio_eq_config_info_t {
u32 preset_id; /* SEIREN_PRESET_ID */
u32 num_of_bands;
u32 band_level[BAND_NUM_MAX];
u32 center_freq[BAND_NUM_MAX];
u32 band_width[BAND_NUM_MAX];
};
struct audio_eq_max_config_info_t {
u32 max_num_of_presets;
u32 max_num_of_bands;
u32 range_of_freq;
u32 range_of_band_level;
};
struct audio_eq_band_info_t {
u32 band_id;
u32 band_level;
u32 center_freq;
u32 band_width;
};
struct seiren_info {
struct platform_device *pdev;
spinlock_t lock;
#if defined(CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD)
spinlock_t cmd_lock;
spinlock_t compr_lock;
#endif
void __iomem *regs;
void __iomem *regs_s;
void __iomem *mailbox;
void __iomem *sram;
struct clk *clk_ca5;
unsigned int irq_ca5;
struct proc_dir_entry *proc_file;
#ifdef CONFIG_SND_SAMSUNG_IOMMU
struct iommu_domain *domain;
#endif
bool pm_on;
bool pm_suspended;
unsigned char *fwarea[FWAREA_NUM];
phys_addr_t fwarea_pa[FWAREA_NUM];
unsigned char *bufmem;
phys_addr_t bufmem_pa;
unsigned char *fwmem;
phys_addr_t fwmem_pa;
void __kernel *fwmem_sram_bak;
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
unsigned int mailbox_bak[32];
#endif
volatile bool isr_done;
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
volatile bool isr_compr_created;
volatile bool is_compr_open;
struct task_struct *aud_cpu_lock_thrd;
int cpu_lock_level;
bool set_cpu_lock;
#endif
bool fwmem_loaded;
int fw_sbin_size;
int fw_dbin_size;
int rtd_cnt;
struct esa_rtd *rtd_pool[INSTANCE_MAX];
unsigned char fw_log[FW_LOG_MAX];
unsigned int fw_log_pos;
char *fw_log_buf;
bool fw_ready;
bool fw_suspended;
bool fw_use_dram;
#ifdef CONFIG_PM_DEVFREQ
struct pm_qos_request ca5_int_qos;
struct pm_qos_request ca5_mif_qos;
int mif_qos;
int int_qos;
#endif
#ifdef CONFIG_SND_ESA_SA_EFFECT
void __iomem *effect_ram;
bool effect_on;
unsigned int out_sample_rate;
#endif
bool fx_ext_on;
unsigned char *fx_work_buf;
bool fx_irq_done;
int fx_next_idx;
};
struct esa_rtd {
/* BUF informaion */
struct audio_mem_info_t ibuf_info;
struct audio_mem_info_t obuf_info;
struct audio_pcm_config_info_t pcm_info;
unsigned char block_num;
unsigned long buf_maxsize; /* IBUF + OBUF */
bool use_sram;
/* IBUF informaion */
unsigned char *ibuf0;
unsigned char *ibuf1;
unsigned long ibuf_size;
unsigned int ibuf_count;
unsigned int ibuf0_offset;
unsigned int ibuf1_offset;
unsigned int select_ibuf;
/* OBUF informaion */
unsigned char *obuf0;
unsigned char *obuf1;
unsigned long obuf_size;
unsigned int obuf_count;
unsigned int obuf0_offset;
unsigned int obuf1_offset;
unsigned int obuf0_filled_size;
unsigned int obuf1_filled_size;
unsigned int select_obuf;
bool obuf0_filled;
bool obuf1_filled;
/* status information */
unsigned int ip_type;
unsigned int handle_id;
unsigned int get_eos;
bool need_config;
/* multi-instance */
unsigned int idx;
};
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
enum OFFLOAD_IPTYPE {
COMPR_MP3 = 0x0,
COMPR_AAC = 0x1,
};
typedef int (*seiren_ops)(uint32_t cmd, uint32_t size, void* priv);
struct audio_processor {
seiren_ops ops;
void* priv;
uint32_t buffer_size;
void __iomem *reg_ack;
size_t block_num;
size_t handle_id;
size_t codec_id;
size_t num_channels;
size_t sample_rate;
};
extern int esa_compr_set_param(struct audio_processor *ap, uint8_t **buffer);
extern void esa_compr_open(void);
extern void esa_compr_close(void);
extern int esa_compr_send_buffer(const size_t copy_size,
struct audio_processor *ap);
extern int esa_compr_send_cmd(int32_t cmd, struct audio_processor *ap);
extern void __iomem *esa_compr_get_mem(void);
extern u32 esa_compr_pcm_size(void);
extern void esa_compr_hpdet_notifier(bool on);
extern void esa_compr_set_state(bool flag);
#endif
#ifdef CONFIG_SND_ESA_SA_EFFECT
int esa_effect_write(int type, int *value, int count);
extern void esa_compr_set_sample_rate(u32 rate);
extern u32 esa_compr_get_sample_rate(void);
#endif
#endif /* __SEIREN_H */

View file

@ -0,0 +1,22 @@
#ifndef __SEIREN_ERROR_H
#define __SEIREN_ERROR_H
enum {
SEIREN_RETURN_OK = 0,
SEIREN_ERROR_OPEN_FAIL = -1000,
SEIREN_ERROR_ALREADY_OPEN = -1001,
SEIREN_ERROR_NOT_READY = -1002,
SEIREN_ERROR_IBUF_OVERFLOW = -2000,
SEIREN_ERROR_IBUF_INFO = -2001,
SEIREN_ERROR_OBUF_READ = -3000,
SEIREN_ERROR_OBUF_INFO = -3001,
SEIREN_ERROR_OBUF_MMAP = -3002,
SEIREN_ERROR_INVALID_SETTING = -4000,
SEIREN_ERROR_GETINFO_FAIL = -4001
} SEIREN_ERRORTYPE;
#endif /* __SEIREN_ERROR_H */

View file

@ -0,0 +1,15 @@
#ifndef __SEIREN_IOCTL_H
#define __SEIREN_IOCTL_H
#define SEIREN_IOCTL_CH_CREATE (0x1001)
#define SEIREN_IOCTL_CH_DESTROY (0x1002)
#define SEIREN_IOCTL_CH_EXE (0x1003)
#define SEIREN_IOCTL_CH_SET_PARAMS (0x2001)
#define SEIREN_IOCTL_CH_GET_PARAMS (0x2002)
#define SEIREN_IOCTL_CH_RESET (0x2003)
#define SEIREN_IOCTL_CH_FLUSH (0x2004)
#define SEIREN_IOCTL_CH_CONFIG (0x2005)
#define SEIREN_IOCTL_FX_EXT (0x4000)
#define SEIREN_IOCTL_ELPE_DONE (0x5000)
#endif /* __SEIREN_IOCTL_H */

View file

@ -0,0 +1,281 @@
/* sound/soc/samsung/smartq_wm8987.c
*
* Copyright 2010 Maurus Cuelenaere <mcuelenaere@gmail.com>
*
* Based on smdk6410_wm8987.c
* Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com
* Graeme Gregory - graeme.gregory@wolfsonmicro.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <mach/gpio-samsung.h>
#include <asm/mach-types.h>
#include "i2s.h"
#include "../codecs/wm8750.h"
/*
* WM8987 is register compatible with WM8750, so using that as base driver.
*/
static struct snd_soc_card snd_soc_smartq;
static int smartq_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
unsigned int clk = 0;
int ret;
switch (params_rate(params)) {
case 8000:
case 16000:
case 32000:
case 48000:
case 96000:
clk = 12288000;
break;
case 11025:
case 22050:
case 44100:
case 88200:
clk = 11289600;
break;
}
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* set cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* Use PCLK for I2S signal generation */
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0,
0, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* Gate the RCLK output on PAD */
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
0, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* set the codec system clock for DAC and ADC */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
/*
* SmartQ WM8987 HiFi DAI operations.
*/
static struct snd_soc_ops smartq_hifi_ops = {
.hw_params = smartq_hifi_hw_params,
};
static struct snd_soc_jack smartq_jack;
static struct snd_soc_jack_pin smartq_jack_pins[] = {
/* Disable speaker when headphone is plugged in */
{
.pin = "Internal Speaker",
.mask = SND_JACK_HEADPHONE,
},
};
static struct snd_soc_jack_gpio smartq_jack_gpios[] = {
{
.gpio = S3C64XX_GPL(12),
.name = "headphone detect",
.report = SND_JACK_HEADPHONE,
.debounce_time = 200,
},
};
static const struct snd_kcontrol_new wm8987_smartq_controls[] = {
SOC_DAPM_PIN_SWITCH("Internal Speaker"),
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Internal Mic"),
};
static int smartq_speaker_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k,
int event)
{
gpio_set_value(S3C64XX_GPK(12), SND_SOC_DAPM_EVENT_OFF(event));
return 0;
}
static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Internal Speaker", smartq_speaker_event),
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Internal Mic", NULL),
};
static const struct snd_soc_dapm_route audio_map[] = {
{"Headphone Jack", NULL, "LOUT2"},
{"Headphone Jack", NULL, "ROUT2"},
{"Internal Speaker", NULL, "LOUT2"},
{"Internal Speaker", NULL, "ROUT2"},
{"Mic Bias", NULL, "Internal Mic"},
{"LINPUT2", NULL, "Mic Bias"},
};
static int smartq_wm8987_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
int err = 0;
/* set endpoints to not connected */
snd_soc_dapm_nc_pin(dapm, "LINPUT1");
snd_soc_dapm_nc_pin(dapm, "RINPUT1");
snd_soc_dapm_nc_pin(dapm, "OUT3");
snd_soc_dapm_nc_pin(dapm, "ROUT1");
/* set endpoints to default off mode */
snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
/* Headphone jack detection */
err = snd_soc_jack_new(codec, "Headphone Jack",
SND_JACK_HEADPHONE, &smartq_jack);
if (err)
return err;
err = snd_soc_jack_add_pins(&smartq_jack, ARRAY_SIZE(smartq_jack_pins),
smartq_jack_pins);
if (err)
return err;
err = snd_soc_jack_add_gpios(&smartq_jack,
ARRAY_SIZE(smartq_jack_gpios),
smartq_jack_gpios);
return err;
}
static int smartq_wm8987_card_remove(struct snd_soc_card *card)
{
snd_soc_jack_free_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios),
smartq_jack_gpios);
return 0;
}
static struct snd_soc_dai_link smartq_dai[] = {
{
.name = "wm8987",
.stream_name = "SmartQ Hi-Fi",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8750-hifi",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8750.0-0x1a",
.init = smartq_wm8987_init,
.ops = &smartq_hifi_ops,
},
};
static struct snd_soc_card snd_soc_smartq = {
.name = "SmartQ",
.owner = THIS_MODULE,
.remove = smartq_wm8987_card_remove,
.dai_link = smartq_dai,
.num_links = ARRAY_SIZE(smartq_dai),
.dapm_widgets = wm8987_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(wm8987_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
.controls = wm8987_smartq_controls,
.num_controls = ARRAY_SIZE(wm8987_smartq_controls),
};
static struct platform_device *smartq_snd_device;
static int __init smartq_init(void)
{
int ret;
if (!machine_is_smartq7() && !machine_is_smartq5()) {
pr_info("Only SmartQ is supported by this ASoC driver\n");
return -ENODEV;
}
smartq_snd_device = platform_device_alloc("soc-audio", -1);
if (!smartq_snd_device)
return -ENOMEM;
platform_set_drvdata(smartq_snd_device, &snd_soc_smartq);
ret = platform_device_add(smartq_snd_device);
if (ret) {
platform_device_put(smartq_snd_device);
return ret;
}
/* Initialise GPIOs used by amplifiers */
ret = gpio_request(S3C64XX_GPK(12), "amplifiers shutdown");
if (ret) {
dev_err(&smartq_snd_device->dev, "Failed to register GPK12\n");
goto err_unregister_device;
}
/* Disable amplifiers */
ret = gpio_direction_output(S3C64XX_GPK(12), 1);
if (ret) {
dev_err(&smartq_snd_device->dev, "Failed to configure GPK12\n");
goto err_free_gpio_amp_shut;
}
return 0;
err_free_gpio_amp_shut:
gpio_free(S3C64XX_GPK(12));
err_unregister_device:
platform_device_unregister(smartq_snd_device);
return ret;
}
static void __exit smartq_exit(void)
{
gpio_free(S3C64XX_GPK(12));
platform_device_unregister(smartq_snd_device);
}
module_init(smartq_init);
module_exit(smartq_exit);
/* Module information */
MODULE_AUTHOR("Maurus Cuelenaere <mcuelenaere@gmail.com>");
MODULE_DESCRIPTION("ALSA SoC SmartQ WM8987");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,68 @@
/*
* smdk2443_wm9710.c -- SoC audio for smdk2443
*
* Copyright 2007 Wolfson Microelectronics PLC.
* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#include <linux/module.h>
#include <sound/soc.h>
static struct snd_soc_card smdk2443;
static struct snd_soc_dai_link smdk2443_dai[] = {
{
.name = "AC97",
.stream_name = "AC97 HiFi",
.cpu_dai_name = "samsung-ac97",
.codec_dai_name = "ac97-hifi",
.codec_name = "ac97-codec",
.platform_name = "samsung-ac97",
},
};
static struct snd_soc_card smdk2443 = {
.name = "SMDK2443",
.owner = THIS_MODULE,
.dai_link = smdk2443_dai,
.num_links = ARRAY_SIZE(smdk2443_dai),
};
static struct platform_device *smdk2443_snd_ac97_device;
static int __init smdk2443_init(void)
{
int ret;
smdk2443_snd_ac97_device = platform_device_alloc("soc-audio", -1);
if (!smdk2443_snd_ac97_device)
return -ENOMEM;
platform_set_drvdata(smdk2443_snd_ac97_device, &smdk2443);
ret = platform_device_add(smdk2443_snd_ac97_device);
if (ret)
platform_device_put(smdk2443_snd_ac97_device);
return ret;
}
static void __exit smdk2443_exit(void)
{
platform_device_unregister(smdk2443_snd_ac97_device);
}
module_init(smdk2443_init);
module_exit(smdk2443_exit);
/* Module information */
MODULE_AUTHOR("Graeme Gregory, graeme.gregory@wolfsonmicro.com, www.wolfsonmicro.com");
MODULE_DESCRIPTION("ALSA SoC WM9710 SMDK2443");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,808 @@
/*
* Smdk7570-COD3025X Audio Machine driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <sound/tlv.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/exynos-audmixer.h>
#include "i2s.h"
#include "i2s-regs.h"
#define CODEC_BFS_48KHZ 32
#define CODEC_RFS_48KHZ 512
#define CODEC_SAMPLE_RATE_48KHZ 48000
#define CODEC_BFS_192KHZ 64
#define CODEC_RFS_192KHZ 128
#define CODEC_SAMPLE_RATE_192KHZ 192000
#ifdef CONFIG_SND_SOC_SAMSUNG_VERBOSE_DEBUG
#define RX_SRAM_SIZE (0x2000) /* 8 KB */
#ifdef dev_dbg
#undef dev_dbg
#endif
#define dev_dbg dev_err
#endif
static struct snd_soc_card smdk7570_cod3025x_card;
extern void set_ip_idle(bool value);
struct cod3025x_machine_priv {
struct snd_soc_codec *codec;
int aifrate;
bool use_external_jd;
};
static const struct snd_soc_component_driver smdk7570_cmpnt = {
.name = "Smdk7570-audio",
};
static int smdk7570_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct cod3025x_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
int rfs, bfs;
dev_info(card->dev, "aif1: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
priv->aifrate = params_rate(params);
if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) &&
params_buffer_bytes(params) <= RX_SRAM_SIZE)
set_ip_idle(true);
else
set_ip_idle(false);
if (priv->aifrate == CODEC_SAMPLE_RATE_192KHZ) {
rfs = CODEC_RFS_192KHZ;
bfs = CODEC_BFS_192KHZ;
} else {
rfs = CODEC_RFS_48KHZ;
bfs = CODEC_BFS_48KHZ;
}
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set Codec DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set CPU DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
rfs, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_CDCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_OPCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0) {
dev_err(card->dev,
"aif1: Failed to set SAMSUNG_I2S_RCLKSRC_1\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set BFS\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_RCLK, rfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set RFS\n");
return ret;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_AP);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7570_aif2_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
int bfs, ret;
dev_info(card->dev, "aif2: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif2: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_CP);
if (ret < 0) {
dev_err(card->dev, "aif2: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7570_aif3_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
int bfs, ret;
dev_info(card->dev, "aif3: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif3: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_BT);
if (ret < 0) {
dev_err(card->dev, "aif3: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7570_aif5_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct cod3025x_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
int rfs, bfs;
dev_info(card->dev, "aif5: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
priv->aifrate = params_rate(params);
if (priv->aifrate == CODEC_SAMPLE_RATE_192KHZ) {
rfs = CODEC_RFS_192KHZ;
bfs = CODEC_BFS_192KHZ;
} else {
rfs = CODEC_RFS_48KHZ;
bfs = CODEC_BFS_48KHZ;
}
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set CPU DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
rfs, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set SAMSUNG_I2S_CDCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set SAMSUNG_I2S_OPCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0) {
dev_err(card->dev,
"aif5: Failed to set SAMSUNG_I2S_RCLKSRC_1\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set BFS\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_RCLK, rfs);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set RFS\n");
return ret;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_AP1);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7570_aif6_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
int bfs, ret;
dev_info(card->dev, "aif6: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif6: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_CP1);
if (ret < 0) {
dev_err(card->dev, "aif6: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7570_aif1_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_AP);
return 0;
}
void smdk7570_aif1_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_AP);
}
static int smdk7570_aif2_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_CP);
return 0;
}
void smdk7570_aif2_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_CP);
}
static int smdk7570_aif3_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_BT);
return 0;
}
void smdk7570_aif3_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_BT);
}
static int smdk7570_aif4_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_BT);
return 0;
}
void smdk7570_aif4_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_BT);
}
static int smdk7570_aif5_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif5: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_AP1);
return 0;
}
void smdk7570_aif5_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif5: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_AP1);
}
static int smdk7570_aif6_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif6: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_CP1);
return 0;
}
void smdk7570_aif6_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif6: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_CP1);
}
static int smdk7570_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
return 0;
}
static int smdk7570_late_probe(struct snd_soc_card *card)
{
dev_dbg(card->dev, "%s called\n", __func__);
return 0;
}
static int audmixer_init(struct snd_soc_component *cmp)
{
dev_dbg(cmp->dev, "%s called\n", __func__);
return 0;
}
static const struct snd_kcontrol_new smdk7570_controls[] = {
};
const struct snd_soc_dapm_widget smdk7570_dapm_widgets[] = {
};
const struct snd_soc_dapm_route smdk7570_dapm_routes[] = {
};
static struct snd_soc_ops smdk7570_aif1_ops = {
.hw_params = smdk7570_aif1_hw_params,
.startup = smdk7570_aif1_startup,
.shutdown = smdk7570_aif1_shutdown,
};
static struct snd_soc_ops smdk7570_aif2_ops = {
.hw_params = smdk7570_aif2_hw_params,
.startup = smdk7570_aif2_startup,
.shutdown = smdk7570_aif2_shutdown,
};
static struct snd_soc_ops smdk7570_aif3_ops = {
.hw_params = smdk7570_aif3_hw_params,
.startup = smdk7570_aif3_startup,
.shutdown = smdk7570_aif3_shutdown,
};
static struct snd_soc_ops smdk7570_aif4_ops = {
.startup = smdk7570_aif4_startup,
.shutdown = smdk7570_aif4_shutdown,
};
static struct snd_soc_ops smdk7570_aif5_ops = {
.hw_params = smdk7570_aif5_hw_params,
.startup = smdk7570_aif5_startup,
.shutdown = smdk7570_aif5_shutdown,
};
static struct snd_soc_ops smdk7570_aif6_ops = {
.hw_params = smdk7570_aif6_hw_params,
.startup = smdk7570_aif6_startup,
.shutdown = smdk7570_aif6_shutdown,
};
static struct snd_soc_dai_driver smdk7570_ext_dai[] = {
{
.name = "smdk7570 voice call",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "smdk7570 BT",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "smdk7570 FM",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
};
static struct snd_soc_dai_link smdk7570_cod3025x_dai[] = {
/* Playback and Recording */
{
.name = "smdk7570-cod3025x",
.stream_name = "i2s0-pri",
.codec_dai_name = "cod3026x-aif",
.ops = &smdk7570_aif1_ops,
},
/* Deep buffer playback */
{
.name = "smdk7570-cod3025x-sec",
.cpu_dai_name = "samsung-i2s-sec",
.stream_name = "i2s0-sec",
.platform_name = "samsung-i2s-sec",
.codec_dai_name = "cod3026x-aif",
.ops = &smdk7570_aif1_ops,
},
/* Voice Call */
{
.name = "cp",
.stream_name = "voice call",
.cpu_dai_name = "smdk7570 voice call",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "cod3026x-aif2",
.ops = &smdk7570_aif2_ops,
.ignore_suspend = 1,
},
/* BT */
{
.name = "bt",
.stream_name = "bluetooth audio",
.cpu_dai_name = "smdk7570 BT",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "dummy-aif2",
.ops = &smdk7570_aif3_ops,
.ignore_suspend = 1,
},
/* FM */
{
.name = "fm",
.stream_name = "FM audio",
.cpu_dai_name = "smdk7570 FM",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "cod3026x-aif",
.ops = &smdk7570_aif4_ops,
.ignore_suspend = 1,
},
/* AMP AP Interface */
{
.name = "smdk7570-cod3026x-amp",
.stream_name = "i2s1-pri",
.codec_dai_name = "dummy-aif2",
.ops = &smdk7570_aif5_ops,
},
/* AMP CP Interface */
{
.name = "cp-amp",
.stream_name = "voice call amp",
.cpu_dai_name = "smdk7570 voice call",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "dummy-aif2",
.ops = &smdk7570_aif6_ops,
.ignore_suspend = 1,
},
};
static struct snd_soc_aux_dev audmixer_aux_dev[] = {
{
.init = audmixer_init,
},
};
static struct snd_soc_codec_conf audmixer_codec_conf[] = {
{
.name_prefix = "AudioMixer",
},
};
static struct snd_soc_card smdk7570_cod3025x_card = {
.name = "Smdk7570-I2S",
.owner = THIS_MODULE,
.dai_link = smdk7570_cod3025x_dai,
.num_links = ARRAY_SIZE(smdk7570_cod3025x_dai),
.controls = smdk7570_controls,
.num_controls = ARRAY_SIZE(smdk7570_controls),
.dapm_widgets = smdk7570_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(smdk7570_dapm_widgets),
.dapm_routes = smdk7570_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(smdk7570_dapm_routes),
.late_probe = smdk7570_late_probe,
.set_bias_level = smdk7570_set_bias_level,
.aux_dev = audmixer_aux_dev,
.num_aux_devs = ARRAY_SIZE(audmixer_aux_dev),
.codec_conf = audmixer_codec_conf,
.num_configs = ARRAY_SIZE(audmixer_codec_conf),
};
static int smdk7570_audio_probe(struct platform_device *pdev)
{
int n, ret;
struct device_node *np = pdev->dev.of_node;
struct device_node *cpu_np, *codec_np, *auxdev_np;
struct snd_soc_card *card = &smdk7570_cod3025x_card;
struct cod3025x_machine_priv *priv;
if (!np) {
dev_err(&pdev->dev, "Failed to get device node\n");
return -EINVAL;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card->dev = &pdev->dev;
card->num_links = 0;
ret = snd_soc_register_component(card->dev, &smdk7570_cmpnt,
smdk7570_ext_dai,
ARRAY_SIZE(smdk7570_ext_dai));
if (ret) {
dev_err(&pdev->dev, "Failed to register component: %d\n", ret);
return ret;
}
for (n = 0; n < ARRAY_SIZE(smdk7570_cod3025x_dai); n++) {
/* Skip parsing DT for fully formed dai links */
if (smdk7570_cod3025x_dai[n].platform_name &&
smdk7570_cod3025x_dai[n].codec_name) {
dev_dbg(card->dev,
"Skipping dt for populated dai link %s\n",
smdk7570_cod3025x_dai[n].name);
card->num_links++;
continue;
}
cpu_np = of_parse_phandle(np, "samsung,audio-cpu", n);
if (!cpu_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-cpu' missing\n");
break;
}
codec_np = of_parse_phandle(np, "samsung,audio-codec", n);
if (!codec_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec' missing\n");
break;
}
smdk7570_cod3025x_dai[n].codec_of_node = codec_np;
if (!smdk7570_cod3025x_dai[n].cpu_dai_name)
smdk7570_cod3025x_dai[n].cpu_of_node = cpu_np;
if (!smdk7570_cod3025x_dai[n].platform_name)
smdk7570_cod3025x_dai[n].platform_of_node = cpu_np;
card->num_links++;
}
for (n = 0; n < ARRAY_SIZE(audmixer_aux_dev); n++) {
auxdev_np = of_parse_phandle(np, "samsung,auxdev", n);
if (!auxdev_np) {
dev_err(&pdev->dev,
"Property 'samsung,auxdev' missing\n");
return -EINVAL;
}
audmixer_aux_dev[n].codec_of_node = auxdev_np;
audmixer_codec_conf[n].of_node = auxdev_np;
}
snd_soc_card_set_drvdata(card, priv);
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev, "Failed to register card:%d\n", ret);
return ret;
}
static int smdk7570_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static const struct of_device_id smdk7570_cod3026x_of_match[] = {
{.compatible = "samsung,smdk7570-cod3026x",},
{},
};
MODULE_DEVICE_TABLE(of, smdk7570_cod3026x_of_match);
static struct platform_driver smdk7570_audio_driver = {
.driver = {
.name = "Smdk7570-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(smdk7570_cod3026x_of_match),
},
.probe = smdk7570_audio_probe,
.remove = smdk7570_audio_remove,
};
module_platform_driver(smdk7570_audio_driver);
MODULE_DESCRIPTION("ALSA SoC Smdk7570 COD3025X");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:smdk7570-audio");

View file

@ -0,0 +1,851 @@
/*
* Smdk7570-COD9002X Audio Machine driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <sound/tlv.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/exynos-audmixer.h>
#include "i2s.h"
#include "i2s-regs.h"
#define CODEC_BFS_48KHZ 32
#define CODEC_RFS_48KHZ 512
#define CODEC_SAMPLE_RATE_48KHZ 48000
#define CODEC_BFS_192KHZ 64
#define CODEC_RFS_192KHZ 128
#define CODEC_SAMPLE_RATE_192KHZ 192000
#ifdef CONFIG_SND_SOC_SAMSUNG_VERBOSE_DEBUG
#ifdef dev_dbg
#undef dev_dbg
#endif
#define dev_dbg dev_err
#endif
static struct snd_soc_card smdk7570_cod9002x_card;
struct cod9002x_machine_priv {
struct snd_soc_codec *codec;
int aifrate;
bool use_external_jd;
};
static const struct snd_soc_component_driver smdk7570_cmpnt = {
.name = "Smdk7570-audio",
};
static int smdk7570_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *amixer_dai = rtd->codec_dais[0];
struct snd_soc_dai *codec_dai = rtd->codec_dais[1];
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct cod9002x_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
int rfs, bfs;
dev_info(card->dev, "aif1: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
priv->aifrate = params_rate(params);
if (priv->aifrate == CODEC_SAMPLE_RATE_192KHZ) {
rfs = CODEC_RFS_192KHZ;
bfs = CODEC_BFS_192KHZ;
} else {
rfs = CODEC_RFS_48KHZ;
bfs = CODEC_BFS_48KHZ;
}
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set Codec DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set CPU DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
rfs, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_CDCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_OPCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0) {
dev_err(card->dev,
"aif1: Failed to set SAMSUNG_I2S_RCLKSRC_1\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set BFS\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_RCLK, rfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set RFS\n");
return ret;
}
ret = snd_soc_dai_set_bclk_ratio(amixer_dai, bfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7570_aif2_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *amixer_dai = rtd->codec_dais[0];
int bfs, ret;
dev_info(card->dev, "aif2: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif2: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = snd_soc_dai_set_bclk_ratio(amixer_dai, bfs);
if (ret < 0) {
dev_err(card->dev, "aif2: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7570_aif5_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *amixer_dai = rtd->codec_dais[0];
struct cod9002x_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
int rfs, bfs;
dev_info(card->dev, "aif5: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
priv->aifrate = params_rate(params);
if (priv->aifrate == CODEC_SAMPLE_RATE_192KHZ) {
rfs = CODEC_RFS_192KHZ;
bfs = CODEC_BFS_192KHZ;
} else {
rfs = CODEC_RFS_48KHZ;
bfs = CODEC_BFS_48KHZ;
}
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set CPU DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
rfs, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set SAMSUNG_I2S_CDCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set SAMSUNG_I2S_OPCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0) {
dev_err(card->dev,
"aif5: Failed to set SAMSUNG_I2S_RCLKSRC_1\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set BFS\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_RCLK, rfs);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set RFS\n");
return ret;
}
ret = snd_soc_dai_set_bclk_ratio(amixer_dai, bfs);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7570_aif6_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *amixer_dai = rtd->codec_dais[0];
int bfs, ret;
dev_info(card->dev, "aif6: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif6: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = snd_soc_dai_set_bclk_ratio(amixer_dai, bfs);
if (ret < 0) {
dev_err(card->dev, "aif6: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7570_aif1_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void smdk7570_aif1_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int smdk7570_aif2_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void smdk7570_aif2_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int smdk7570_aif3_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void smdk7570_aif3_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int smdk7570_aif4_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void smdk7570_aif4_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int smdk7570_aif5_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif5: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void smdk7570_aif5_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif5: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int smdk7570_aif6_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif6: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void smdk7570_aif6_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif6: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int smdk7570_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
return 0;
}
static int smdk7570_late_probe(struct snd_soc_card *card)
{
dev_dbg(card->dev, "%s called\n", __func__);
return 0;
}
static int audmixer_init(struct snd_soc_component *cmp)
{
dev_dbg(cmp->dev, "%s called\n", __func__);
return 0;
}
static const struct snd_kcontrol_new smdk7570_controls[] = {
};
const struct snd_soc_dapm_widget smdk7570_dapm_widgets[] = {
};
const struct snd_soc_dapm_route smdk7570_dapm_routes[] = {
};
static struct snd_soc_ops smdk7570_aif1_ops = {
.hw_params = smdk7570_aif1_hw_params,
.startup = smdk7570_aif1_startup,
.shutdown = smdk7570_aif1_shutdown,
};
static struct snd_soc_ops smdk7570_aif2_ops = {
.hw_params = smdk7570_aif2_hw_params,
.startup = smdk7570_aif2_startup,
.shutdown = smdk7570_aif2_shutdown,
};
static struct snd_soc_ops smdk7570_aif3_ops = {
.startup = smdk7570_aif3_startup,
.shutdown = smdk7570_aif3_shutdown,
};
static struct snd_soc_ops smdk7570_aif4_ops = {
.startup = smdk7570_aif4_startup,
.shutdown = smdk7570_aif4_shutdown,
};
static struct snd_soc_ops smdk7570_aif5_ops = {
.hw_params = smdk7570_aif5_hw_params,
.startup = smdk7570_aif5_startup,
.shutdown = smdk7570_aif5_shutdown,
};
static struct snd_soc_ops smdk7570_aif6_ops = {
.hw_params = smdk7570_aif6_hw_params,
.startup = smdk7570_aif6_startup,
.shutdown = smdk7570_aif6_shutdown,
};
static struct snd_soc_dai_driver smdk7570_ext_dai[] = {
{
.name = "smdk7570 voice call",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "smdk7570 BT",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "smdk7570 FM",
.playback = {
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = SNDRV_PCM_FMTBIT_S16_LE
},
},
};
static struct snd_soc_dai_link_component codecs_ap0[] = {{
.name = "14880000.s1403x",
.dai_name = "AP0",
}, {
.dai_name = "cod9002x-aif",
},
};
static struct snd_soc_dai_link_component codecs_cp0[] = {{
.name = "14880000.s1403x",
.dai_name = "CP0",
}, {
.dai_name = "cod9002x-aif2",
},
};
static struct snd_soc_dai_link_component codecs_bt[] = {{
.name = "14880000.s1403x",
.dai_name = "BT",
}, {
.dai_name = "dummy-aif2",
},
};
static struct snd_soc_dai_link_component codecs_fm[] = {{
.name = "14880000.s1403x",
.dai_name = "FM",
}, {
.dai_name = "cod9002x-aif",
},
};
static struct snd_soc_dai_link_component codecs_ap1[] = {{
.name = "14880000.s1403x",
.dai_name = "AP1",
}, {
.dai_name = "dummy-aif2",
},
};
static struct snd_soc_dai_link_component codecs_cp1[] = {{
.name = "14880000.s1403x",
.dai_name = "CP1",
}, {
.dai_name = "dummy-aif2",
},
};
static struct snd_soc_dai_link smdk7570_cod9002x_dai[] = {
/* Playback and Recording */
{
.name = "smdk7570-cod9002x",
.stream_name = "i2s0-pri",
.codecs = codecs_ap0,
.num_codecs = ARRAY_SIZE(codecs_ap0),
.ops = &smdk7570_aif1_ops,
},
/* Deep buffer playback */
{
.name = "smdk7570-cod9002x-sec",
.cpu_dai_name = "samsung-i2s-sec",
.stream_name = "i2s0-sec",
.platform_name = "samsung-i2s-sec",
.codecs = codecs_ap0,
.num_codecs = ARRAY_SIZE(codecs_ap0),
.ops = &smdk7570_aif1_ops,
},
/* Voice Call */
{
.name = "cp",
.stream_name = "voice call",
.cpu_dai_name = "smdk7570 voice call",
.platform_name = "snd-soc-dummy",
.codecs = codecs_cp0,
.num_codecs = ARRAY_SIZE(codecs_cp0),
.ops = &smdk7570_aif2_ops,
.ignore_suspend = 1,
},
/* BT */
{
.name = "bt",
.stream_name = "bluetooth audio",
.cpu_dai_name = "smdk7570 BT",
.platform_name = "snd-soc-dummy",
.codecs = codecs_bt,
.num_codecs = ARRAY_SIZE(codecs_bt),
.ops = &smdk7570_aif3_ops,
.ignore_suspend = 1,
},
/* FM */
{
.name = "fm",
.stream_name = "FM audio",
.cpu_dai_name = "smdk7570 FM",
.platform_name = "snd-soc-dummy",
.codecs = codecs_fm,
.num_codecs = ARRAY_SIZE(codecs_fm),
.ops = &smdk7570_aif4_ops,
.ignore_suspend = 1,
},
/* AMP AP Interface */
{
.name = "smdk7570-cod9002x-amp",
.stream_name = "i2s1-pri",
.codecs = codecs_ap1,
.num_codecs = ARRAY_SIZE(codecs_ap1),
.ops = &smdk7570_aif5_ops,
},
/* AMP CP Interface */
{
.name = "cp-amp",
.stream_name = "voice call amp",
.cpu_dai_name = "smdk7570 voice call",
.platform_name = "snd-soc-dummy",
.codecs = codecs_cp1,
.num_codecs = ARRAY_SIZE(codecs_cp1),
.ops = &smdk7570_aif6_ops,
.ignore_suspend = 1,
},
/* SW MIXER1 Interface */
{
.name = "playback-eax0",
.stream_name = "eax0",
.cpu_dai_name = "samsung-eax.0",
.platform_name = "samsung-eax.0",
.codecs = codecs_ap0,
.num_codecs = ARRAY_SIZE(codecs_ap0),
.ops = &smdk7570_aif1_ops,
.ignore_suspend = 1,
},
/* SW MIXER2 Interface */
{
.name = "playback-eax1",
.stream_name = "eax1",
.cpu_dai_name = "samsung-eax.1",
.platform_name = "samsung-eax.1",
.codecs = codecs_ap0,
.num_codecs = ARRAY_SIZE(codecs_ap0),
.ops = &smdk7570_aif1_ops,
.ignore_suspend = 1,
},
/* SW MIXER3 Interface */
{
.name = "playback-eax2",
.stream_name = "eax2",
.cpu_dai_name = "samsung-eax.2",
.platform_name = "samsung-eax.2",
.codecs = codecs_ap0,
.num_codecs = ARRAY_SIZE(codecs_ap0),
.ops = &smdk7570_aif1_ops,
.ignore_suspend = 1,
},
/* SW MIXER4 Interface */
{
.name = "playback-eax3",
.stream_name = "eax3",
.cpu_dai_name = "samsung-eax.3",
.platform_name = "samsung-eax.3",
.codecs = codecs_ap0,
.num_codecs = ARRAY_SIZE(codecs_ap0),
.ops = &smdk7570_aif1_ops,
.ignore_suspend = 1,
},
};
static struct snd_soc_aux_dev audmixer_aux_dev[] = {
{
.init = audmixer_init,
},
};
static struct snd_soc_codec_conf audmixer_codec_conf[] = {
{
.name_prefix = "AudioMixer",
},
};
static struct snd_soc_card smdk7570_cod9002x_card = {
.name = "Smdk7570-I2S",
.owner = THIS_MODULE,
.dai_link = smdk7570_cod9002x_dai,
.num_links = ARRAY_SIZE(smdk7570_cod9002x_dai),
.controls = smdk7570_controls,
.num_controls = ARRAY_SIZE(smdk7570_controls),
.dapm_widgets = smdk7570_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(smdk7570_dapm_widgets),
.dapm_routes = smdk7570_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(smdk7570_dapm_routes),
.late_probe = smdk7570_late_probe,
.set_bias_level = smdk7570_set_bias_level,
.aux_dev = audmixer_aux_dev,
.num_aux_devs = ARRAY_SIZE(audmixer_aux_dev),
.codec_conf = audmixer_codec_conf,
.num_configs = ARRAY_SIZE(audmixer_codec_conf),
};
static int smdk7570_audio_probe(struct platform_device *pdev)
{
int n, ret;
struct device_node *np = pdev->dev.of_node;
struct device_node *cpu_np, *codec_np, *auxdev_np;
struct snd_soc_card *card = &smdk7570_cod9002x_card;
struct cod9002x_machine_priv *priv;
if (!np) {
dev_err(&pdev->dev, "Failed to get device node\n");
return -EINVAL;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card->dev = &pdev->dev;
card->num_links = 0;
ret = snd_soc_register_component(card->dev, &smdk7570_cmpnt,
smdk7570_ext_dai,
ARRAY_SIZE(smdk7570_ext_dai));
if (ret) {
dev_err(&pdev->dev, "Failed to register component: %d\n", ret);
return ret;
}
for (n = 0; n < ARRAY_SIZE(smdk7570_cod9002x_dai); n++) {
/* Skip parsing DT for fully formed dai links */
if (smdk7570_cod9002x_dai[n].platform_name &&
smdk7570_cod9002x_dai[n].codec_name) {
dev_dbg(card->dev,
"Skipping dt for populated dai link %s\n",
smdk7570_cod9002x_dai[n].name);
card->num_links++;
continue;
}
cpu_np = of_parse_phandle(np, "samsung,audio-cpu", n);
if (!cpu_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-cpu' missing\n");
break;
}
codec_np = of_parse_phandle(np, "samsung,audio-codec", n);
if (!codec_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec' missing\n");
break;
}
smdk7570_cod9002x_dai[n].codecs[1].of_node = codec_np;
if (!smdk7570_cod9002x_dai[n].cpu_dai_name)
smdk7570_cod9002x_dai[n].cpu_of_node = cpu_np;
if (!smdk7570_cod9002x_dai[n].platform_name)
smdk7570_cod9002x_dai[n].platform_of_node = cpu_np;
card->num_links++;
}
for (n = 0; n < ARRAY_SIZE(audmixer_aux_dev); n++) {
auxdev_np = of_parse_phandle(np, "samsung,auxdev", n);
if (!auxdev_np) {
dev_err(&pdev->dev,
"Property 'samsung,auxdev' missing\n");
return -EINVAL;
}
audmixer_aux_dev[n].codec_of_node = auxdev_np;
audmixer_codec_conf[n].of_node = auxdev_np;
}
snd_soc_card_set_drvdata(card, priv);
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev, "Failed to register card:%d\n", ret);
return ret;
}
static int smdk7570_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static const struct of_device_id smdk7570_cod9002x_of_match[] = {
{.compatible = "samsung,smdk7570-cod9002x",},
{},
};
MODULE_DEVICE_TABLE(of, smdk7570_cod9002x_of_match);
static struct platform_driver smdk7570_audio_driver = {
.driver = {
.name = "Smdk7570-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(smdk7570_cod9002x_of_match),
},
.probe = smdk7570_audio_probe,
.remove = smdk7570_audio_remove,
};
module_platform_driver(smdk7570_audio_driver);
MODULE_DESCRIPTION("ALSA SoC Smdk7570 COD9002X");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:smdk7570-audio");

View file

@ -0,0 +1,312 @@
/*
* espresso7420_dummy.c
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
/*#include <mach/regs-pmu.h>*/
#include "i2s.h"
#include "i2s-regs.h"
/* ESPRESSO use CLKOUT from AP */
#define ESPRESSO_MCLK_FREQ 24000000
#define ESPRESSO_AUD_PLL_FREQ 491520000
static bool clkout_enabled;
static struct snd_soc_card espresso;
static void espresso_enable_mclk(bool on)
{
pr_debug("%s: %s\n", __func__, on ? "on" : "off");
clkout_enabled = on;
/*
writel(on ? 0x1000 : 0x1001, EXYNOS_PMU_PMU_DEBUG);
*/
}
#if 0 /* later */
static int set_aud_pll_rate(unsigned long rate)
{
struct clk *fout_aud_pll;
fout_aud_pll = clk_get(espresso.dev, "aud_pll");
if (IS_ERR(fout_aud_pll)) {
printk(KERN_ERR "%s: failed to get fout_aud_pll\n", __func__);
return PTR_ERR(fout_aud_pll);
}
if (rate == clk_get_rate(fout_aud_pll))
goto out;
rate += 20; /* margin */
clk_set_rate(fout_aud_pll, rate);
pr_debug("%s: aud_pll rate = %ld\n",
__func__, clk_get_rate(fout_aud_pll));
out:
clk_put(fout_aud_pll);
return 0;
}
#endif
/*
* ESPRESSO I2S DAI operations. (AP master)
*/
static int espresso_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int pll, div, sclk, bfs, psr, rfs, ret;
unsigned long rclk;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
return -EINVAL;
}
switch (params_rate(params)) {
case 16000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
case 88200:
case 96000:
if (bfs == 48)
rfs = 384;
else
rfs = 256;
break;
case 64000:
rfs = 384;
break;
case 8000:
case 11025:
case 12000:
if (bfs == 48)
rfs = 768;
else
rfs = 512;
break;
default:
return -EINVAL;
}
rclk = params_rate(params) * rfs;
switch (rclk) {
case 4096000:
case 5644800:
case 6144000:
case 8467200:
case 9216000:
psr = 8;
break;
case 8192000:
case 11289600:
case 12288000:
case 16934400:
case 18432000:
psr = 4;
break;
case 22579200:
case 24576000:
case 33868800:
case 36864000:
psr = 2;
break;
case 67737600:
case 73728000:
psr = 1;
break;
default:
printk("Not yet supported!\n");
return -EINVAL;
}
/* Set AUD_PLL frequency */
sclk = rclk * psr;
for (div = 2; div <= 16; div++) {
if (sclk * div > ESPRESSO_AUD_PLL_FREQ)
break;
}
pll = sclk * (div - 1);
// later set_aud_pll_rate(pll);
/* CLKOUT(XXTI) for Codec MCLK */
espresso_enable_mclk(true);
/* Set CPU DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
0, SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops espresso_ops = {
.hw_params = espresso_hw_params,
};
static struct snd_soc_dai_link espresso_dai[] = {
{ /* Primary DAI i/f */
.name = "DUMMY PRI",
.stream_name = "i2s0-pri",
.codec_dai_name = "dummy-aif3",
.ops = &espresso_ops,
}, { /* Secondary DAI i/f */
.name = "DUMMY SEC",
.stream_name = "i2s0-sec",
.cpu_dai_name = "samsung-i2s-sec",
.platform_name = "samsung-i2s-sec",
.codec_dai_name = "dummy-aif3",
.ops = &espresso_ops,
}
};
static int espresso_suspend_post(struct snd_soc_card *card)
{
espresso_enable_mclk(false);
return 0;
}
static int espresso_resume_pre(struct snd_soc_card *card)
{
espresso_enable_mclk(true);
return 0;
}
static struct snd_soc_card espresso = {
.name = "ESPRESSO-I2S",
.owner = THIS_MODULE,
.suspend_post = espresso_suspend_post,
.resume_pre = espresso_resume_pre,
.dai_link = espresso_dai,
.num_links = ARRAY_SIZE(espresso_dai),
};
static int espresso_audio_probe(struct platform_device *pdev)
{
int n, ret;
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &espresso;
bool hdmi_avail = true;
card->dev = &pdev->dev;
for (n = 0; np && n < ARRAY_SIZE(espresso_dai); n++) {
if (!espresso_dai[n].cpu_dai_name) {
espresso_dai[n].cpu_of_node = of_parse_phandle(np,
"samsung,audio-cpu", n);
if (!espresso_dai[n].cpu_of_node && hdmi_avail) {
espresso_dai[n].cpu_of_node = of_parse_phandle(np,
"samsung,audio-cpu-hdmi", 0);
hdmi_avail = false;
}
if (!espresso_dai[n].cpu_of_node) {
dev_err(&pdev->dev, "Property "
"'samsung,audio-cpu' missing or invalid\n");
ret = -EINVAL;
}
}
if (!espresso_dai[n].platform_name)
espresso_dai[n].platform_of_node = espresso_dai[n].cpu_of_node;
espresso_dai[n].codec_name = NULL;
espresso_dai[n].codec_of_node = of_parse_phandle(np,
"samsung,audio-codec", n);
if (!espresso_dai[0].codec_of_node) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec' missing or invalid\n");
ret = -EINVAL;
}
}
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret);
return ret;
}
static int espresso_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id samsung_dummy_of_match[] = {
{ .compatible = "samsung,universal-dummy", },
{},
};
MODULE_DEVICE_TABLE(of, samsung_dummy_of_match);
#endif /* CONFIG_OF */
static struct platform_driver espresso_audio_driver = {
.driver = {
.name = "espresso-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(samsung_dummy_of_match),
},
.probe = espresso_audio_probe,
.remove = espresso_audio_remove,
};
module_platform_driver(espresso_audio_driver);
MODULE_DESCRIPTION("ALSA SoC ESPRESSO7420 DUMMY");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:espresso-audio");

View file

@ -0,0 +1,811 @@
/*
* Smdk7870-COD3025X Audio Machine driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <sound/tlv.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/exynos-audmixer.h>
#include "i2s.h"
#include "i2s-regs.h"
#define CODEC_BFS_48KHZ 32
#define CODEC_RFS_48KHZ 512
#define CODEC_SAMPLE_RATE_48KHZ 48000
#define CODEC_BFS_192KHZ 64
#define CODEC_RFS_192KHZ 128
#define CODEC_SAMPLE_RATE_192KHZ 192000
#ifdef CONFIG_SND_SOC_SAMSUNG_VERBOSE_DEBUG
#define RX_SRAM_SIZE (0x2000) /* 8 KB */
#ifdef dev_dbg
#undef dev_dbg
#endif
#define dev_dbg dev_err
#endif
static struct snd_soc_card smdk7870_cod3025x_card;
extern void set_ip_idle(bool value);
struct cod3025x_machine_priv {
struct snd_soc_codec *codec;
int aifrate;
bool use_external_jd;
};
static const struct snd_soc_component_driver smdk7870_cmpnt = {
.name = "Smdk7870-audio",
};
static int smdk7870_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct cod3025x_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
int rfs, bfs;
dev_info(card->dev, "aif1: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
priv->aifrate = params_rate(params);
if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) &&
params_buffer_bytes(params) <= RX_SRAM_SIZE)
set_ip_idle(true);
else
set_ip_idle(false);
if (priv->aifrate == CODEC_SAMPLE_RATE_192KHZ) {
rfs = CODEC_RFS_192KHZ;
bfs = CODEC_BFS_192KHZ;
} else {
rfs = CODEC_RFS_48KHZ;
bfs = CODEC_BFS_48KHZ;
}
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set Codec DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set CPU DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
rfs, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_CDCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_OPCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0) {
dev_err(card->dev,
"aif1: Failed to set SAMSUNG_I2S_RCLKSRC_1\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set BFS\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_RCLK, rfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set RFS\n");
return ret;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_AP);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7870_aif2_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
int bfs, ret;
dev_info(card->dev, "aif2: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif2: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_CP);
if (ret < 0) {
dev_err(card->dev, "aif2: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7870_aif3_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
int bfs, ret;
dev_info(card->dev, "aif3: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif3: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_BT);
if (ret < 0) {
dev_err(card->dev, "aif3: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7870_aif5_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct cod3025x_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
int rfs, bfs;
dev_info(card->dev, "aif5: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
priv->aifrate = params_rate(params);
if (priv->aifrate == CODEC_SAMPLE_RATE_192KHZ) {
rfs = CODEC_RFS_192KHZ;
bfs = CODEC_BFS_192KHZ;
} else {
rfs = CODEC_RFS_48KHZ;
bfs = CODEC_BFS_48KHZ;
}
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set CPU DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
rfs, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set SAMSUNG_I2S_CDCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set SAMSUNG_I2S_OPCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0) {
dev_err(card->dev,
"aif5: Failed to set SAMSUNG_I2S_RCLKSRC_1\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set BFS\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_RCLK, rfs);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set RFS\n");
return ret;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_AP1);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7870_aif6_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
int bfs, ret;
dev_info(card->dev, "aif6: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif6: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_CP1);
if (ret < 0) {
dev_err(card->dev, "aif6: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int smdk7870_aif1_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_AP);
return 0;
}
void smdk7870_aif1_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_AP);
}
static int smdk7870_aif2_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_CP);
return 0;
}
void smdk7870_aif2_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_CP);
}
static int smdk7870_aif3_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_BT);
return 0;
}
void smdk7870_aif3_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_BT);
}
static int smdk7870_aif4_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_BT);
return 0;
}
void smdk7870_aif4_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_BT);
}
static int smdk7870_aif5_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif5: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_AP1);
return 0;
}
void smdk7870_aif5_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif5: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_AP1);
}
static int smdk7870_aif6_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif6: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_CP1);
return 0;
}
void smdk7870_aif6_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif6: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_CP1);
}
static int smdk7870_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
return 0;
}
static int smdk7870_late_probe(struct snd_soc_card *card)
{
dev_dbg(card->dev, "%s called\n", __func__);
return 0;
}
static int audmixer_init(struct snd_soc_component *cmp)
{
dev_dbg(cmp->dev, "%s called\n", __func__);
return 0;
}
static const struct snd_kcontrol_new smdk7870_controls[] = {
};
const struct snd_soc_dapm_widget smdk7870_dapm_widgets[] = {
};
const struct snd_soc_dapm_route smdk7870_dapm_routes[] = {
};
static struct snd_soc_ops smdk7870_aif1_ops = {
.hw_params = smdk7870_aif1_hw_params,
.startup = smdk7870_aif1_startup,
.shutdown = smdk7870_aif1_shutdown,
};
static struct snd_soc_ops smdk7870_aif2_ops = {
.hw_params = smdk7870_aif2_hw_params,
.startup = smdk7870_aif2_startup,
.shutdown = smdk7870_aif2_shutdown,
};
static struct snd_soc_ops smdk7870_aif3_ops = {
.hw_params = smdk7870_aif3_hw_params,
.startup = smdk7870_aif3_startup,
.shutdown = smdk7870_aif3_shutdown,
};
static struct snd_soc_ops smdk7870_aif4_ops = {
.startup = smdk7870_aif4_startup,
.shutdown = smdk7870_aif4_shutdown,
};
static struct snd_soc_ops smdk7870_aif5_ops = {
.hw_params = smdk7870_aif5_hw_params,
.startup = smdk7870_aif5_startup,
.shutdown = smdk7870_aif5_shutdown,
};
static struct snd_soc_ops smdk7870_aif6_ops = {
.hw_params = smdk7870_aif6_hw_params,
.startup = smdk7870_aif6_startup,
.shutdown = smdk7870_aif6_shutdown,
};
static struct snd_soc_dai_driver smdk7870_ext_dai[] = {
{
.name = "smdk7870 voice call",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "smdk7870 BT",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "smdk7870 FM",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
};
static struct snd_soc_dai_link smdk7870_cod3025x_dai[] = {
/* Playback and Recording */
{
.name = "smdk7870-cod3025x",
.stream_name = "i2s0-pri",
.codec_dai_name = "cod3026x-aif",
.ops = &smdk7870_aif1_ops,
},
/* Deep buffer playback */
{
.name = "smdk7870-cod3025x-sec",
.cpu_dai_name = "samsung-i2s-sec",
.stream_name = "i2s0-sec",
.platform_name = "samsung-i2s-sec",
.codec_dai_name = "cod3026x-aif",
.ops = &smdk7870_aif1_ops,
},
/* Voice Call */
{
.name = "cp",
.stream_name = "voice call",
.cpu_dai_name = "smdk7870 voice call",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "cod3026x-aif2",
.ops = &smdk7870_aif2_ops,
.ignore_suspend = 1,
},
/* BT */
{
.name = "bt",
.stream_name = "bluetooth audio",
.cpu_dai_name = "smdk7870 BT",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "dummy-aif2",
.ops = &smdk7870_aif3_ops,
.ignore_suspend = 1,
},
/* FM */
{
.name = "fm",
.stream_name = "FM audio",
.cpu_dai_name = "smdk7870 FM",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "cod3026x-aif",
.ops = &smdk7870_aif4_ops,
.ignore_suspend = 1,
},
/* AMP AP Interface */
{
.name = "smdk7870-cod3026x-amp",
.stream_name = "i2s1-pri",
.codec_dai_name = "dummy-aif2",
.ops = &smdk7870_aif5_ops,
},
/* AMP CP Interface */
{
.name = "cp-amp",
.stream_name = "voice call amp",
.cpu_dai_name = "smdk7870 voice call",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "dummy-aif2",
.ops = &smdk7870_aif6_ops,
.ignore_suspend = 1,
},
};
static struct snd_soc_aux_dev audmixer_aux_dev[] = {
{
.init = audmixer_init,
},
};
static struct snd_soc_codec_conf audmixer_codec_conf[] = {
{
.name_prefix = "AudioMixer",
},
};
static struct snd_soc_card smdk7870_cod3025x_card = {
.name = "Smdk7870-I2S",
.owner = THIS_MODULE,
.dai_link = smdk7870_cod3025x_dai,
.num_links = ARRAY_SIZE(smdk7870_cod3025x_dai),
.controls = smdk7870_controls,
.num_controls = ARRAY_SIZE(smdk7870_controls),
.dapm_widgets = smdk7870_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(smdk7870_dapm_widgets),
.dapm_routes = smdk7870_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(smdk7870_dapm_routes),
.late_probe = smdk7870_late_probe,
.set_bias_level = smdk7870_set_bias_level,
.aux_dev = audmixer_aux_dev,
.num_aux_devs = ARRAY_SIZE(audmixer_aux_dev),
.codec_conf = audmixer_codec_conf,
.num_configs = ARRAY_SIZE(audmixer_codec_conf),
};
static int smdk7870_audio_probe(struct platform_device *pdev)
{
int n, ret;
struct device_node *np = pdev->dev.of_node;
struct device_node *cpu_np, *codec_np, *auxdev_np;
struct snd_soc_card *card = &smdk7870_cod3025x_card;
struct cod3025x_machine_priv *priv;
if (!np) {
dev_err(&pdev->dev, "Failed to get device node\n");
return -EINVAL;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card->dev = &pdev->dev;
card->num_links = 0;
ret = snd_soc_register_component(card->dev, &smdk7870_cmpnt,
smdk7870_ext_dai,
ARRAY_SIZE(smdk7870_ext_dai));
if (ret) {
dev_err(&pdev->dev, "Failed to register component: %d\n", ret);
return ret;
}
for (n = 0; n < ARRAY_SIZE(smdk7870_cod3025x_dai); n++) {
/* Skip parsing DT for fully formed dai links */
if (smdk7870_cod3025x_dai[n].platform_name &&
smdk7870_cod3025x_dai[n].codec_name) {
dev_dbg(card->dev,
"Skipping dt for populated dai link %s\n",
smdk7870_cod3025x_dai[n].name);
card->num_links++;
continue;
}
cpu_np = of_parse_phandle(np, "samsung,audio-cpu", n);
if (!cpu_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-cpu' missing\n");
break;
}
codec_np = of_parse_phandle(np, "samsung,audio-codec", n);
if (!codec_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec' missing\n");
break;
}
smdk7870_cod3025x_dai[n].codec_of_node = codec_np;
if (!smdk7870_cod3025x_dai[n].cpu_dai_name)
smdk7870_cod3025x_dai[n].cpu_of_node = cpu_np;
if (!smdk7870_cod3025x_dai[n].platform_name)
smdk7870_cod3025x_dai[n].platform_of_node = cpu_np;
card->num_links++;
}
for (n = 0; n < ARRAY_SIZE(audmixer_aux_dev); n++) {
auxdev_np = of_parse_phandle(np, "samsung,auxdev", n);
if (!auxdev_np) {
dev_err(&pdev->dev,
"Property 'samsung,auxdev' missing\n");
return -EINVAL;
}
audmixer_aux_dev[n].codec_of_node = auxdev_np;
audmixer_codec_conf[n].of_node = auxdev_np;
}
snd_soc_card_set_drvdata(card, priv);
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev, "Failed to register card:%d\n", ret);
return ret;
}
static int smdk7870_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static const struct of_device_id smdk7870_cod3026x_of_match[] = {
{.compatible = "samsung,smdk7870-cod3026x",},
{},
};
MODULE_DEVICE_TABLE(of, smdk7870_cod3026x_of_match);
static struct platform_driver smdk7870_audio_driver = {
.driver = {
.name = "Smdk7870-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(smdk7870_cod3026x_of_match),
},
.probe = smdk7870_audio_probe,
.remove = smdk7870_audio_remove,
};
module_platform_driver(smdk7870_audio_driver);
MODULE_DESCRIPTION("ALSA SoC Smdk7870 COD3025X");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:smdk7870-audio");

View file

@ -0,0 +1,223 @@
/*
* smdk_spdif.c -- S/PDIF audio for SMDK
*
* Copyright 2010 Samsung Electronics Co. Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
*/
#include <linux/clk.h>
#include <linux/module.h>
#include <sound/soc.h>
#include "spdif.h"
/* Audio clock settings are belonged to board specific part. Every
* board can set audio source clock setting which is matched with H/W
* like this function-'set_audio_clock_heirachy'.
*/
static int set_audio_clock_heirachy(struct platform_device *pdev)
{
struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif;
int ret = 0;
fout_epll = clk_get(NULL, "fout_epll");
if (IS_ERR(fout_epll)) {
printk(KERN_WARNING "%s: Cannot find fout_epll.\n",
__func__);
return -EINVAL;
}
mout_epll = clk_get(NULL, "mout_epll");
if (IS_ERR(mout_epll)) {
printk(KERN_WARNING "%s: Cannot find mout_epll.\n",
__func__);
ret = -EINVAL;
goto out1;
}
sclk_audio0 = clk_get(&pdev->dev, "sclk_audio");
if (IS_ERR(sclk_audio0)) {
printk(KERN_WARNING "%s: Cannot find sclk_audio.\n",
__func__);
ret = -EINVAL;
goto out2;
}
sclk_spdif = clk_get(NULL, "sclk_spdif");
if (IS_ERR(sclk_spdif)) {
printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n",
__func__);
ret = -EINVAL;
goto out3;
}
/* Set audio clock hierarchy for S/PDIF */
clk_set_parent(mout_epll, fout_epll);
clk_set_parent(sclk_audio0, mout_epll);
clk_set_parent(sclk_spdif, sclk_audio0);
clk_put(sclk_spdif);
out3:
clk_put(sclk_audio0);
out2:
clk_put(mout_epll);
out1:
clk_put(fout_epll);
return ret;
}
/* We should haved to set clock directly on this part because of clock
* scheme of Samsudng SoCs did not support to set rates from abstrct
* clock of it's hierarchy.
*/
static int set_audio_clock_rate(unsigned long epll_rate,
unsigned long audio_rate)
{
struct clk *fout_epll, *sclk_spdif;
fout_epll = clk_get(NULL, "fout_epll");
if (IS_ERR(fout_epll)) {
printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
return -ENOENT;
}
clk_set_rate(fout_epll, epll_rate);
clk_put(fout_epll);
sclk_spdif = clk_get(NULL, "sclk_spdif");
if (IS_ERR(sclk_spdif)) {
printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__);
return -ENOENT;
}
clk_set_rate(sclk_spdif, audio_rate);
clk_put(sclk_spdif);
return 0;
}
static int smdk_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
unsigned long pll_out, rclk_rate;
int ret, ratio;
switch (params_rate(params)) {
case 44100:
pll_out = 45158400;
break;
case 32000:
case 48000:
case 96000:
pll_out = 49152000;
break;
default:
return -EINVAL;
}
/* Setting ratio to 512fs helps to use S/PDIF with HDMI without
* modify S/PDIF ASoC machine driver.
*/
ratio = 512;
rclk_rate = params_rate(params) * ratio;
/* Set audio source clock rates */
ret = set_audio_clock_rate(pll_out, rclk_rate);
if (ret < 0)
return ret;
/* Set S/PDIF uses internal source clock */
ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK,
rclk_rate, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return ret;
}
static struct snd_soc_ops smdk_spdif_ops = {
.hw_params = smdk_hw_params,
};
static struct snd_soc_dai_link smdk_dai = {
.name = "S/PDIF",
.stream_name = "S/PDIF PCM Playback",
.platform_name = "samsung-spdif",
.cpu_dai_name = "samsung-spdif",
.codec_dai_name = "dit-hifi",
.codec_name = "spdif-dit",
.ops = &smdk_spdif_ops,
};
static struct snd_soc_card smdk = {
.name = "SMDK-S/PDIF",
.owner = THIS_MODULE,
.dai_link = &smdk_dai,
.num_links = 1,
};
static struct platform_device *smdk_snd_spdif_dit_device;
static struct platform_device *smdk_snd_spdif_device;
static int __init smdk_init(void)
{
int ret;
smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1);
if (!smdk_snd_spdif_dit_device)
return -ENOMEM;
ret = platform_device_add(smdk_snd_spdif_dit_device);
if (ret)
goto err1;
smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1);
if (!smdk_snd_spdif_device) {
ret = -ENOMEM;
goto err2;
}
platform_set_drvdata(smdk_snd_spdif_device, &smdk);
ret = platform_device_add(smdk_snd_spdif_device);
if (ret)
goto err3;
/* Set audio clock hierarchy manually */
ret = set_audio_clock_heirachy(smdk_snd_spdif_device);
if (ret)
goto err4;
return 0;
err4:
platform_device_del(smdk_snd_spdif_device);
err3:
platform_device_put(smdk_snd_spdif_device);
err2:
platform_device_del(smdk_snd_spdif_dit_device);
err1:
platform_device_put(smdk_snd_spdif_dit_device);
return ret;
}
static void __exit smdk_exit(void)
{
platform_device_unregister(smdk_snd_spdif_device);
platform_device_unregister(smdk_snd_spdif_dit_device);
}
module_init(smdk_init);
module_exit(smdk_exit);
MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,253 @@
/*
* smdk_wm8580.c
*
* Copyright (c) 2009 Samsung Electronics Co. Ltd
* Author: Jaswinder Singh <jassisinghbrar@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <asm/mach-types.h>
#include "../codecs/wm8580.h"
#include "i2s.h"
/*
* Default CFG switch settings to use this driver:
*
* SMDK6410: Set CFG1 1-3 Off, CFG2 1-4 On
*/
/* SMDK has a 12MHZ crystal attached to WM8580 */
#define SMDK_WM8580_FREQ 12000000
static int smdk_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
unsigned int pll_out;
int bfs, rfs, ret;
switch (params_width(params)) {
case 8:
bfs = 16;
break;
case 16:
bfs = 32;
break;
default:
return -EINVAL;
}
/* The Fvco for WM8580 PLLs must fall within [90,100]MHz.
* This criterion can't be met if we request PLL output
* as {8000x256, 64000x256, 11025x256}Hz.
* As a wayout, we rather change rfs to a minimum value that
* results in (params_rate(params) * rfs), and itself, acceptable
* to both - the CODEC and the CPU.
*/
switch (params_rate(params)) {
case 16000:
case 22050:
case 32000:
case 44100:
case 48000:
case 88200:
case 96000:
rfs = 256;
break;
case 64000:
rfs = 384;
break;
case 8000:
case 11025:
rfs = 512;
break;
default:
return -EINVAL;
}
pll_out = params_rate(params) * rfs;
/* Set the Codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
/* Set the AP DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
/* Set WM8580 to drive MCLK from its PLLA */
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK,
WM8580_CLKSRC_PLLA);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8580_PLLA, 0,
SMDK_WM8580_FREQ, pll_out);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA,
pll_out, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
/*
* SMDK WM8580 DAI operations.
*/
static struct snd_soc_ops smdk_ops = {
.hw_params = smdk_hw_params,
};
/* SMDK Playback widgets */
static const struct snd_soc_dapm_widget smdk_wm8580_dapm_widgets[] = {
SND_SOC_DAPM_HP("Front", NULL),
SND_SOC_DAPM_HP("Center+Sub", NULL),
SND_SOC_DAPM_HP("Rear", NULL),
SND_SOC_DAPM_MIC("MicIn", NULL),
SND_SOC_DAPM_LINE("LineIn", NULL),
};
/* SMDK-PAIFTX connections */
static const struct snd_soc_dapm_route smdk_wm8580_audio_map[] = {
/* MicIn feeds AINL */
{"AINL", NULL, "MicIn"},
/* LineIn feeds AINL/R */
{"AINL", NULL, "LineIn"},
{"AINR", NULL, "LineIn"},
/* Front Left/Right are fed VOUT1L/R */
{"Front", NULL, "VOUT1L"},
{"Front", NULL, "VOUT1R"},
/* Center/Sub are fed VOUT2L/R */
{"Center+Sub", NULL, "VOUT2L"},
{"Center+Sub", NULL, "VOUT2R"},
/* Rear Left/Right are fed VOUT3L/R */
{"Rear", NULL, "VOUT3L"},
{"Rear", NULL, "VOUT3R"},
};
static int smdk_wm8580_init_paiftx(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
/* Enabling the microphone requires the fitting of a 0R
* resistor to connect the line from the microphone jack.
*/
snd_soc_dapm_disable_pin(dapm, "MicIn");
return 0;
}
enum {
PRI_PLAYBACK = 0,
PRI_CAPTURE,
SEC_PLAYBACK,
};
static struct snd_soc_dai_link smdk_dai[] = {
[PRI_PLAYBACK] = { /* Primary Playback i/f */
.name = "WM8580 PAIF RX",
.stream_name = "Playback",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8580-hifi-playback",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8580.0-001b",
.ops = &smdk_ops,
},
[PRI_CAPTURE] = { /* Primary Capture i/f */
.name = "WM8580 PAIF TX",
.stream_name = "Capture",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8580-hifi-capture",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8580.0-001b",
.init = smdk_wm8580_init_paiftx,
.ops = &smdk_ops,
},
[SEC_PLAYBACK] = { /* Sec_Fifo Playback i/f */
.name = "Sec_FIFO TX",
.stream_name = "Playback",
.cpu_dai_name = "samsung-i2s-sec",
.codec_dai_name = "wm8580-hifi-playback",
.platform_name = "samsung-i2s-sec",
.codec_name = "wm8580.0-001b",
.ops = &smdk_ops,
},
};
static struct snd_soc_card smdk = {
.name = "SMDK-I2S",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = 2,
.dapm_widgets = smdk_wm8580_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(smdk_wm8580_dapm_widgets),
.dapm_routes = smdk_wm8580_audio_map,
.num_dapm_routes = ARRAY_SIZE(smdk_wm8580_audio_map),
};
static struct platform_device *smdk_snd_device;
static int __init smdk_audio_init(void)
{
int ret;
char *str;
if (machine_is_smdkc100()
|| machine_is_smdkv210() || machine_is_smdkc110()) {
smdk.num_links = 3;
} else if (machine_is_smdk6410()) {
str = (char *)smdk_dai[PRI_PLAYBACK].cpu_dai_name;
str[strlen(str) - 1] = '2';
str = (char *)smdk_dai[PRI_CAPTURE].cpu_dai_name;
str[strlen(str) - 1] = '2';
}
smdk_snd_device = platform_device_alloc("soc-audio", -1);
if (!smdk_snd_device)
return -ENOMEM;
platform_set_drvdata(smdk_snd_device, &smdk);
ret = platform_device_add(smdk_snd_device);
if (ret)
platform_device_put(smdk_snd_device);
return ret;
}
module_init(smdk_audio_init);
static void __exit smdk_audio_exit(void)
{
platform_device_unregister(smdk_snd_device);
}
module_exit(smdk_audio_exit);
MODULE_AUTHOR("Jaswinder Singh, jassisinghbrar@gmail.com");
MODULE_DESCRIPTION("ALSA SoC SMDK WM8580");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,186 @@
/*
* sound/soc/samsung/smdk_wm8580pcm.c
*
* Copyright (c) 2011 Samsung Electronics Co. Ltd
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/pcm.h>
#include <asm/mach-types.h>
#include "../codecs/wm8580.h"
#include "dma.h"
#include "pcm.h"
/*
* Board Settings:
* o '1' means 'ON'
* o '0' means 'OFF'
* o 'X' means 'Don't care'
*
* SMDK6410 Base B/D: CFG1-0000, CFG2-1111
* SMDKC110, SMDKV210: CFGB11-100100, CFGB12-0000
*/
#define SMDK_WM8580_EXT_OSC 12000000
#define SMDK_WM8580_EXT_MCLK 4096000
#define SMDK_WM8580_EXT_VOICE 2048000
static unsigned long mclk_freq;
static unsigned long xtal_freq;
/*
* If MCLK clock directly gets from XTAL, we don't have to use PLL
* to make MCLK, but if XTAL clock source connects with other codec
* pin (like XTI), we should have to set codec's PLL to make MCLK.
* Because Samsung SoC does not support pcmcdclk output like I2S.
*/
static int smdk_wm8580_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int rfs, ret;
switch (params_rate(params)) {
case 8000:
break;
default:
printk(KERN_ERR "%s:%d Sampling Rate %u not supported!\n",
__func__, __LINE__, params_rate(params));
return -EINVAL;
}
rfs = mclk_freq / params_rate(params) / 2;
/* Set the codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* Set the cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_B
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
if (mclk_freq == xtal_freq) {
ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_MCLK,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK,
WM8580_CLKSRC_MCLK);
if (ret < 0)
return ret;
} else {
ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK,
WM8580_CLKSRC_PLLA);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8580_PLLA, 0,
xtal_freq, mclk_freq);
if (ret < 0)
return ret;
}
/* Set PCM source clock on CPU */
ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* Set SCLK_DIV for making bclk */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops smdk_wm8580_pcm_ops = {
.hw_params = smdk_wm8580_pcm_hw_params,
};
static struct snd_soc_dai_link smdk_dai[] = {
{
.name = "WM8580 PAIF PCM RX",
.stream_name = "Playback",
.cpu_dai_name = "samsung-pcm.0",
.codec_dai_name = "wm8580-hifi-playback",
.platform_name = "samsung-audio",
.codec_name = "wm8580.0-001b",
.ops = &smdk_wm8580_pcm_ops,
}, {
.name = "WM8580 PAIF PCM TX",
.stream_name = "Capture",
.cpu_dai_name = "samsung-pcm.0",
.codec_dai_name = "wm8580-hifi-capture",
.platform_name = "samsung-pcm.0",
.codec_name = "wm8580.0-001b",
.ops = &smdk_wm8580_pcm_ops,
},
};
static struct snd_soc_card smdk_pcm = {
.name = "SMDK-PCM",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = 2,
};
/*
* After SMDKC110 Base Board's Rev is '0.1', 12MHz External OSC(X1)
* is absent (or not connected), so we connect EXT_VOICE_CLK(OSC4),
* 2.0484Mhz, directly with MCLK both Codec and SoC.
*/
static int snd_smdk_probe(struct platform_device *pdev)
{
int ret = 0;
xtal_freq = SMDK_WM8580_EXT_OSC;
mclk_freq = SMDK_WM8580_EXT_MCLK;
if (machine_is_smdkc110() || machine_is_smdkv210())
xtal_freq = mclk_freq = SMDK_WM8580_EXT_VOICE;
smdk_pcm.dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, &smdk_pcm);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret);
return ret;
}
static struct platform_driver snd_smdk_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "samsung-smdk-pcm",
},
.probe = snd_smdk_probe,
};
module_platform_driver(snd_smdk_driver);
MODULE_AUTHOR("Sangbeom Kim, <sbkim73@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC SMDK WM8580 for PCM");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,205 @@
/*
* smdk_wm8994.c
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include "../codecs/wm8994.h"
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
/*
* Default CFG switch settings to use this driver:
* SMDKV310: CFG5-1000, CFG7-111111
*/
/*
* Configure audio route as :-
* $ amixer sset 'DAC1' on,on
* $ amixer sset 'Right Headphone Mux' 'DAC'
* $ amixer sset 'Left Headphone Mux' 'DAC'
* $ amixer sset 'DAC1R Mixer AIF1.1' on
* $ amixer sset 'DAC1L Mixer AIF1.1' on
* $ amixer sset 'IN2L' on
* $ amixer sset 'IN2L PGA IN2LN' on
* $ amixer sset 'MIXINL IN2L' on
* $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on
* $ amixer sset 'IN2R' on
* $ amixer sset 'IN2R PGA IN2RN' on
* $ amixer sset 'MIXINR IN2R' on
* $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on
*/
/* SMDK has a 16.934MHZ crystal attached to WM8994 */
#define SMDK_WM8994_FREQ 16934000
struct smdk_wm8994_data {
int mclk1_rate;
};
/* Default SMDKs */
static struct smdk_wm8994_data smdk_board_data = {
.mclk1_rate = SMDK_WM8994_FREQ,
};
static int smdk_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
unsigned int pll_out;
int ret;
/* AIF1CLK should be >=3MHz for optimal performance */
if (params_width(params) == 24)
pll_out = params_rate(params) * 384;
else if (params_rate(params) == 8000 || params_rate(params) == 11025)
pll_out = params_rate(params) * 512;
else
pll_out = params_rate(params) * 256;
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
SMDK_WM8994_FREQ, pll_out);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
pll_out, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
/*
* SMDK WM8994 DAI operations.
*/
static struct snd_soc_ops smdk_ops = {
.hw_params = smdk_hw_params,
};
static int smdk_wm8994_init_paiftx(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
/* Other pins NC */
snd_soc_dapm_nc_pin(dapm, "HPOUT2P");
snd_soc_dapm_nc_pin(dapm, "HPOUT2N");
snd_soc_dapm_nc_pin(dapm, "SPKOUTLN");
snd_soc_dapm_nc_pin(dapm, "SPKOUTLP");
snd_soc_dapm_nc_pin(dapm, "SPKOUTRP");
snd_soc_dapm_nc_pin(dapm, "SPKOUTRN");
snd_soc_dapm_nc_pin(dapm, "LINEOUT1N");
snd_soc_dapm_nc_pin(dapm, "LINEOUT1P");
snd_soc_dapm_nc_pin(dapm, "LINEOUT2N");
snd_soc_dapm_nc_pin(dapm, "LINEOUT2P");
snd_soc_dapm_nc_pin(dapm, "IN1LP");
snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN");
snd_soc_dapm_nc_pin(dapm, "IN1RP");
snd_soc_dapm_nc_pin(dapm, "IN2RP:VXRP");
return 0;
}
static struct snd_soc_dai_link smdk_dai[] = {
{ /* Primary DAI i/f */
.name = "WM8994 AIF1",
.stream_name = "Pri_Dai",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8994-codec",
.init = smdk_wm8994_init_paiftx,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ops = &smdk_ops,
}, { /* Sec_Fifo Playback i/f */
.name = "Sec_FIFO TX",
.stream_name = "Sec_Dai",
.cpu_dai_name = "samsung-i2s-sec",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-i2s-sec",
.codec_name = "wm8994-codec",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ops = &smdk_ops,
},
};
static struct snd_soc_card smdk = {
.name = "SMDK-I2S",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = ARRAY_SIZE(smdk_dai),
};
static const struct of_device_id samsung_wm8994_of_match[] = {
{ .compatible = "samsung,smdk-wm8994", .data = &smdk_board_data },
{},
};
MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match);
static int smdk_audio_probe(struct platform_device *pdev)
{
int ret;
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &smdk;
struct smdk_wm8994_data *board;
const struct of_device_id *id;
card->dev = &pdev->dev;
board = devm_kzalloc(&pdev->dev, sizeof(*board), GFP_KERNEL);
if (!board)
return -ENOMEM;
if (np) {
smdk_dai[0].cpu_dai_name = NULL;
smdk_dai[0].cpu_of_node = of_parse_phandle(np,
"samsung,i2s-controller", 0);
if (!smdk_dai[0].cpu_of_node) {
dev_err(&pdev->dev,
"Property 'samsung,i2s-controller' missing or invalid\n");
ret = -EINVAL;
}
smdk_dai[0].platform_name = NULL;
smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node;
}
id = of_match_device(of_match_ptr(samsung_wm8994_of_match), &pdev->dev);
if (id)
*board = *((struct smdk_wm8994_data *)id->data);
platform_set_drvdata(pdev, board);
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret);
return ret;
}
static struct platform_driver smdk_audio_driver = {
.driver = {
.name = "smdk-audio-wm8994",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_wm8994_of_match),
.pm = &snd_soc_pm_ops,
},
.probe = smdk_audio_probe,
};
module_platform_driver(smdk_audio_driver);
MODULE_DESCRIPTION("ALSA SoC SMDK WM8994");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:smdk-audio-wm8994");

View file

@ -0,0 +1,156 @@
/*
* sound/soc/samsung/smdk_wm8994pcm.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "../codecs/wm8994.h"
#include "dma.h"
#include "pcm.h"
/*
* Board Settings:
* o '1' means 'ON'
* o '0' means 'OFF'
* o 'X' means 'Don't care'
*
* SMDKC210, SMDKV310: CFG3- 1001, CFG5-1000, CFG7-111111
*/
/*
* Configure audio route as :-
* $ amixer sset 'DAC1' on,on
* $ amixer sset 'Right Headphone Mux' 'DAC'
* $ amixer sset 'Left Headphone Mux' 'DAC'
* $ amixer sset 'DAC1R Mixer AIF1.1' on
* $ amixer sset 'DAC1L Mixer AIF1.1' on
* $ amixer sset 'IN2L' on
* $ amixer sset 'IN2L PGA IN2LN' on
* $ amixer sset 'MIXINL IN2L' on
* $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on
* $ amixer sset 'IN2R' on
* $ amixer sset 'IN2R PGA IN2RN' on
* $ amixer sset 'MIXINR IN2R' on
* $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on
*/
/* SMDK has a 16.9344MHZ crystal attached to WM8994 */
#define SMDK_WM8994_FREQ 16934400
static int smdk_wm8994_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
unsigned long mclk_freq;
int rfs, ret;
switch(params_rate(params)) {
case 8000:
rfs = 512;
break;
default:
dev_err(cpu_dai->dev, "%s:%d Sampling Rate %u not supported!\n",
__func__, __LINE__, params_rate(params));
return -EINVAL;
}
mclk_freq = params_rate(params) * rfs;
/* Set the codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* Set the cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_B
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
SMDK_WM8994_FREQ, mclk_freq);
if (ret < 0)
return ret;
/* Set PCM source clock on CPU */
ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* Set SCLK_DIV for making bclk */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops smdk_wm8994_pcm_ops = {
.hw_params = smdk_wm8994_pcm_hw_params,
};
static struct snd_soc_dai_link smdk_dai[] = {
{
.name = "WM8994 PAIF PCM",
.stream_name = "Primary PCM",
.cpu_dai_name = "samsung-pcm.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-pcm.0",
.codec_name = "wm8994-codec",
.ops = &smdk_wm8994_pcm_ops,
},
};
static struct snd_soc_card smdk_pcm = {
.name = "SMDK-PCM",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = 1,
};
static int snd_smdk_probe(struct platform_device *pdev)
{
int ret = 0;
smdk_pcm.dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, &smdk_pcm);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret);
return ret;
}
static struct platform_driver snd_smdk_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "samsung-smdk-pcm",
},
.probe = snd_smdk_probe,
};
module_platform_driver(snd_smdk_driver);
MODULE_AUTHOR("Sangbeom Kim, <sbkim73@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC SMDK WM8994 for PCM");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,108 @@
/*
* smdk_wm9713.c -- SoC audio for SMDK
*
* Copyright 2010 Samsung Electronics Co. Ltd.
* Author: Jaswinder Singh Brar <jassisinghbrar@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
*/
#include <linux/module.h>
#include <sound/soc.h>
static struct snd_soc_card smdk;
/*
* Default CFG switch settings to use this driver:
*
* SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off
* SMDKC100: Set CFG6 1-3 On, CFG7 1 On
* SMDKC110: Set CFGB10 1-2 Off, CFGB12 1-3 On
* SMDKV210: Set CFGB10 1-2 Off, CFGB12 1-3 On
* SMDKV310: Set CFG2 1-2 Off, CFG4 All On, CFG7 All Off, CFG8 1-On
*/
/*
Playback (HeadPhone):-
$ amixer sset 'Headphone' unmute
$ amixer sset 'Right Headphone Out Mux' 'Headphone'
$ amixer sset 'Left Headphone Out Mux' 'Headphone'
$ amixer sset 'Right HP Mixer PCM' unmute
$ amixer sset 'Left HP Mixer PCM' unmute
Capture (LineIn):-
$ amixer sset 'Right Capture Source' 'Line'
$ amixer sset 'Left Capture Source' 'Line'
*/
static struct snd_soc_dai_link smdk_dai = {
.name = "AC97",
.stream_name = "AC97 PCM",
.platform_name = "samsung-ac97",
.cpu_dai_name = "samsung-ac97",
.codec_dai_name = "wm9713-hifi",
.codec_name = "wm9713-codec",
};
static struct snd_soc_card smdk = {
.name = "SMDK WM9713",
.owner = THIS_MODULE,
.dai_link = &smdk_dai,
.num_links = 1,
};
static struct platform_device *smdk_snd_wm9713_device;
static struct platform_device *smdk_snd_ac97_device;
static int __init smdk_init(void)
{
int ret;
smdk_snd_wm9713_device = platform_device_alloc("wm9713-codec", -1);
if (!smdk_snd_wm9713_device)
return -ENOMEM;
ret = platform_device_add(smdk_snd_wm9713_device);
if (ret)
goto err1;
smdk_snd_ac97_device = platform_device_alloc("soc-audio", -1);
if (!smdk_snd_ac97_device) {
ret = -ENOMEM;
goto err2;
}
platform_set_drvdata(smdk_snd_ac97_device, &smdk);
ret = platform_device_add(smdk_snd_ac97_device);
if (ret)
goto err3;
return 0;
err3:
platform_device_put(smdk_snd_ac97_device);
err2:
platform_device_del(smdk_snd_wm9713_device);
err1:
platform_device_put(smdk_snd_wm9713_device);
return ret;
}
static void __exit smdk_exit(void)
{
platform_device_unregister(smdk_snd_ac97_device);
platform_device_unregister(smdk_snd_wm9713_device);
}
module_init(smdk_init);
module_exit(smdk_exit);
/* Module information */
MODULE_AUTHOR("Jaswinder Singh Brar, jassisinghbrar@gmail.com");
MODULE_DESCRIPTION("ALSA SoC SMDK+WM9713");
MODULE_LICENSE("GPL");

128
sound/soc/samsung/snow.c Normal file
View file

@ -0,0 +1,128 @@
/*
* ASoC machine driver for Snow boards
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <sound/soc.h>
#include "i2s.h"
#define FIN_PLL_RATE 24000000
static struct snd_soc_dai_link snow_dai[] = {
{
.name = "Primary",
.stream_name = "Primary",
.codec_dai_name = "HiFi",
.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
},
};
static int snow_late_probe(struct snd_soc_card *card)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
struct snd_soc_dai *cpu_dai = card->rtd[0].cpu_dai;
int ret;
/* Set the MCLK rate for the codec */
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
FIN_PLL_RATE, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* Select I2S Bus clock to set RCLK and BCLK */
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0,
0, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_card snow_snd = {
.name = "Snow-I2S",
.dai_link = snow_dai,
.num_links = ARRAY_SIZE(snow_dai),
.late_probe = snow_late_probe,
};
static int snow_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &snow_snd;
struct device_node *i2s_node, *codec_node;
int i, ret;
i2s_node = of_parse_phandle(pdev->dev.of_node,
"samsung,i2s-controller", 0);
if (!i2s_node) {
dev_err(&pdev->dev,
"Property 'i2s-controller' missing or invalid\n");
return -EINVAL;
}
codec_node = of_parse_phandle(pdev->dev.of_node,
"samsung,audio-codec", 0);
if (!codec_node) {
dev_err(&pdev->dev,
"Property 'audio-codec' missing or invalid\n");
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(snow_dai); i++) {
snow_dai[i].codec_of_node = codec_node;
snow_dai[i].cpu_of_node = i2s_node;
snow_dai[i].platform_of_node = i2s_node;
}
card->dev = &pdev->dev;
/* Update card-name if provided through DT, else use default name */
snd_soc_of_parse_card_name(card, "samsung,model");
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
return ret;
}
return ret;
}
static const struct of_device_id snow_of_match[] = {
{ .compatible = "google,snow-audio-max98090", },
{ .compatible = "google,snow-audio-max98091", },
{ .compatible = "google,snow-audio-max98095", },
{},
};
MODULE_DEVICE_TABLE(of, snow_of_match);
static struct platform_driver snow_driver = {
.driver = {
.name = "snow-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = snow_of_match,
},
.probe = snow_probe,
};
module_platform_driver(snow_driver);
MODULE_DESCRIPTION("ALSA SoC Audio machine driver for Snow");
MODULE_LICENSE("GPL");

489
sound/soc/samsung/spdif.c Normal file
View file

@ -0,0 +1,489 @@
/* sound/soc/samsung/spdif.c
*
* ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
*
* Copyright (c) 2010 Samsung Electronics Co. Ltd
* http://www.samsung.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <linux/platform_data/asoc-s3c.h>
#include "dma.h"
#include "spdif.h"
/* Registers */
#define CLKCON 0x00
#define CON 0x04
#define BSTAS 0x08
#define CSTAS 0x0C
#define DATA_OUTBUF 0x10
#define DCNT 0x14
#define BSTAS_S 0x18
#define DCNT_S 0x1C
#define CLKCTL_MASK 0x7
#define CLKCTL_MCLK_EXT (0x1 << 2)
#define CLKCTL_PWR_ON (0x1 << 0)
#define CON_MASK 0x3ffffff
#define CON_FIFO_TH_SHIFT 19
#define CON_FIFO_TH_MASK (0x7 << 19)
#define CON_USERDATA_23RDBIT (0x1 << 12)
#define CON_SW_RESET (0x1 << 5)
#define CON_MCLKDIV_MASK (0x3 << 3)
#define CON_MCLKDIV_256FS (0x0 << 3)
#define CON_MCLKDIV_384FS (0x1 << 3)
#define CON_MCLKDIV_512FS (0x2 << 3)
#define CON_PCM_MASK (0x3 << 1)
#define CON_PCM_16BIT (0x0 << 1)
#define CON_PCM_20BIT (0x1 << 1)
#define CON_PCM_24BIT (0x2 << 1)
#define CON_PCM_DATA (0x1 << 0)
#define CSTAS_MASK 0x3fffffff
#define CSTAS_SAMP_FREQ_MASK (0xF << 24)
#define CSTAS_SAMP_FREQ_44 (0x0 << 24)
#define CSTAS_SAMP_FREQ_48 (0x2 << 24)
#define CSTAS_SAMP_FREQ_32 (0x3 << 24)
#define CSTAS_SAMP_FREQ_96 (0xA << 24)
#define CSTAS_CATEGORY_MASK (0xFF << 8)
#define CSTAS_CATEGORY_CODE_CDP (0x01 << 8)
#define CSTAS_NO_COPYRIGHT (0x1 << 2)
/**
* struct samsung_spdif_info - Samsung S/PDIF Controller information
* @lock: Spin lock for S/PDIF.
* @dev: The parent device passed to use from the probe.
* @regs: The pointer to the device register block.
* @clk_rate: Current clock rate for calcurate ratio.
* @pclk: The peri-clock pointer for spdif master operation.
* @sclk: The source clock pointer for making sync signals.
* @save_clkcon: Backup clkcon reg. in suspend.
* @save_con: Backup con reg. in suspend.
* @save_cstas: Backup cstas reg. in suspend.
* @dma_playback: DMA information for playback channel.
*/
struct samsung_spdif_info {
spinlock_t lock;
struct device *dev;
void __iomem *regs;
unsigned long clk_rate;
struct clk *pclk;
struct clk *sclk;
u32 saved_clkcon;
u32 saved_con;
u32 saved_cstas;
struct s3c_dma_params *dma_playback;
};
static struct s3c_dma_params spdif_stereo_out;
static struct samsung_spdif_info spdif_info;
static inline struct samsung_spdif_info *to_info(struct snd_soc_dai *cpu_dai)
{
return snd_soc_dai_get_drvdata(cpu_dai);
}
static void spdif_snd_txctrl(struct samsung_spdif_info *spdif, int on)
{
void __iomem *regs = spdif->regs;
u32 clkcon;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
if (on)
writel(clkcon | CLKCTL_PWR_ON, regs + CLKCON);
else
writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
}
static int spdif_set_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct samsung_spdif_info *spdif = to_info(cpu_dai);
u32 clkcon;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
clkcon = readl(spdif->regs + CLKCON);
if (clk_id == SND_SOC_SPDIF_INT_MCLK)
clkcon &= ~CLKCTL_MCLK_EXT;
else
clkcon |= CLKCTL_MCLK_EXT;
writel(clkcon, spdif->regs + CLKCON);
spdif->clk_rate = freq;
return 0;
}
static int spdif_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai);
unsigned long flags;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
spin_lock_irqsave(&spdif->lock, flags);
spdif_snd_txctrl(spdif, 1);
spin_unlock_irqrestore(&spdif->lock, flags);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
spin_lock_irqsave(&spdif->lock, flags);
spdif_snd_txctrl(spdif, 0);
spin_unlock_irqrestore(&spdif->lock, flags);
break;
default:
return -EINVAL;
}
return 0;
}
static int spdif_sysclk_ratios[] = {
512, 384, 256,
};
static int spdif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *socdai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai);
void __iomem *regs = spdif->regs;
struct s3c_dma_params *dma_data;
u32 con, clkcon, cstas;
unsigned long flags;
int i, ratio;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dma_data = spdif->dma_playback;
else {
dev_err(spdif->dev, "Capture is not supported\n");
return -EINVAL;
}
snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);
spin_lock_irqsave(&spdif->lock, flags);
con = readl(regs + CON) & CON_MASK;
cstas = readl(regs + CSTAS) & CSTAS_MASK;
clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
con &= ~CON_FIFO_TH_MASK;
con |= (0x7 << CON_FIFO_TH_SHIFT);
con |= CON_USERDATA_23RDBIT;
con |= CON_PCM_DATA;
con &= ~CON_PCM_MASK;
switch (params_width(params)) {
case 16:
con |= CON_PCM_16BIT;
break;
default:
dev_err(spdif->dev, "Unsupported data size.\n");
goto err;
}
ratio = spdif->clk_rate / params_rate(params);
for (i = 0; i < ARRAY_SIZE(spdif_sysclk_ratios); i++)
if (ratio == spdif_sysclk_ratios[i])
break;
if (i == ARRAY_SIZE(spdif_sysclk_ratios)) {
dev_err(spdif->dev, "Invalid clock ratio %ld/%d\n",
spdif->clk_rate, params_rate(params));
goto err;
}
con &= ~CON_MCLKDIV_MASK;
switch (ratio) {
case 256:
con |= CON_MCLKDIV_256FS;
break;
case 384:
con |= CON_MCLKDIV_384FS;
break;
case 512:
con |= CON_MCLKDIV_512FS;
break;
}
cstas &= ~CSTAS_SAMP_FREQ_MASK;
switch (params_rate(params)) {
case 44100:
cstas |= CSTAS_SAMP_FREQ_44;
break;
case 48000:
cstas |= CSTAS_SAMP_FREQ_48;
break;
case 32000:
cstas |= CSTAS_SAMP_FREQ_32;
break;
case 96000:
cstas |= CSTAS_SAMP_FREQ_96;
break;
default:
dev_err(spdif->dev, "Invalid sampling rate %d\n",
params_rate(params));
goto err;
}
cstas &= ~CSTAS_CATEGORY_MASK;
cstas |= CSTAS_CATEGORY_CODE_CDP;
cstas |= CSTAS_NO_COPYRIGHT;
writel(con, regs + CON);
writel(cstas, regs + CSTAS);
writel(clkcon, regs + CLKCON);
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
err:
spin_unlock_irqrestore(&spdif->lock, flags);
return -EINVAL;
}
static void spdif_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai);
void __iomem *regs = spdif->regs;
u32 con, clkcon;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
con = readl(regs + CON) & CON_MASK;
clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
writel(con | CON_SW_RESET, regs + CON);
cpu_relax();
writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
}
#ifdef CONFIG_PM
static int spdif_suspend(struct snd_soc_dai *cpu_dai)
{
struct samsung_spdif_info *spdif = to_info(cpu_dai);
u32 con = spdif->saved_con;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
spdif->saved_clkcon = readl(spdif->regs + CLKCON) & CLKCTL_MASK;
spdif->saved_con = readl(spdif->regs + CON) & CON_MASK;
spdif->saved_cstas = readl(spdif->regs + CSTAS) & CSTAS_MASK;
writel(con | CON_SW_RESET, spdif->regs + CON);
cpu_relax();
return 0;
}
static int spdif_resume(struct snd_soc_dai *cpu_dai)
{
struct samsung_spdif_info *spdif = to_info(cpu_dai);
dev_dbg(spdif->dev, "Entered %s\n", __func__);
writel(spdif->saved_clkcon, spdif->regs + CLKCON);
writel(spdif->saved_con, spdif->regs + CON);
writel(spdif->saved_cstas, spdif->regs + CSTAS);
return 0;
}
#else
#define spdif_suspend NULL
#define spdif_resume NULL
#endif
static const struct snd_soc_dai_ops spdif_dai_ops = {
.set_sysclk = spdif_set_sysclk,
.trigger = spdif_trigger,
.hw_params = spdif_hw_params,
.shutdown = spdif_shutdown,
};
static struct snd_soc_dai_driver samsung_spdif_dai = {
.name = "samsung-spdif",
.playback = {
.stream_name = "S/PDIF Playback",
.channels_min = 2,
.channels_max = 2,
.rates = (SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000),
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
.ops = &spdif_dai_ops,
.suspend = spdif_suspend,
.resume = spdif_resume,
};
static const struct snd_soc_component_driver samsung_spdif_component = {
.name = "samsung-spdif",
};
static int spdif_probe(struct platform_device *pdev)
{
struct s3c_audio_pdata *spdif_pdata;
struct resource *mem_res, *dma_res;
struct samsung_spdif_info *spdif;
int ret;
spdif_pdata = pdev->dev.platform_data;
dev_dbg(&pdev->dev, "Entered %s\n", __func__);
dma_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dma_res) {
dev_err(&pdev->dev, "Unable to get dma resource.\n");
return -ENXIO;
}
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_res) {
dev_err(&pdev->dev, "Unable to get register resource.\n");
return -ENXIO;
}
if (spdif_pdata && spdif_pdata->cfg_gpio
&& spdif_pdata->cfg_gpio(pdev)) {
dev_err(&pdev->dev, "Unable to configure GPIO pins\n");
return -EINVAL;
}
spdif = &spdif_info;
spdif->dev = &pdev->dev;
spin_lock_init(&spdif->lock);
spdif->pclk = devm_clk_get(&pdev->dev, "spdif");
if (IS_ERR(spdif->pclk)) {
dev_err(&pdev->dev, "failed to get peri-clock\n");
ret = -ENOENT;
goto err0;
}
clk_prepare_enable(spdif->pclk);
spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif");
if (IS_ERR(spdif->sclk)) {
dev_err(&pdev->dev, "failed to get internal source clock\n");
ret = -ENOENT;
goto err1;
}
clk_prepare_enable(spdif->sclk);
/* Request S/PDIF Register's memory region */
if (!request_mem_region(mem_res->start,
resource_size(mem_res), "samsung-spdif")) {
dev_err(&pdev->dev, "Unable to request register region\n");
ret = -EBUSY;
goto err2;
}
spdif->regs = ioremap(mem_res->start, 0x100);
if (spdif->regs == NULL) {
dev_err(&pdev->dev, "Cannot ioremap registers\n");
ret = -ENXIO;
goto err3;
}
dev_set_drvdata(&pdev->dev, spdif);
ret = devm_snd_soc_register_component(&pdev->dev,
&samsung_spdif_component, &samsung_spdif_dai, 1);
if (ret != 0) {
dev_err(&pdev->dev, "fail to register dai\n");
goto err4;
}
spdif_stereo_out.dma_size = 2;
spdif_stereo_out.dma_addr = mem_res->start + DATA_OUTBUF;
spdif_stereo_out.channel = dma_res->start;
spdif->dma_playback = &spdif_stereo_out;
ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "failed to register DMA: %d\n", ret);
goto err4;
}
return 0;
err4:
iounmap(spdif->regs);
err3:
release_mem_region(mem_res->start, resource_size(mem_res));
err2:
clk_disable_unprepare(spdif->sclk);
err1:
clk_disable_unprepare(spdif->pclk);
err0:
return ret;
}
static int spdif_remove(struct platform_device *pdev)
{
struct samsung_spdif_info *spdif = &spdif_info;
struct resource *mem_res;
iounmap(spdif->regs);
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem_res)
release_mem_region(mem_res->start, resource_size(mem_res));
clk_disable_unprepare(spdif->sclk);
clk_disable_unprepare(spdif->pclk);
return 0;
}
static struct platform_driver samsung_spdif_driver = {
.probe = spdif_probe,
.remove = spdif_remove,
.driver = {
.name = "samsung-spdif",
.owner = THIS_MODULE,
},
};
module_platform_driver(samsung_spdif_driver);
MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
MODULE_DESCRIPTION("Samsung S/PDIF Controller Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-spdif");

19
sound/soc/samsung/spdif.h Normal file
View file

@ -0,0 +1,19 @@
/* sound/soc/samsung/spdif.h
*
* ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
*
* Copyright (c) 2010 Samsung Electronics Co. Ltd
* http://www.samsung.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_SAMSUNG_SPDIF_H
#define __SND_SOC_SAMSUNG_SPDIF_H __FILE__
#define SND_SOC_SPDIF_INT_MCLK 0
#define SND_SOC_SPDIF_EXT_MCLK 1
#endif /* __SND_SOC_SAMSUNG_SPDIF_H */

View file

@ -0,0 +1,354 @@
/*
* Speyside audio support
*
* Copyright 2011 Wolfson Microelectronics
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm8996.h"
#include "../codecs/wm9081.h"
#define WM8996_HPSEL_GPIO 214
#define MCLK_AUDIO_RATE (512 * 48000)
static int speyside_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[1].codec_dai;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_dai_set_sysclk(codec_dai, WM8996_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8996_FLL_MCLK2,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL\n");
return ret;
}
break;
default:
break;
}
return 0;
}
static int speyside_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[1].codec_dai;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
ret = snd_soc_dai_set_pll(codec_dai, 0,
WM8996_FLL_MCLK2,
32768, MCLK_AUDIO_RATE);
if (ret < 0) {
pr_err("Failed to start FLL\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai,
WM8996_SYSCLK_FLL,
MCLK_AUDIO_RATE,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
}
break;
default:
break;
}
card->dapm.bias_level = level;
return 0;
}
static struct snd_soc_jack speyside_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin speyside_headset_pins[] = {
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
};
/* Default the headphone selection to active high */
static int speyside_jack_polarity;
static int speyside_get_micbias(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink)
{
if (speyside_jack_polarity && (strcmp(source->name, "MICB1") == 0))
return 1;
if (!speyside_jack_polarity && (strcmp(source->name, "MICB2") == 0))
return 1;
return 0;
}
static void speyside_set_polarity(struct snd_soc_codec *codec,
int polarity)
{
speyside_jack_polarity = !polarity;
gpio_direction_output(WM8996_HPSEL_GPIO, speyside_jack_polarity);
/* Re-run DAPM to make sure we're using the correct mic bias */
snd_soc_dapm_sync(&codec->dapm);
}
static int speyside_wm0010_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *dai = rtd->codec_dai;
int ret;
ret = snd_soc_dai_set_sysclk(dai, 0, MCLK_AUDIO_RATE, 0);
if (ret < 0)
return ret;
return 0;
}
static int speyside_wm8996_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *dai = rtd->codec_dai;
struct snd_soc_codec *codec = rtd->codec;
int ret;
ret = snd_soc_dai_set_sysclk(dai, WM8996_SYSCLK_MCLK2, 32768, 0);
if (ret < 0)
return ret;
ret = gpio_request(WM8996_HPSEL_GPIO, "HP_SEL");
if (ret != 0)
pr_err("Failed to request HP_SEL GPIO: %d\n", ret);
gpio_direction_output(WM8996_HPSEL_GPIO, speyside_jack_polarity);
ret = snd_soc_jack_new(codec, "Headset",
SND_JACK_LINEOUT | SND_JACK_HEADSET |
SND_JACK_BTN_0,
&speyside_headset);
if (ret)
return ret;
ret = snd_soc_jack_add_pins(&speyside_headset,
ARRAY_SIZE(speyside_headset_pins),
speyside_headset_pins);
if (ret)
return ret;
wm8996_detect(codec, &speyside_headset, speyside_set_polarity);
return 0;
}
static int speyside_late_probe(struct snd_soc_card *card)
{
snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone");
snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic");
snd_soc_dapm_ignore_suspend(&card->dapm, "Main AMIC");
snd_soc_dapm_ignore_suspend(&card->dapm, "Main DMIC");
snd_soc_dapm_ignore_suspend(&card->dapm, "Main Speaker");
snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Output");
snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Input");
return 0;
}
static const struct snd_soc_pcm_stream dsp_codec_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link speyside_dai[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm0010-sdi1",
.platform_name = "samsung-i2s.0",
.codec_name = "spi0.0",
.init = speyside_wm0010_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.cpu_dai_name = "wm0010-sdi2",
.codec_dai_name = "wm8996-aif1",
.codec_name = "wm8996.1-001a",
.init = speyside_wm8996_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.params = &dsp_codec_params,
.ignore_suspend = 1,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm8996-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
},
};
static int speyside_wm9081_init(struct snd_soc_component *component)
{
struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
/* At any time the WM9081 is active it will have this clock */
return snd_soc_codec_set_sysclk(codec, WM9081_SYSCLK_MCLK, 0,
MCLK_AUDIO_RATE, 0);
}
static struct snd_soc_aux_dev speyside_aux_dev[] = {
{
.name = "wm9081",
.codec_name = "wm9081.1-006c",
.init = speyside_wm9081_init,
},
};
static struct snd_soc_codec_conf speyside_codec_conf[] = {
{
.dev_name = "wm9081.1-006c",
.name_prefix = "Sub",
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("Main DMIC"),
SOC_DAPM_PIN_SWITCH("Main AMIC"),
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
SOC_DAPM_PIN_SWITCH("Headphone"),
};
static struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
SND_SOC_DAPM_MIC("Main AMIC", NULL),
SND_SOC_DAPM_MIC("Main DMIC", NULL),
};
static struct snd_soc_dapm_route audio_paths[] = {
{ "IN1RN", NULL, "MICB1" },
{ "IN1RP", NULL, "MICB1" },
{ "IN1RN", NULL, "MICB2" },
{ "IN1RP", NULL, "MICB2" },
{ "MICB1", NULL, "Headset Mic", speyside_get_micbias },
{ "MICB2", NULL, "Headset Mic", speyside_get_micbias },
{ "IN1LP", NULL, "MICB2" },
{ "IN1RN", NULL, "MICB1" },
{ "MICB2", NULL, "Main AMIC" },
{ "DMIC1DAT", NULL, "MICB1" },
{ "DMIC2DAT", NULL, "MICB1" },
{ "MICB1", NULL, "Main DMIC" },
{ "Headphone", NULL, "HPOUT1L" },
{ "Headphone", NULL, "HPOUT1R" },
{ "Sub IN1", NULL, "HPOUT2L" },
{ "Sub IN2", NULL, "HPOUT2R" },
{ "Main Speaker", NULL, "Sub SPKN" },
{ "Main Speaker", NULL, "Sub SPKP" },
{ "Main Speaker", NULL, "SPKDAT" },
};
static struct snd_soc_card speyside = {
.name = "Speyside",
.owner = THIS_MODULE,
.dai_link = speyside_dai,
.num_links = ARRAY_SIZE(speyside_dai),
.aux_dev = speyside_aux_dev,
.num_aux_devs = ARRAY_SIZE(speyside_aux_dev),
.codec_conf = speyside_codec_conf,
.num_configs = ARRAY_SIZE(speyside_codec_conf),
.set_bias_level = speyside_set_bias_level,
.set_bias_level_post = speyside_set_bias_level_post,
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.fully_routed = true,
.late_probe = speyside_late_probe,
};
static int speyside_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &speyside;
int ret;
card->dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
static struct platform_driver speyside_driver = {
.driver = {
.name = "speyside",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = speyside_probe,
};
module_platform_driver(speyside_driver);
MODULE_DESCRIPTION("Speyside audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:speyside");

View file

@ -0,0 +1,248 @@
/*
* Tobermory audio support
*
* Copyright 2011 Wolfson Microelectronics
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm8962.h"
static int sample_rate = 44100;
static int tobermory_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
WM8962_FLL_MCLK, 32768,
sample_rate * 512);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
ret = snd_soc_dai_set_sysclk(codec_dai,
WM8962_SYSCLK_FLL,
sample_rate * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
0, 0, 0);
return ret;
}
}
break;
default:
break;
}
return 0;
}
static int tobermory_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
32768, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to switch away from FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
break;
default:
break;
}
dapm->bias_level = level;
return 0;
}
static int tobermory_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
sample_rate = params_rate(params);
return 0;
}
static struct snd_soc_ops tobermory_ops = {
.hw_params = tobermory_hw_params,
};
static struct snd_soc_dai_link tobermory_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8962",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8962.1-001a",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ops = &tobermory_ops,
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("DMIC"),
};
static struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("DMIC", NULL),
SND_SOC_DAPM_MIC("AMIC", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
};
static struct snd_soc_dapm_route audio_paths[] = {
{ "Headphone", NULL, "HPOUTL" },
{ "Headphone", NULL, "HPOUTR" },
{ "Main Speaker", NULL, "SPKOUTL" },
{ "Main Speaker", NULL, "SPKOUTR" },
{ "Headset Mic", NULL, "MICBIAS" },
{ "IN4L", NULL, "Headset Mic" },
{ "IN4R", NULL, "Headset Mic" },
{ "AMIC", NULL, "MICBIAS" },
{ "IN1L", NULL, "AMIC" },
{ "IN1R", NULL, "AMIC" },
{ "DMIC", NULL, "MICBIAS" },
{ "DMICDAT", NULL, "DMIC" },
};
static struct snd_soc_jack tobermory_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin tobermory_headset_pins[] = {
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
{
.pin = "Headphone",
.mask = SND_JACK_MICROPHONE,
},
};
static int tobermory_late_probe(struct snd_soc_card *card)
{
struct snd_soc_codec *codec = card->rtd[0].codec;
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
int ret;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_jack_new(codec, "Headset",
SND_JACK_HEADSET | SND_JACK_BTN_0,
&tobermory_headset);
if (ret)
return ret;
ret = snd_soc_jack_add_pins(&tobermory_headset,
ARRAY_SIZE(tobermory_headset_pins),
tobermory_headset_pins);
if (ret)
return ret;
wm8962_mic_detect(codec, &tobermory_headset);
return 0;
}
static struct snd_soc_card tobermory = {
.name = "Tobermory",
.owner = THIS_MODULE,
.dai_link = tobermory_dai,
.num_links = ARRAY_SIZE(tobermory_dai),
.set_bias_level = tobermory_set_bias_level,
.set_bias_level_post = tobermory_set_bias_level_post,
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.fully_routed = true,
.late_probe = tobermory_late_probe,
};
static int tobermory_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &tobermory;
int ret;
card->dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
static struct platform_driver tobermory_driver = {
.driver = {
.name = "tobermory",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = tobermory_probe,
};
module_platform_driver(tobermory_driver);
MODULE_DESCRIPTION("Tobermory audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:tobermory");

View file

@ -0,0 +1,678 @@
/*
* universal7270-Unknown Audio Machine driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <sound/tlv.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/exynos-audmixer.h>
#include "i2s.h"
#include "i2s-regs.h"
#include "../codecs/largo.h"
#define CODEC_BFS_48KHZ 32
#define CODEC_RFS_48KHZ 512
#define CODEC_SAMPLE_RATE_48KHZ 48000
#define CODEC_BFS_192KHZ 64
#define CODEC_RFS_192KHZ 128
#define CODEC_SAMPLE_RATE_192KHZ 192000
#define MCLK_RATE 24000000
#define SYSCLK_RATE 147456000
#ifdef CONFIG_SND_SOC_SAMSUNG_VERBOSE_DEBUG
#ifdef dev_dbg
#undef dev_dbg
#endif
#define dev_dbg dev_err
#endif
static struct snd_soc_card universal7270_largo_card;
static const struct snd_soc_component_driver universal7270_cmpnt = {
.name = "universal7270-audio",
};
static int universal7270_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *amixer_dai = rtd->codec_dais[0];
struct snd_soc_dai *codec_dai = rtd->codec_dais[1];
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int ret;
int rfs, bfs;
dev_info(card->dev, "aif1: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
if (params_rate(params) == CODEC_SAMPLE_RATE_192KHZ) {
rfs = CODEC_RFS_192KHZ;
bfs = CODEC_BFS_192KHZ;
} else {
rfs = CODEC_RFS_48KHZ;
bfs = CODEC_BFS_48KHZ;
}
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set Codec DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set CPU DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
rfs, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_CDCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_OPCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0) {
dev_err(card->dev,
"aif1: Failed to set SAMSUNG_I2S_RCLKSRC_1\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set BFS\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_RCLK, rfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set RFS\n");
return ret;
}
ret = snd_soc_dai_set_bclk_ratio(amixer_dai, bfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int universal7270_aif2_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *amixer_dai = rtd->codec_dais[0];
int bfs, ret;
dev_info(card->dev, "aif2: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif2: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = snd_soc_dai_set_bclk_ratio(amixer_dai, bfs);
if (ret < 0) {
dev_err(card->dev, "aif2: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int universal7270_aif1_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void universal7270_aif1_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int universal7270_aif2_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void universal7270_aif2_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int universal7270_aif3_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void universal7270_aif3_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int universal7270_aif4_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
return 0;
}
void universal7270_aif4_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
}
static int universal7270_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dais[1];
struct snd_soc_codec *codec = codec_dai->codec;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
break;
pr_info("pll in audio codec is enabled\n");
ret = snd_soc_codec_set_pll(codec, LARGO_FLL1,
ARIZONA_FLL_SRC_MCLK2,
32768,
SYSCLK_RATE);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
break;
default:
break;
}
return 0;
}
static int universal7270_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dais[1];
struct snd_soc_codec *codec = codec_dai->codec;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
pr_info("pll in audio codec is disabled\n");
ret = snd_soc_codec_set_pll(codec, LARGO_FLL1, 0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
break;
default:
break;
}
dapm->bias_level = level;
return 0;
}
static void universal7270_ez2c_trigger(void)
{
pr_warn("Ez2Control Triggered\n");
}
static int universal7270_late_probe(struct snd_soc_card *card)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dais[1];
struct snd_soc_codec *codec = codec_dai->codec;
int ret;
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_SYSCLK,
ARIZONA_CLK_SRC_FLL1,
SYSCLK_RATE,
SND_SOC_CLOCK_IN);
if (ret != 0) {
dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai, ARIZONA_CLK_SYSCLK, 0, 0);
if (ret != 0) {
dev_err(codec_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_pll(codec, LARGO_FLL1_REFCLK,
ARIZONA_FLL_SRC_NONE, 0, 0);
if (ret < 0) {
pr_err("Failed to clear FLL refclk: %d\n", ret);
return ret;
}
arizona_set_ez2ctrl_cb(codec, universal7270_ez2c_trigger);
return 0;
}
static int audmixer_init(struct snd_soc_component *cmp)
{
dev_dbg(cmp->dev, "%s called\n", __func__);
return 0;
}
static const struct snd_kcontrol_new universal7270_controls[] = {
};
const struct snd_soc_dapm_widget universal7270_dapm_widgets[] = {
SND_SOC_DAPM_MIC("DMIC1", NULL),
};
const struct snd_soc_dapm_route universal7270_dapm_routes[] = {
{ "DMIC1", NULL, "MICBIAS1" },
{ "IN1L", NULL, "DMIC1" },
{ "IN1R", NULL, "DMIC1" },
};
static struct snd_soc_ops universal7270_aif1_ops = {
.hw_params = universal7270_aif1_hw_params,
.startup = universal7270_aif1_startup,
.shutdown = universal7270_aif1_shutdown,
};
static struct snd_soc_ops universal7270_aif2_ops = {
.hw_params = universal7270_aif2_hw_params,
.startup = universal7270_aif2_startup,
.shutdown = universal7270_aif2_shutdown,
};
static struct snd_soc_ops universal7270_aif3_ops = {
.startup = universal7270_aif3_startup,
.shutdown = universal7270_aif3_shutdown,
};
static struct snd_soc_ops universal7270_aif4_ops = {
.startup = universal7270_aif4_startup,
.shutdown = universal7270_aif4_shutdown,
};
static struct snd_soc_dai_driver universal7270_ext_dai[] = {
{
.name = "universal7270 voice call",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "universal7270 BT",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "universal7270 FM",
.playback = {
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = SNDRV_PCM_FMTBIT_S16_LE
},
},
};
static struct snd_soc_dai_link_component codecs_ap0[] = {{
.name = "14880000.s1403x",
.dai_name = "AP0",
}, {
.dai_name = "largo-aif1",
},
};
static struct snd_soc_dai_link_component codecs_cp0[] = {{
.name = "14880000.s1403x",
.dai_name = "CP0",
}, {
.dai_name = "largo-aif1",
},
};
static struct snd_soc_dai_link_component codecs_bt[] = {{
.name = "14880000.s1403x",
.dai_name = "BT",
}, {
.dai_name = "dummy-aif2",
},
};
static struct snd_soc_dai_link_component codecs_fm[] = {{
.name = "14880000.s1403x",
.dai_name = "FM",
}, {
.dai_name = "largo-aif2",
},
};
static struct snd_soc_dai_link universal7270_largo_dai[] = {
/* Playback and Recording */
{
.name = "universal7270-largo",
.stream_name = "i2s0-pri",
.codecs = codecs_ap0,
.num_codecs = ARRAY_SIZE(codecs_ap0),
.ops = &universal7270_aif1_ops,
},
/* Deep buffer playback */
{
.name = "universal7270-largo-sec",
.cpu_dai_name = "samsung-i2s-sec",
.stream_name = "i2s0-sec",
.platform_name = "samsung-i2s-sec",
.codecs = codecs_ap0,
.num_codecs = ARRAY_SIZE(codecs_ap0),
.ops = &universal7270_aif1_ops,
},
/* Voice Call */
{
.name = "cp",
.stream_name = "voice call",
.cpu_dai_name = "universal7270 voice call",
.platform_name = "snd-soc-dummy",
.codecs = codecs_cp0,
.num_codecs = ARRAY_SIZE(codecs_cp0),
.ops = &universal7270_aif2_ops,
.ignore_suspend = 1,
},
/* BT */
{
.name = "bt",
.stream_name = "bluetooth audio",
.cpu_dai_name = "universal7270 BT",
.platform_name = "snd-soc-dummy",
.codecs = codecs_bt,
.num_codecs = ARRAY_SIZE(codecs_bt),
.ops = &universal7270_aif3_ops,
.ignore_suspend = 1,
},
/* FM */
{
.name = "fm",
.stream_name = "FM audio",
.cpu_dai_name = "universal7270 FM",
.platform_name = "snd-soc-dummy",
.codecs = codecs_fm,
.num_codecs = ARRAY_SIZE(codecs_fm),
.ops = &universal7270_aif4_ops,
.ignore_suspend = 1,
},
};
static struct snd_soc_aux_dev audmixer_aux_dev[] = {
{
.init = audmixer_init,
},
};
static struct snd_soc_codec_conf audmixer_codec_conf[] = {
{
.name_prefix = "AudioMixer",
},
};
static struct snd_soc_card universal7270_largo_card = {
.name = "universal7270-I2S",
.owner = THIS_MODULE,
.dai_link = universal7270_largo_dai,
.num_links = ARRAY_SIZE(universal7270_largo_dai),
.controls = universal7270_controls,
.num_controls = ARRAY_SIZE(universal7270_controls),
.dapm_widgets = universal7270_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(universal7270_dapm_widgets),
.dapm_routes = universal7270_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(universal7270_dapm_routes),
.late_probe = universal7270_late_probe,
.set_bias_level = universal7270_set_bias_level,
.set_bias_level_post = universal7270_set_bias_level_post,
.aux_dev = audmixer_aux_dev,
.num_aux_devs = ARRAY_SIZE(audmixer_aux_dev),
.codec_conf = audmixer_codec_conf,
.num_configs = ARRAY_SIZE(audmixer_codec_conf),
};
static int universal7270_audio_probe(struct platform_device *pdev)
{
int n, ret;
struct device_node *np = pdev->dev.of_node;
struct device_node *cpu_np, *codec_np, *auxdev_np;
struct snd_soc_card *card = &universal7270_largo_card;
if (!np) {
dev_err(&pdev->dev, "Failed to get device node\n");
return -EINVAL;
}
card->dev = &pdev->dev;
card->num_links = 0;
ret = snd_soc_register_component(card->dev, &universal7270_cmpnt,
universal7270_ext_dai,
ARRAY_SIZE(universal7270_ext_dai));
if (ret) {
dev_err(&pdev->dev, "Failed to register component: %d\n", ret);
return ret;
}
for (n = 0; n < ARRAY_SIZE(universal7270_largo_dai); n++) {
/* Skip parsing DT for fully formed dai links */
if (universal7270_largo_dai[n].platform_name &&
universal7270_largo_dai[n].codec_name) {
dev_dbg(card->dev,
"Skipping dt for populated dai link %s\n",
universal7270_largo_dai[n].name);
card->num_links++;
continue;
}
cpu_np = of_parse_phandle(np, "samsung,audio-cpu", n);
if (!cpu_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-cpu' missing\n");
break;
}
codec_np = of_parse_phandle(np, "samsung,audio-codec", n);
if (!codec_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec' missing\n");
break;
}
universal7270_largo_dai[n].codecs[1].of_node = codec_np;
if (!universal7270_largo_dai[n].cpu_dai_name)
universal7270_largo_dai[n].cpu_of_node = cpu_np;
if (!universal7270_largo_dai[n].platform_name)
universal7270_largo_dai[n].platform_of_node = cpu_np;
card->num_links++;
}
for (n = 0; n < ARRAY_SIZE(audmixer_aux_dev); n++) {
auxdev_np = of_parse_phandle(np, "samsung,auxdev", n);
if (!auxdev_np) {
dev_err(&pdev->dev,
"Property 'samsung,auxdev' missing\n");
return -EINVAL;
}
audmixer_aux_dev[n].codec_of_node = auxdev_np;
audmixer_codec_conf[n].of_node = auxdev_np;
}
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev, "Failed to register card:%d\n", ret);
return ret;
}
static int universal7270_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static const struct of_device_id universal7270_largo_of_match[] = {
{.compatible = "samsung,universal7270-wm1831",},
};
MODULE_DEVICE_TABLE(of, universal7270_largo_of_match);
static struct platform_driver universal7270_audio_driver = {
.driver = {
.name = "universal7270-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(universal7270_largo_of_match),
},
.probe = universal7270_audio_probe,
.remove = universal7270_audio_remove,
};
module_platform_driver(universal7270_audio_driver);
MODULE_DESCRIPTION("ALSA SoC universal7270 largo");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:universal7270-audio");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,856 @@
/*
* Universal7870-COD3025X Audio Machine driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <sound/tlv.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/exynos-audmixer.h>
#include "i2s.h"
#include "i2s-regs.h"
#define CODEC_BFS_48KHZ 32
#define CODEC_RFS_48KHZ 512
#define CODEC_SAMPLE_RATE_48KHZ 48000
#define CODEC_BFS_192KHZ 64
#define CODEC_RFS_192KHZ 128
#define CODEC_SAMPLE_RATE_192KHZ 192000
#ifdef CONFIG_SND_SOC_SAMSUNG_VERBOSE_DEBUG
#define RX_SRAM_SIZE (0x2000) /* 8 KB */
#ifdef dev_dbg
#undef dev_dbg
#endif
#define dev_dbg dev_err
#endif
static struct snd_soc_card universal7870_cod3025x_card;
extern void set_ip_idle(bool value);
struct cod3025x_machine_priv {
struct snd_soc_codec *codec;
int aifrate;
bool use_external_jd;
};
static const struct snd_soc_component_driver universal7870_cmpnt = {
.name = "Universal7870-audio",
};
static int universal7870_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct cod3025x_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
int rfs, bfs;
dev_info(card->dev, "aif1: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
priv->aifrate = params_rate(params);
if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) &&
params_buffer_bytes(params) <= RX_SRAM_SIZE)
set_ip_idle(true);
else
set_ip_idle(false);
if (priv->aifrate == CODEC_SAMPLE_RATE_192KHZ) {
rfs = CODEC_RFS_192KHZ;
bfs = CODEC_BFS_192KHZ;
} else {
rfs = CODEC_RFS_48KHZ;
bfs = CODEC_BFS_48KHZ;
}
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set Codec DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set CPU DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
rfs, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_CDCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set SAMSUNG_I2S_OPCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0) {
dev_err(card->dev,
"aif1: Failed to set SAMSUNG_I2S_RCLKSRC_1\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set BFS\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_RCLK, rfs);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to set RFS\n");
return ret;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_AP);
if (ret < 0) {
dev_err(card->dev, "aif1: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int universal7870_aif2_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
int bfs, ret;
dev_info(card->dev, "aif2: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif2: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_CP);
if (ret < 0) {
dev_err(card->dev, "aif2: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int universal7870_aif3_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
int bfs, ret;
dev_info(card->dev, "aif3: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif3: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_BT);
if (ret < 0) {
dev_err(card->dev, "aif3: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int universal7870_aif5_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct cod3025x_machine_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
int rfs, bfs;
dev_info(card->dev, "aif5: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
priv->aifrate = params_rate(params);
if (priv->aifrate == CODEC_SAMPLE_RATE_192KHZ) {
rfs = CODEC_RFS_192KHZ;
bfs = CODEC_BFS_192KHZ;
} else {
rfs = CODEC_RFS_48KHZ;
bfs = CODEC_BFS_48KHZ;
}
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set CPU DAIFMT\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
rfs, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set SAMSUNG_I2S_CDCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set SAMSUNG_I2S_OPCLK\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0) {
dev_err(card->dev,
"aif5: Failed to set SAMSUNG_I2S_RCLKSRC_1\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set BFS\n");
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai,
SAMSUNG_I2S_DIV_RCLK, rfs);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to set RFS\n");
return ret;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_AP1);
if (ret < 0) {
dev_err(card->dev, "aif5: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int universal7870_aif6_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
int bfs, ret;
dev_info(card->dev, "aif6: %dch, %dHz, %dbytes\n",
params_channels(params), params_rate(params),
params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
dev_err(card->dev, "aif6: Unsupported PCM_FORMAT\n");
return -EINVAL;
}
ret = audmixer_hw_params(substream, params, bfs, AUDMIXER_IF_CP1);
if (ret < 0) {
dev_err(card->dev, "aif6: Failed to configure mixer\n");
return ret;
}
return 0;
}
static int universal7870_aif1_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_AP);
return 0;
}
void universal7870_aif1_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif1: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_AP);
}
static int universal7870_aif2_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_CP);
return 0;
}
void universal7870_aif2_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif2: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_CP);
}
static int universal7870_aif3_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_BT);
return 0;
}
void universal7870_aif3_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif3: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_BT);
}
static int universal7870_aif4_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_BT);
return 0;
}
void universal7870_aif4_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif4: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_BT);
}
static int universal7870_aif5_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif5: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_AP1);
return 0;
}
void universal7870_aif5_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif5: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_AP1);
}
static int universal7870_aif6_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif6: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_startup(AUDMIXER_IF_CP1);
return 0;
}
void universal7870_aif6_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "aif6: (%s) %s called\n",
substream->stream ? "C" : "P", __func__);
audmixer_shutdown(AUDMIXER_IF_CP1);
}
static int universal7870_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
return 0;
}
static int universal7870_late_probe(struct snd_soc_card *card)
{
dev_dbg(card->dev, "%s called\n", __func__);
return 0;
}
static int audmixer_init(struct snd_soc_component *cmp)
{
dev_dbg(cmp->dev, "%s called\n", __func__);
return 0;
}
static const struct snd_kcontrol_new universal7870_controls[] = {
};
const struct snd_soc_dapm_widget universal7870_dapm_widgets[] = {
};
const struct snd_soc_dapm_route universal7870_dapm_routes[] = {
};
static struct snd_soc_ops universal7870_aif1_ops = {
.hw_params = universal7870_aif1_hw_params,
.startup = universal7870_aif1_startup,
.shutdown = universal7870_aif1_shutdown,
};
static struct snd_soc_ops universal7870_aif2_ops = {
.hw_params = universal7870_aif2_hw_params,
.startup = universal7870_aif2_startup,
.shutdown = universal7870_aif2_shutdown,
};
static struct snd_soc_ops universal7870_aif3_ops = {
.hw_params = universal7870_aif3_hw_params,
.startup = universal7870_aif3_startup,
.shutdown = universal7870_aif3_shutdown,
};
static struct snd_soc_ops universal7870_aif4_ops = {
.startup = universal7870_aif4_startup,
.shutdown = universal7870_aif4_shutdown,
};
static struct snd_soc_ops universal7870_aif5_ops = {
.hw_params = universal7870_aif5_hw_params,
.startup = universal7870_aif5_startup,
.shutdown = universal7870_aif5_shutdown,
};
static struct snd_soc_ops universal7870_aif6_ops = {
.hw_params = universal7870_aif6_hw_params,
.startup = universal7870_aif6_startup,
.shutdown = universal7870_aif6_shutdown,
};
static struct snd_soc_dai_driver universal7870_ext_dai[] = {
{
.name = "universal7870 voice call",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "universal7870 BT",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
{
.name = "universal7870 FM",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE)
},
},
};
static struct snd_soc_dai_link universal7870_cod3025x_dai[] = {
/* Playback and Recording */
{
.name = "universal7870-cod3025x",
.stream_name = "i2s0-pri",
.codec_dai_name = "cod3026x-aif",
.ops = &universal7870_aif1_ops,
},
/* Deep buffer playback */
{
.name = "universal7870-cod3025x-sec",
.cpu_dai_name = "samsung-i2s-sec",
.stream_name = "i2s0-sec",
.platform_name = "samsung-i2s-sec",
.codec_dai_name = "cod3026x-aif",
.ops = &universal7870_aif1_ops,
},
/* Voice Call */
{
.name = "cp",
.stream_name = "voice call",
.cpu_dai_name = "universal7870 voice call",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "cod3026x-aif2",
.ops = &universal7870_aif2_ops,
.ignore_suspend = 1,
},
/* BT */
{
.name = "bt",
.stream_name = "bluetooth audio",
.cpu_dai_name = "universal7870 BT",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "dummy-aif2",
.ops = &universal7870_aif3_ops,
.ignore_suspend = 1,
},
/* FM */
{
.name = "fm",
.stream_name = "FM audio",
.cpu_dai_name = "universal7870 FM",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "cod3026x-aif",
.ops = &universal7870_aif4_ops,
.ignore_suspend = 1,
},
/* AMP AP Interface */
{
.name = "universal7870-cod3026x-amp",
.stream_name = "i2s1-pri",
.codec_dai_name = "dummy-aif2",
.ops = &universal7870_aif5_ops,
},
/* AMP CP Interface */
{
.name = "cp-amp",
.stream_name = "voice call amp",
.cpu_dai_name = "universal7870 voice call",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "dummy-aif2",
.ops = &universal7870_aif6_ops,
.ignore_suspend = 1,
},
/* SW MIXER1 Interface */
{
.name = "playback-eax0",
.stream_name = "eax0",
.cpu_dai_name = "samsung-eax.0",
.platform_name = "samsung-eax.0",
.codec_dai_name = "cod3026x-aif",
.ops = &universal7870_aif1_ops,
.ignore_suspend = 1,
},
/* SW MIXER2 Interface */
{
.name = "playback-eax1",
.stream_name = "eax1",
.cpu_dai_name = "samsung-eax.1",
.platform_name = "samsung-eax.1",
.codec_dai_name = "cod3026x-aif",
.ops = &universal7870_aif1_ops,
.ignore_suspend = 1,
},
/* SW MIXER3 Interface */
{
.name = "playback-eax2",
.stream_name = "eax2",
.cpu_dai_name = "samsung-eax.2",
.platform_name = "samsung-eax.2",
.codec_dai_name = "cod3026x-aif",
.ops = &universal7870_aif1_ops,
.ignore_suspend = 1,
},
/* SW MIXER4 Interface */
{
.name = "playback-eax3",
.stream_name = "eax3",
.cpu_dai_name = "samsung-eax.3",
.platform_name = "samsung-eax.3",
.codec_dai_name = "cod3026x-aif",
.ops = &universal7870_aif1_ops,
.ignore_suspend = 1,
},
};
static struct snd_soc_aux_dev audmixer_aux_dev[] = {
{
.init = audmixer_init,
},
};
static struct snd_soc_codec_conf audmixer_codec_conf[] = {
{
.name_prefix = "AudioMixer",
},
};
static struct snd_soc_card universal7870_cod3025x_card = {
.name = "Universal7870-I2S",
.owner = THIS_MODULE,
.dai_link = universal7870_cod3025x_dai,
.num_links = ARRAY_SIZE(universal7870_cod3025x_dai),
.controls = universal7870_controls,
.num_controls = ARRAY_SIZE(universal7870_controls),
.dapm_widgets = universal7870_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(universal7870_dapm_widgets),
.dapm_routes = universal7870_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(universal7870_dapm_routes),
.late_probe = universal7870_late_probe,
.set_bias_level = universal7870_set_bias_level,
.aux_dev = audmixer_aux_dev,
.num_aux_devs = ARRAY_SIZE(audmixer_aux_dev),
.codec_conf = audmixer_codec_conf,
.num_configs = ARRAY_SIZE(audmixer_codec_conf),
};
static int universal7870_audio_probe(struct platform_device *pdev)
{
int n, ret;
struct device_node *np = pdev->dev.of_node;
struct device_node *cpu_np, *codec_np, *auxdev_np;
struct snd_soc_card *card = &universal7870_cod3025x_card;
struct cod3025x_machine_priv *priv;
if (!np) {
dev_err(&pdev->dev, "Failed to get device node\n");
return -EINVAL;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card->dev = &pdev->dev;
card->num_links = 0;
ret = snd_soc_register_component(card->dev, &universal7870_cmpnt,
universal7870_ext_dai,
ARRAY_SIZE(universal7870_ext_dai));
if (ret) {
dev_err(&pdev->dev, "Failed to register component: %d\n", ret);
return ret;
}
for (n = 0; n < ARRAY_SIZE(universal7870_cod3025x_dai); n++) {
/* Skip parsing DT for fully formed dai links */
if (universal7870_cod3025x_dai[n].platform_name &&
universal7870_cod3025x_dai[n].codec_name) {
dev_dbg(card->dev,
"Skipping dt for populated dai link %s\n",
universal7870_cod3025x_dai[n].name);
card->num_links++;
continue;
}
cpu_np = of_parse_phandle(np, "samsung,audio-cpu", n);
if (!cpu_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-cpu' missing\n");
break;
}
codec_np = of_parse_phandle(np, "samsung,audio-codec", n);
if (!codec_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec' missing\n");
break;
}
universal7870_cod3025x_dai[n].codec_of_node = codec_np;
if (!universal7870_cod3025x_dai[n].cpu_dai_name)
universal7870_cod3025x_dai[n].cpu_of_node = cpu_np;
if (!universal7870_cod3025x_dai[n].platform_name)
universal7870_cod3025x_dai[n].platform_of_node = cpu_np;
card->num_links++;
}
for (n = 0; n < ARRAY_SIZE(audmixer_aux_dev); n++) {
auxdev_np = of_parse_phandle(np, "samsung,auxdev", n);
if (!auxdev_np) {
dev_err(&pdev->dev,
"Property 'samsung,auxdev' missing\n");
return -EINVAL;
}
audmixer_aux_dev[n].codec_of_node = auxdev_np;
audmixer_codec_conf[n].of_node = auxdev_np;
}
snd_soc_card_set_drvdata(card, priv);
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev, "Failed to register card:%d\n", ret);
return ret;
}
static int universal7870_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static const struct of_device_id universal7870_cod3026x_of_match[] = {
{.compatible = "samsung,universal7870-cod3026x",},
{},
};
MODULE_DEVICE_TABLE(of, universal7870_cod3026x_of_match);
static struct platform_driver universal7870_audio_driver = {
.driver = {
.name = "Universal7870-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(universal7870_cod3026x_of_match),
},
.probe = universal7870_audio_probe,
.remove = universal7870_audio_remove,
};
module_platform_driver(universal7870_audio_driver);
MODULE_DESCRIPTION("ALSA SoC Universal7870 COD3025X");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:universal7870-audio");

View file

@ -0,0 +1,312 @@
/*
* espresso7420_dummy.c
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
/*#include <mach/regs-pmu.h>*/
#include "i2s.h"
#include "i2s-regs.h"
/* ESPRESSO use CLKOUT from AP */
#define ESPRESSO_MCLK_FREQ 24000000
#define ESPRESSO_AUD_PLL_FREQ 491520000
static bool clkout_enabled;
static struct snd_soc_card espresso;
static void espresso_enable_mclk(bool on)
{
pr_debug("%s: %s\n", __func__, on ? "on" : "off");
clkout_enabled = on;
/*
writel(on ? 0x1000 : 0x1001, EXYNOS_PMU_PMU_DEBUG);
*/
}
#if 0 /* later */
static int set_aud_pll_rate(unsigned long rate)
{
struct clk *fout_aud_pll;
fout_aud_pll = clk_get(espresso.dev, "aud_pll");
if (IS_ERR(fout_aud_pll)) {
printk(KERN_ERR "%s: failed to get fout_aud_pll\n", __func__);
return PTR_ERR(fout_aud_pll);
}
if (rate == clk_get_rate(fout_aud_pll))
goto out;
rate += 20; /* margin */
clk_set_rate(fout_aud_pll, rate);
pr_debug("%s: aud_pll rate = %ld\n",
__func__, clk_get_rate(fout_aud_pll));
out:
clk_put(fout_aud_pll);
return 0;
}
#endif
/*
* ESPRESSO I2S DAI operations. (AP master)
*/
static int espresso_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int pll, div, sclk, bfs, psr, rfs, ret;
unsigned long rclk;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
return -EINVAL;
}
switch (params_rate(params)) {
case 16000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
case 88200:
case 96000:
if (bfs == 48)
rfs = 384;
else
rfs = 256;
break;
case 64000:
rfs = 384;
break;
case 8000:
case 11025:
case 12000:
if (bfs == 48)
rfs = 768;
else
rfs = 512;
break;
default:
return -EINVAL;
}
rclk = params_rate(params) * rfs;
switch (rclk) {
case 4096000:
case 5644800:
case 6144000:
case 8467200:
case 9216000:
psr = 8;
break;
case 8192000:
case 11289600:
case 12288000:
case 16934400:
case 18432000:
psr = 4;
break;
case 22579200:
case 24576000:
case 33868800:
case 36864000:
psr = 2;
break;
case 67737600:
case 73728000:
psr = 1;
break;
default:
printk("Not yet supported!\n");
return -EINVAL;
}
/* Set AUD_PLL frequency */
sclk = rclk * psr;
for (div = 2; div <= 16; div++) {
if (sclk * div > ESPRESSO_AUD_PLL_FREQ)
break;
}
pll = sclk * (div - 1);
// later set_aud_pll_rate(pll);
/* CLKOUT(XXTI) for Codec MCLK */
espresso_enable_mclk(true);
/* Set CPU DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
0, SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops espresso_ops = {
.hw_params = espresso_hw_params,
};
static struct snd_soc_dai_link espresso_dai[] = {
{ /* Primary DAI i/f */
.name = "DUMMY PRI",
.stream_name = "i2s0-pri",
.codec_dai_name = "dummy-aif3",
.ops = &espresso_ops,
}, { /* Secondary DAI i/f */
.name = "DUMMY SEC",
.stream_name = "i2s0-sec",
.cpu_dai_name = "samsung-i2s-sec",
.platform_name = "samsung-i2s-sec",
.codec_dai_name = "dummy-aif3",
.ops = &espresso_ops,
}
};
static int espresso_suspend_post(struct snd_soc_card *card)
{
espresso_enable_mclk(false);
return 0;
}
static int espresso_resume_pre(struct snd_soc_card *card)
{
espresso_enable_mclk(true);
return 0;
}
static struct snd_soc_card espresso = {
.name = "ESPRESSO-I2S",
.owner = THIS_MODULE,
.suspend_post = espresso_suspend_post,
.resume_pre = espresso_resume_pre,
.dai_link = espresso_dai,
.num_links = ARRAY_SIZE(espresso_dai),
};
static int espresso_audio_probe(struct platform_device *pdev)
{
int n, ret;
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &espresso;
bool hdmi_avail = true;
card->dev = &pdev->dev;
for (n = 0; np && n < ARRAY_SIZE(espresso_dai); n++) {
if (!espresso_dai[n].cpu_dai_name) {
espresso_dai[n].cpu_of_node = of_parse_phandle(np,
"samsung,audio-cpu", n);
if (!espresso_dai[n].cpu_of_node && hdmi_avail) {
espresso_dai[n].cpu_of_node = of_parse_phandle(np,
"samsung,audio-cpu-hdmi", 0);
hdmi_avail = false;
}
if (!espresso_dai[n].cpu_of_node) {
dev_err(&pdev->dev, "Property "
"'samsung,audio-cpu' missing or invalid\n");
ret = -EINVAL;
}
}
if (!espresso_dai[n].platform_name)
espresso_dai[n].platform_of_node = espresso_dai[n].cpu_of_node;
espresso_dai[n].codec_name = NULL;
espresso_dai[n].codec_of_node = of_parse_phandle(np,
"samsung,audio-codec", n);
if (!espresso_dai[0].codec_of_node) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec' missing or invalid\n");
ret = -EINVAL;
}
}
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret);
return ret;
}
static int espresso_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id samsung_dummy_of_match[] = {
{ .compatible = "samsung,universal-dummy", },
{},
};
MODULE_DEVICE_TABLE(of, samsung_dummy_of_match);
#endif /* CONFIG_OF */
static struct platform_driver espresso_audio_driver = {
.driver = {
.name = "espresso-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(samsung_dummy_of_match),
},
.probe = espresso_audio_probe,
.remove = espresso_audio_remove,
};
module_platform_driver(espresso_audio_driver);
MODULE_DESCRIPTION("ALSA SoC ESPRESSO7420 DUMMY");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:espresso-audio");

View file

@ -0,0 +1,411 @@
/*
* ALSA SoC dummy cpu & platform dai driver
*
* This driver provides one dummy dai.
*
* Copyright (c) 2014 Samsung Electronics
* http://www.samsungsemi.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/dma/dma-pl330.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/pcm_params.h>
#define PERIOD_MIN 4
static DECLARE_WAIT_QUEUE_HEAD(compr_cap_wq);
extern ssize_t esa_copy(unsigned long hwbuf, ssize_t size);
extern int esa_compr_running(void);
extern void esa_compr_ctrl_fxintr(bool fxon);
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 1,
.channels_max = 8,
.buffer_bytes_max = 256*1024,
.period_bytes_min = 128,
.period_bytes_max = 32*1024,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
struct runtime_data {
spinlock_t lock;
int state;
unsigned int dma_loaded;
unsigned int dma_period;
unsigned long buf_start;
unsigned long buf_pos;
unsigned long buf_end;
unsigned long period_bytes;
struct snd_pcm_hardware hw;
struct snd_pcm_substream *substream;
struct task_struct *compr_cap_kthread;
bool running;
bool opened;
bool dram_used;
} rd;
static int dummy_dma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
unsigned long totbytes = params_buffer_bytes(params);
pr_debug("Entered %s\n", __func__);
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = totbytes;
spin_lock_irq(&prtd->lock);
prtd->dma_loaded = 0;
prtd->dma_period = params_period_bytes(params);
prtd->buf_start = (unsigned long)runtime->dma_area;
prtd->buf_pos = prtd->buf_start;
prtd->buf_end = prtd->buf_start + totbytes;
while ((totbytes / prtd->dma_period) < PERIOD_MIN)
prtd->dma_period >>= 1;
spin_unlock_irq(&prtd->lock);
pr_info("Dummy DMA:%s:Addr=@0x%lx Total=%d PrdSz=%d(%d) #Prds=%d \n",
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? "P" : "C",
prtd->buf_start, (u32)runtime->dma_bytes,
params_period_bytes(params),(u32) prtd->dma_period,
params_periods(params));
return 0;
}
static int dummy_dma_hw_free(struct snd_pcm_substream *substream)
{
snd_pcm_set_runtime_buffer(substream, NULL);
return 0;
}
static int dummy_dma_prepare(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_debug("Entered %s +\n", __func__);
prtd->dma_loaded = 0;
prtd->buf_pos = prtd->buf_start;
pr_debug("Entered %s -\n", __func__);
return ret;
}
static snd_pcm_uframes_t dummy_dma_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
unsigned long res;
pr_debug("Entered %s\n", __func__);
res = prtd->buf_pos - prtd->buf_start;
pr_debug("%s res = %lx\n", __func__, res);
return bytes_to_frames(substream->runtime, res);
}
static int dummy_dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
pr_info("Entered %s\n", __func__);
if (rd.opened)
return -EBUSY;
if (!esa_compr_running())
return -ENODEV;
spin_lock_init(&rd.lock);
memcpy(&rd.hw, &dma_hardware, sizeof(struct snd_pcm_hardware));
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
runtime->private_data = &rd;
snd_soc_set_runtime_hwparams(substream, &rd.hw);
rd.opened = true;
pr_info("%s: prtd = %p\n", __func__, &rd);
return 0;
}
static int dummy_dma_close(struct snd_pcm_substream *substream)
{
pr_info("Entered %s\n", __func__);
rd.opened = false;
return 0;
}
static int dummy_dma_copy(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, void __user *buf, snd_pcm_uframes_t count)
{
struct snd_pcm_runtime *runtime = substream->runtime;
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, pos);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, count)))
return -EFAULT;
} else {
if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, count)))
return -EFAULT;
}
return 0;
}
static int dummy_dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_info("Entered %s\n", __func__);
spin_lock(&prtd->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/* Enable seiren firmware effect Fx external interrupt
to capture offload's PCM data from firmware */
esa_compr_ctrl_fxintr(true);
rd.running = true;
if (waitqueue_active(&compr_cap_wq))
wake_up_interruptible(&compr_cap_wq);
break;
case SNDRV_PCM_TRIGGER_STOP:
rd.running = false;
/* Disable seiren firmware effect Fx externalinterrupt */
esa_compr_ctrl_fxintr(false);
break;
default:
ret = -EINVAL;
break;
}
spin_unlock(&prtd->lock);
return ret;
}
static struct snd_pcm_ops dummy_dma_ops = {
.open = dummy_dma_open,
.close = dummy_dma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dummy_dma_hw_params,
.hw_free = dummy_dma_hw_free,
.prepare = dummy_dma_prepare,
.trigger = dummy_dma_trigger,
.pointer = dummy_dma_pointer,
.copy = dummy_dma_copy,
};
static int compr_cap_kthr(void *p)
{
struct runtime_data *prtd = (struct runtime_data *)p;
int ret = 0;
while (!kthread_should_stop()) {
wait_event_interruptible(compr_cap_wq, rd.running);
ret = esa_copy(prtd->buf_pos, prtd->dma_period);
if (ret < 0) {
pr_err("Failed to get f/w decoded pcm data\n");
rd.running = false;
continue;
}
prtd->buf_pos = prtd->buf_pos + prtd->dma_period;
if (prtd->buf_pos >= prtd->buf_end)
prtd->buf_pos = prtd->buf_start;
snd_pcm_period_elapsed(rd.substream);
}
return 0;
}
static int preallocate_dma_buffer_of(struct snd_pcm *pcm, int stream,
struct device_node *np)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
dma_addr_t dma_addr;
size_t size = dma_hardware.buffer_bytes_max;
pr_debug("Entered %s\n", __func__);
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = dma_alloc_coherent(pcm->card->dev, size, &dma_addr, GFP_KERNEL);
if (!buf->area)
return -ENOMEM;
buf->addr = dma_addr;
buf->bytes = size;
rd.substream = substream;
return 0;
}
static void dummy_dma_free_dma_buffers(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
pr_debug("Entered %s\n", __func__);
substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
if (!substream)
return;
buf = &substream->dma_buffer;
if (!buf->area)
return;
dma_free_coherent(pcm->card->dev, buf->bytes, buf->area, buf->addr);
buf->area = NULL;
}
static u64 dma_mask = DMA_BIT_MASK(32);
static int dummy_dma_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
struct device_node *np = rtd->cpu_dai->dev->of_node;
struct sched_param param = { .sched_priority = 0 };
struct task_struct *ret_task;
int ret = 0;
pr_debug("Entered %s\n", __func__);
if (!card->dev->dma_mask)
card->dev->dma_mask = &dma_mask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
ret = 0;
if (np)
ret = preallocate_dma_buffer_of(pcm,
SNDRV_PCM_STREAM_CAPTURE, np);
if (ret)
goto out;
}
ret_task = kthread_run(compr_cap_kthr, &rd, "compr_cap_kthr");
if (IS_ERR(ret_task)) {
pr_info("%s: failed to create compr_cap thread(%ld)\n",
__func__, PTR_ERR(ret_task));
ret = PTR_ERR(ret_task);
} else {
sched_setscheduler(ret_task, SCHED_NORMAL, &param);
}
out:
return ret;
}
static struct snd_soc_platform_driver dummy_asoc_platform = {
.ops = &dummy_dma_ops,
.pcm_new = dummy_dma_new,
.pcm_free = dummy_dma_free_dma_buffers,
};
#define SAMSUNG_I2S_RATES SNDRV_PCM_RATE_8000_192000
#define SAMSUNG_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE)
static struct snd_soc_dai_driver dummy_i2s_dai_drv = {
.name = "dummy-i2s-dai-driver",
};
static const struct snd_soc_component_driver dummy_i2s_component = {
.name = "dummy-i2s",
};
static int dummy_cpu_probe(struct platform_device *pdev)
{
dummy_i2s_dai_drv.symmetric_rates = 1;
dummy_i2s_dai_drv.capture.channels_min = 1;
dummy_i2s_dai_drv.capture.channels_max = 2;
dummy_i2s_dai_drv.capture.rates = SAMSUNG_I2S_RATES;
dummy_i2s_dai_drv.capture.formats = SAMSUNG_I2S_FMTS;
snd_soc_register_component(&pdev->dev, &dummy_i2s_component,
&dummy_i2s_dai_drv, 1);
snd_soc_register_platform(&pdev->dev, &dummy_asoc_platform);
return 0;
}
static int dummy_cpu_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static const struct of_device_id dummy_cpu_of_match[] = {
{ .compatible = "samsung,dummy-i2s", },
{},
};
MODULE_DEVICE_TABLE(of, dummy_cpu_of_match);
static struct platform_driver dummy_cpu_driver = {
.probe = dummy_cpu_probe,
.remove = dummy_cpu_remove,
.driver = {
.name = "dummy-i2s",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(dummy_cpu_of_match),
},
};
module_platform_driver(dummy_cpu_driver);
MODULE_AUTHOR("Hyunwoong Kim <khw0178.kim@samsung.com>");
MODULE_DESCRIPTION("Dummy dai driver");
MODULE_LICENSE("GPL");