mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-28 23:08:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
397
sound/soc/samsung/Kconfig
Normal file
397
sound/soc/samsung/Kconfig
Normal 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
112
sound/soc/samsung/Makefile
Normal 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
454
sound/soc/samsung/ac97.c
Normal 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
459
sound/soc/samsung/bells.c
Normal 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
966
sound/soc/samsung/compr.c
Normal 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(×tamp, 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, ×tamp, 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
18
sound/soc/samsung/compr.h
Normal 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
|
||||
80
sound/soc/samsung/cp_dummy.c
Normal file
80
sound/soc/samsung/cp_dummy.c
Normal 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
792
sound/soc/samsung/dma.c
Normal 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
35
sound/soc/samsung/dma.h
Normal 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
|
||||
81
sound/soc/samsung/dmaengine.c
Normal file
81
sound/soc/samsung/dmaengine.c
Normal 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
566
sound/soc/samsung/eax-dai.c
Normal 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
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
28
sound/soc/samsung/eax.h
Normal 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
|
||||
377
sound/soc/samsung/esa_sa_effect.c
Normal file
377
sound/soc/samsung/esa_sa_effect.c
Normal 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");
|
||||
111
sound/soc/samsung/esa_sa_effect.h
Normal file
111
sound/soc/samsung/esa_sa_effect.h
Normal 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
|
||||
926
sound/soc/samsung/espresso8890_wm5110.c
Normal file
926
sound/soc/samsung/espresso8890_wm5110.c
Normal 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");
|
||||
304
sound/soc/samsung/goni_wm8994.c
Normal file
304
sound/soc/samsung/goni_wm8994.c
Normal 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");
|
||||
278
sound/soc/samsung/h1940_uda1380.c
Normal file
278
sound/soc/samsung/h1940_uda1380.c
Normal 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");
|
||||
228
sound/soc/samsung/i2s-regs.h
Normal file
228
sound/soc/samsung/i2s-regs.h
Normal 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
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
24
sound/soc/samsung/i2s.h
Normal 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
431
sound/soc/samsung/idma.c
Normal 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
26
sound/soc/samsung/idma.h
Normal 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_ */
|
||||
114
sound/soc/samsung/jack_cod9002x.c
Normal file
114
sound/soc/samsung/jack_cod9002x.c
Normal 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);
|
||||
}
|
||||
|
||||
174
sound/soc/samsung/jive_wm8750.c
Normal file
174
sound/soc/samsung/jive_wm8750.c
Normal 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");
|
||||
329
sound/soc/samsung/littlemill.c
Normal file
329
sound/soc/samsung/littlemill.c
Normal 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");
|
||||
72
sound/soc/samsung/ln2440sbc_alc650.c
Normal file
72
sound/soc/samsung/ln2440sbc_alc650.c
Normal 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
212
sound/soc/samsung/lowland.c
Normal 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");
|
||||
758
sound/soc/samsung/lpass-exynos7570.c
Normal file
758
sound/soc/samsung/lpass-exynos7570.c
Normal 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, ®_list, node)
|
||||
ar->val = readl(ar->reg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void lpass_reg_restore(void)
|
||||
{
|
||||
struct aud_reg *ar;
|
||||
|
||||
list_for_each_entry(ar, ®_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, ®_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");
|
||||
751
sound/soc/samsung/lpass-exynos7870.c
Normal file
751
sound/soc/samsung/lpass-exynos7870.c
Normal 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, ®_list, node)
|
||||
ar->val = readl(ar->reg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void lpass_reg_restore(void)
|
||||
{
|
||||
struct aud_reg *ar;
|
||||
|
||||
list_for_each_entry(ar, ®_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, ®_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");
|
||||
144
sound/soc/samsung/lpass-exynos8890.c
Normal file
144
sound/soc/samsung/lpass-exynos8890.c
Normal 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
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
118
sound/soc/samsung/lpass.h
Normal 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 */
|
||||
412
sound/soc/samsung/neo1973_wm8753.c
Normal file
412
sound/soc/samsung/neo1973_wm8753.c
Normal 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");
|
||||
177
sound/soc/samsung/odroidx2_max98090.c
Normal file
177
sound/soc/samsung/odroidx2_max98090.c
Normal 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
639
sound/soc/samsung/pcm.c
Normal 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
17
sound/soc/samsung/pcm.h
Normal 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 */
|
||||
66
sound/soc/samsung/regs-ac97.h
Normal file
66
sound/soc/samsung/regs-ac97.h
Normal 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__ */
|
||||
115
sound/soc/samsung/regs-i2s-v2.h
Normal file
115
sound/soc/samsung/regs-i2s-v2.h
Normal 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 */
|
||||
69
sound/soc/samsung/regs-iis.h
Normal file
69
sound/soc/samsung/regs-iis.h
Normal 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__ */
|
||||
300
sound/soc/samsung/rx1950_uda1380.c
Normal file
300
sound/soc/samsung/rx1950_uda1380.c
Normal 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");
|
||||
735
sound/soc/samsung/s3c-i2s-v2.c
Normal file
735
sound/soc/samsung/s3c-i2s-v2.c
Normal 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");
|
||||
107
sound/soc/samsung/s3c-i2s-v2.h
Normal file
107
sound/soc/samsung/s3c-i2s-v2.h
Normal 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 */
|
||||
193
sound/soc/samsung/s3c2412-i2s.c
Normal file
193
sound/soc/samsung/s3c2412-i2s.c
Normal 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");
|
||||
27
sound/soc/samsung/s3c2412-i2s.h
Normal file
27
sound/soc/samsung/s3c2412-i2s.h
Normal 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 */
|
||||
498
sound/soc/samsung/s3c24xx-i2s.c
Normal file
498
sound/soc/samsung/s3c24xx-i2s.c
Normal 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");
|
||||
35
sound/soc/samsung/s3c24xx-i2s.h
Normal file
35
sound/soc/samsung/s3c24xx-i2s.h
Normal 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_*/
|
||||
388
sound/soc/samsung/s3c24xx_simtec.c
Normal file
388
sound/soc/samsung/s3c24xx_simtec.c
Normal 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");
|
||||
22
sound/soc/samsung/s3c24xx_simtec.h
Normal file
22
sound/soc/samsung/s3c24xx_simtec.h
Normal 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
|
||||
115
sound/soc/samsung/s3c24xx_simtec_hermes.c
Normal file
115
sound/soc/samsung/s3c24xx_simtec_hermes.c
Normal 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");
|
||||
103
sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
Normal file
103
sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
Normal 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");
|
||||
351
sound/soc/samsung/s3c24xx_uda134x.c
Normal file
351
sound/soc/samsung/s3c24xx_uda134x.c
Normal 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");
|
||||
28
sound/soc/samsung/seiren/Kconfig
Normal file
28
sound/soc/samsung/seiren/Kconfig
Normal 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
|
||||
3
sound/soc/samsung/seiren/Makefile
Normal file
3
sound/soc/samsung/seiren/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# SEIREN Audio Solution Support
|
||||
obj-$(CONFIG_SND_SAMSUNG_SEIREN) += seiren.o
|
||||
obj-$(CONFIG_SND_SAMSUNG_ELPE) += lpeffwork.o
|
||||
234
sound/soc/samsung/seiren/lpeffwork.c
Normal file
234
sound/soc/samsung/seiren/lpeffwork.c
Normal 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");
|
||||
16
sound/soc/samsung/seiren/lpeffwork.h
Normal file
16
sound/soc/samsung/seiren/lpeffwork.h
Normal 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__ */
|
||||
414
sound/soc/samsung/seiren/seiren-dma.c
Normal file
414
sound/soc/samsung/seiren/seiren-dma.c
Normal 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");
|
||||
31
sound/soc/samsung/seiren/seiren-dma.h
Normal file
31
sound/soc/samsung/seiren/seiren-dma.h
Normal 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 */
|
||||
2321
sound/soc/samsung/seiren/seiren.c
Normal file
2321
sound/soc/samsung/seiren/seiren.c
Normal file
File diff suppressed because it is too large
Load diff
453
sound/soc/samsung/seiren/seiren.h
Normal file
453
sound/soc/samsung/seiren/seiren.h
Normal 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 */
|
||||
22
sound/soc/samsung/seiren/seiren_error.h
Normal file
22
sound/soc/samsung/seiren/seiren_error.h
Normal 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 */
|
||||
15
sound/soc/samsung/seiren/seiren_ioctl.h
Normal file
15
sound/soc/samsung/seiren/seiren_ioctl.h
Normal 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 */
|
||||
281
sound/soc/samsung/smartq_wm8987.c
Normal file
281
sound/soc/samsung/smartq_wm8987.c
Normal 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");
|
||||
68
sound/soc/samsung/smdk2443_wm9710.c
Normal file
68
sound/soc/samsung/smdk2443_wm9710.c
Normal 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");
|
||||
808
sound/soc/samsung/smdk7570-cod3026.c
Normal file
808
sound/soc/samsung/smdk7570-cod3026.c
Normal 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");
|
||||
851
sound/soc/samsung/smdk7570-cod9002.c
Normal file
851
sound/soc/samsung/smdk7570-cod9002.c
Normal 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");
|
||||
312
sound/soc/samsung/smdk7570_dummy.c
Normal file
312
sound/soc/samsung/smdk7570_dummy.c
Normal 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");
|
||||
811
sound/soc/samsung/smdk7870-cod3026.c
Normal file
811
sound/soc/samsung/smdk7870-cod3026.c
Normal 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");
|
||||
223
sound/soc/samsung/smdk_spdif.c
Normal file
223
sound/soc/samsung/smdk_spdif.c
Normal 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");
|
||||
253
sound/soc/samsung/smdk_wm8580.c
Normal file
253
sound/soc/samsung/smdk_wm8580.c
Normal 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");
|
||||
186
sound/soc/samsung/smdk_wm8580pcm.c
Normal file
186
sound/soc/samsung/smdk_wm8580pcm.c
Normal 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");
|
||||
205
sound/soc/samsung/smdk_wm8994.c
Normal file
205
sound/soc/samsung/smdk_wm8994.c
Normal 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");
|
||||
156
sound/soc/samsung/smdk_wm8994pcm.c
Normal file
156
sound/soc/samsung/smdk_wm8994pcm.c
Normal 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");
|
||||
108
sound/soc/samsung/smdk_wm9713.c
Normal file
108
sound/soc/samsung/smdk_wm9713.c
Normal 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
128
sound/soc/samsung/snow.c
Normal 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
489
sound/soc/samsung/spdif.c
Normal 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
19
sound/soc/samsung/spdif.h
Normal 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 */
|
||||
354
sound/soc/samsung/speyside.c
Normal file
354
sound/soc/samsung/speyside.c
Normal 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");
|
||||
248
sound/soc/samsung/tobermory.c
Normal file
248
sound/soc/samsung/tobermory.c
Normal 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");
|
||||
678
sound/soc/samsung/universal7270-largo.c
Normal file
678
sound/soc/samsung/universal7270-largo.c
Normal 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");
|
||||
1095
sound/soc/samsung/universal7570-cod9002.c
Normal file
1095
sound/soc/samsung/universal7570-cod9002.c
Normal file
File diff suppressed because it is too large
Load diff
856
sound/soc/samsung/universal7870-cod3026.c
Normal file
856
sound/soc/samsung/universal7870-cod3026.c
Normal 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");
|
||||
312
sound/soc/samsung/universal8890_dummy.c
Normal file
312
sound/soc/samsung/universal8890_dummy.c
Normal 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");
|
||||
411
sound/soc/samsung/visualizercap-dummydai.c
Normal file
411
sound/soc/samsung/visualizercap-dummydai.c
Normal 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, ¶m);
|
||||
}
|
||||
|
||||
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");
|
||||
Loading…
Add table
Add a link
Reference in a new issue