diff --git a/arch/arm/configs/odroidx_defconfig b/arch/arm/configs/odroidx_defconfig index 8c8d8f0f1f4a6b..2fc2dc945642e6 100644 --- a/arch/arm/configs/odroidx_defconfig +++ b/arch/arm/configs/odroidx_defconfig @@ -2033,11 +2033,15 @@ CONFIG_SND_USB=y CONFIG_SND_SOC=y # CONFIG_SND_DESIGNWARE_I2S is not set CONFIG_SND_SOC_SAMSUNG=y +CONFIG_SND_SAMSUNG_I2S=y # CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994 is not set # CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF is not set # CONFIG_SND_SOC_SMDK_WM8994_PCM is not set +CONFIG_SND_SOC_HKDK_MAX98090=y +CONFIG_MAX98090_HEADSET=y CONFIG_SND_SOC_I2C_AND_SPI=y # CONFIG_SND_SOC_ALL_CODECS is not set +CONFIG_SND_SOC_MAX98090=y # CONFIG_SND_SIMPLE_CARD is not set # CONFIG_SOUND_PRIME is not set diff --git a/arch/arm/mach-exynos/clock-exynos4.c b/arch/arm/mach-exynos/clock-exynos4.c index 7e7a7ebbe22c70..a3478db9c58cba 100644 --- a/arch/arm/mach-exynos/clock-exynos4.c +++ b/arch/arm/mach-exynos/clock-exynos4.c @@ -24,6 +24,7 @@ #include #include +#include #include #include "common.h" @@ -198,6 +199,11 @@ static int exynos4_clk_ip_perir_ctrl(struct clk *clk, int enable) return s5p_gatectrl(EXYNOS4_CLKGATE_IP_PERIR, clk, enable); } +static int exynos4_clk_vpll_ctrl(struct clk *clk, int enable) +{ + return s5p_gatectrl(EXYNOS4_VPLL_CON0, clk, enable); +} + int exynos4_clk_ip_dmc_ctrl(struct clk *clk, int enable) { return s5p_gatectrl(EXYNOS4_CLKGATE_IP_DMC, clk, enable); @@ -213,6 +219,16 @@ static int exynos4_clk_dac_ctrl(struct clk *clk, int enable) return s5p_gatectrl(S5P_DAC_PHY_CONTROL, clk, enable); } +static int exynos4_clksrc_mask_maudio_ctrl(struct clk *clk, int enable) +{ + return s5p_gatectrl(EXYNOS4_CLKSRC_MASK_MAUDIO, clk, enable); +} + +int exynos4_clk_audss_ctrl(struct clk *clk, int enable) +{ + return s5p_gatectrl(S5P_CLKGATE_AUDSS, clk, enable); +} + /* Core list of CMU_CPU side */ static struct clksrc_clk exynos4_clk_mout_apll = { @@ -451,6 +467,25 @@ static struct clksrc_sources exynos4_clkset_sclk_vpll = { .nr_sources = ARRAY_SIZE(exynos4_clkset_sclk_vpll_list), }; +static struct clk *exynos4_clkset_mout_audss_list[] = { + &clk_ext_xtal_mux, + &clk_fout_epll, +}; + +static struct clksrc_sources clkset_mout_audss = { + .sources = exynos4_clkset_mout_audss_list, + .nr_sources = ARRAY_SIZE(exynos4_clkset_mout_audss_list), +}; + +static struct clksrc_clk exynos4_clk_mout_audss = { + .clk = { + .name = "mout_audss", + }, + .sources = &clkset_mout_audss, + .reg_src = { .reg = S5P_CLKSRC_AUDSS, .shift = 0, .size = 1 }, +}; + + static struct clksrc_clk exynos4_clk_sclk_vpll = { .clk = { .name = "sclk_vpll", @@ -459,6 +494,43 @@ static struct clksrc_clk exynos4_clk_sclk_vpll = { .reg_src = { .reg = EXYNOS4_CLKSRC_TOP0, .shift = 8, .size = 1 }, }; +static struct clksrc_clk exynos4_clk_dout_audss_srp = { + .clk = { + .name = "dout_srp", + .parent = &exynos4_clk_mout_audss.clk, + }, + .reg_div = { .reg = S5P_CLKDIV_AUDSS, .shift = 0, .size = 4 }, +}; + +static struct clksrc_clk exynos4_clk_sclk_audss_bus = { + .clk = { + .name = "busclk", + .parent = &exynos4_clk_dout_audss_srp.clk, + .enable = exynos4_clk_audss_ctrl, + .ctrlbit = (1 << 2), + }, + .reg_div = { .reg = S5P_CLKDIV_AUDSS, .shift = 4, .size = 4 }, +}; + +static struct clk exynos4_init_audss_clocks[] = { + { + .name = "srpclk", + .enable = exynos4_clk_audss_ctrl, + .parent = &exynos4_clk_dout_audss_srp.clk, + .ctrlbit = (1 << 0), + }, { + .name = "iis", + .devname = "samsung-i2s.0", + .enable = exynos4_clk_audss_ctrl, + .ctrlbit = (1 << 3) | (1 << 2), + }, { + .name = "pcm", + .devname = "samsung-pcm.0", + .enable = exynos4_clk_audss_ctrl, + .ctrlbit = (1 << 5), + }, +}; + static struct clk exynos4_init_clocks_off[] = { { .name = "timers", @@ -599,11 +671,6 @@ static struct clk exynos4_init_clocks_off[] = { .devname = "exynos4210-spi.2", .enable = exynos4_clk_ip_peril_ctrl, .ctrlbit = (1 << 18), - }, { - .name = "iis", - .devname = "samsung-i2s.0", - .enable = exynos4_clk_ip_peril_ctrl, - .ctrlbit = (1 << 19), }, { .name = "iis", .devname = "samsung-i2s.1", @@ -728,7 +795,65 @@ static struct clk exynos4_init_clocks_off[] = { .devname = SYSMMU_CLOCK_DEVNAME(fimd0, 10), .enable = exynos4_clk_ip_lcd0_ctrl, .ctrlbit = (1 << 4), - } + }, +}; + +struct clksrc_clk exynos4_clk_audiocdclk0 = { + .clk = { + .name = "audiocdclk", + .rate = 16934400, + }, +}; + +static struct clk *clkset_sclk_audio0_list[] = { + [0] = &exynos4_clk_audiocdclk0.clk, + [1] = NULL, + [2] = &exynos4_clk_sclk_hdmi27m, + [3] = &exynos4_clk_sclk_usbphy0, + [4] = &clk_ext_xtal_mux, + [5] = &clk_xusbxti, + [6] = &exynos4_clk_mout_mpll.clk, + [7] = &exynos4_clk_mout_epll.clk, + [8] = &exynos4_clk_sclk_vpll.clk, +}; + +static struct clksrc_sources exynos4_clkset_sclk_audio0 = { + .sources = clkset_sclk_audio0_list, + .nr_sources = ARRAY_SIZE(clkset_sclk_audio0_list), +}; + +static struct clksrc_clk exynos4_clk_sclk_audio0 = { + .clk = { + .name = "audio-bus", + .enable = exynos4_clksrc_mask_maudio_ctrl, + .ctrlbit = (1 << 0), + }, + .sources = &exynos4_clkset_sclk_audio0, + .reg_src = { .reg = EXYNOS4_CLKSRC_MAUDIO, .shift = 0, .size = 4 }, + .reg_div = { .reg = EXYNOS4_CLKDIV_MAUDIO, .shift = 0, .size = 4 }, +}; + +static struct clk *exynos4_clkset_sclk_audss_list[] = { + &exynos4_clk_mout_audss.clk, + &exynos4_clk_audiocdclk0.clk, + &exynos4_clk_sclk_audio0.clk, +}; + +static struct clksrc_sources exynos4_clkset_sclk_audss = { + .sources = exynos4_clkset_sclk_audss_list, + .nr_sources = ARRAY_SIZE(exynos4_clkset_sclk_audss_list), +}; + +static struct clksrc_clk exynos4_clk_sclk_audss_i2s = { + .clk = { + .name = "i2sclk", + .parent = &exynos4_clk_mout_audss.clk, + .enable = exynos4_clk_audss_ctrl, + .ctrlbit = (1 << 3), + }, + .sources = &exynos4_clkset_sclk_audss, + .reg_src = { .reg = S5P_CLKSRC_AUDSS, .shift = 2, .size = 2 }, + .reg_div = { .reg = S5P_CLKDIV_AUDSS, .shift = 8, .size = 4 }, }; static struct clk exynos4_init_clocks_on[] = { @@ -1102,15 +1227,7 @@ static struct clksrc_clk exynos4_clksrcs[] = { .sources = &exynos4_clkset_mout_mfc, .reg_src = { .reg = EXYNOS4_CLKSRC_MFC, .shift = 8, .size = 1 }, .reg_div = { .reg = EXYNOS4_CLKDIV_MFC, .shift = 0, .size = 4 }, - }, { - .clk = { - .name = "sclk_dwmmc", - .parent = &exynos4_clk_dout_mmc4.clk, - .enable = exynos4_clksrc_mask_fsys_ctrl, - .ctrlbit = (1 << 16), - }, - .reg_div = { .reg = EXYNOS4_CLKDIV_FSYS3, .shift = 8, .size = 8 }, - } + }, }; static struct clksrc_clk exynos4_clk_sclk_uart0 = { @@ -1279,6 +1396,14 @@ static struct clksrc_clk exynos4_clk_sclk_spi2 = { .reg_div = { .reg = EXYNOS4_CLKDIV_PERIL2, .shift = 8, .size = 8 }, }; +static struct clksrc_clk exynos4_clk_sclk_pcm0 = { + .clk = { + .name = "sclk_pcm", + .parent = &exynos4_clk_sclk_audio0.clk, + }, + .reg_div = { .reg = EXYNOS4_CLKDIV_MAUDIO, .shift = 4, .size = 8 }, +}; + /* Clock initialization code */ static struct clksrc_clk *exynos4_sysclks[] = { &exynos4_clk_mout_apll, @@ -1311,6 +1436,13 @@ static struct clksrc_clk *exynos4_sysclks[] = { &exynos4_clk_dout_mmc4, &exynos4_clk_mout_mfc0, &exynos4_clk_mout_mfc1, + &exynos4_clk_audiocdclk0, + &exynos4_clk_mout_audss, + &exynos4_clk_sclk_audss_bus, + &exynos4_clk_sclk_audss_i2s, + &exynos4_clk_dout_audss_srp, + &exynos4_clk_sclk_audio0, + &exynos4_clk_sclk_pcm0, }; static struct clk *exynos4_clk_cdev[] = { @@ -1438,6 +1570,92 @@ static struct clk_ops exynos4_vpll_ops = { .set_rate = exynos4_vpll_set_rate, }; +static unsigned long exynos4_epll_get_rate(struct clk *clk) +{ + return clk->rate; +} + +static u32 epll_div[][6] = { + { 192000000, 0, 48, 3, 1, 0 }, + { 180000000, 0, 45, 3, 1, 0 }, + { 73728000, 1, 73, 3, 3, 47710 }, + { 67737600, 1, 90, 4, 3, 20762 }, + { 49152000, 0, 49, 3, 3, 9961 }, + { 45158400, 0, 45, 3, 3, 10381 }, + { 180633600, 0, 45, 3, 1, 10381 }, +}; + +static int exynos4_epll_set_rate(struct clk *clk, unsigned long rate) +{ + unsigned int epll_con, epll_con_k; + unsigned int i; + unsigned int tmp; + unsigned int epll_rate; + unsigned int locktime; + unsigned int lockcnt; + + /* Return if nothing changed */ + if (clk->rate == rate) + return 0; + + if (clk->parent) + epll_rate = clk_get_rate(clk->parent); + else + epll_rate = clk_ext_xtal_mux.rate; + + if (epll_rate != 24000000) { + pr_err("Invalid Clock : recommended clock is 24MHz.\n"); + return -EINVAL; + } + + + epll_con = __raw_readl(EXYNOS4_EPLL_CON0); + epll_con &= ~(0x1 << 27 | \ + PLL46XX_MDIV_MASK << PLL46XX_MDIV_SHIFT | \ + PLL46XX_PDIV_MASK << PLL46XX_PDIV_SHIFT | \ + PLL46XX_SDIV_MASK << PLL46XX_SDIV_SHIFT); + + for (i = 0; i < ARRAY_SIZE(epll_div); i++) { + if (epll_div[i][0] == rate) { + epll_con_k = epll_div[i][5] << 0; + epll_con |= epll_div[i][1] << 27; + epll_con |= epll_div[i][2] << PLL46XX_MDIV_SHIFT; + epll_con |= epll_div[i][3] << PLL46XX_PDIV_SHIFT; + epll_con |= epll_div[i][4] << PLL46XX_SDIV_SHIFT; + break; + } + } + + if (i == ARRAY_SIZE(epll_div)) { + pr_err("%s: Invalid Clock EPLL Frequency\n", __func__); + return -EINVAL; + } + + epll_rate /= 1000000; + + /* 3000 max_cycls : specification data */ + locktime = 3000 / epll_rate * epll_div[i][3]; + lockcnt = locktime * 10000 / (10000 / epll_rate); + + __raw_writel(lockcnt, EXYNOS4_EPLL_LOCK); + + __raw_writel(epll_con, EXYNOS4_EPLL_CON0); + __raw_writel(epll_con_k, EXYNOS4_EPLL_CON1); + + do { + tmp = __raw_readl(EXYNOS4_EPLL_CON0); + } while (!(tmp & 0x1 << EXYNOS4_EPLLCON0_LOCKED_SHIFT)); + + clk->rate = rate; + + return 0; +} + +static struct clk_ops exynos4_epll_ops = { + .get_rate = exynos4_epll_get_rate, + .set_rate = exynos4_epll_set_rate, +}; + void __init_or_cpufreq exynos4_setup_clocks(void) { struct clk *xtal_clk; @@ -1494,6 +1712,7 @@ void __init_or_cpufreq exynos4_setup_clocks(void) clk_fout_apll.ops = &exynos4_fout_apll_ops; clk_fout_mpll.rate = mpll; + clk_fout_epll.ops = &exynos4_epll_ops; clk_fout_epll.rate = epll; clk_fout_vpll.ops = &exynos4_vpll_ops; clk_fout_vpll.rate = vpll; @@ -1518,6 +1737,16 @@ void __init_or_cpufreq exynos4_setup_clocks(void) clk_h.rate = sclk_dmc; clk_p.rate = aclk_100; + clk_set_parent(&exynos4_clk_mout_audss.clk, &clk_fout_epll); + clk_set_parent(&exynos4_clk_sclk_audio0.clk, + &exynos4_clk_mout_epll.clk); + clk_set_parent(&exynos4_clk_mout_epll.clk, &clk_fout_epll); + + clk_fout_vpll.enable = exynos4_clk_vpll_ctrl; + clk_fout_vpll.ops = &exynos4_vpll_ops; + + clk_set_rate(&exynos4_clk_sclk_apll.clk, 100000000); + for (ptr = 0; ptr < ARRAY_SIZE(exynos4_clksrcs); ptr++) s3c_set_clksrc(&exynos4_clksrcs[ptr], true); } @@ -1577,6 +1806,11 @@ void __init exynos4_register_clocks(void) s3c_disable_clocks(exynos4_init_clocks_off, ARRAY_SIZE(exynos4_init_clocks_off)); clkdev_add_table(exynos4_clk_lookup, ARRAY_SIZE(exynos4_clk_lookup)); + s3c_register_clocks(exynos4_init_audss_clocks, + ARRAY_SIZE(exynos4_init_audss_clocks)); + s3c_disable_clocks(exynos4_init_audss_clocks, + ARRAY_SIZE(exynos4_init_audss_clocks)); + register_syscore_ops(&exynos4_clock_syscore_ops); s3c24xx_register_clock(&dummy_apb_pclk); diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index 94d58af280057f..9450651e1b8708 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -198,6 +198,11 @@ static struct map_desc exynos4_iodesc[] __initdata = { .pfn = __phys_to_pfn(EXYNOS4_PA_HSPHY), .length = SZ_4K, .type = MT_DEVICE, + }, { + .virtual = (unsigned long)S5P_VA_AUDSS, + .pfn = __phys_to_pfn(EXYNOS4_PA_AUDSS), + .length = SZ_4K, + .type = MT_DEVICE, }, }; diff --git a/arch/arm/mach-exynos/include/mach/map.h b/arch/arm/mach-exynos/include/mach/map.h index c72b675b3e4b98..2f8a16f7b5eab9 100644 --- a/arch/arm/mach-exynos/include/mach/map.h +++ b/arch/arm/mach-exynos/include/mach/map.h @@ -34,6 +34,8 @@ #define EXYNOS4_PA_JPEG 0x11840000 +#define EXYNOS4_PA_AUDSS 0x03810000 + /* x = 0...1 */ #define EXYNOS4_PA_FIMC_LITE(x) (0x12390000 + ((x) * 0x10000)) diff --git a/arch/arm/mach-exynos/include/mach/regs-audss.h b/arch/arm/mach-exynos/include/mach/regs-audss.h index ca5a8b64218a98..e75b9599b02186 100644 --- a/arch/arm/mach-exynos/include/mach/regs-audss.h +++ b/arch/arm/mach-exynos/include/mach/regs-audss.h @@ -15,4 +15,10 @@ #define EXYNOS4_AUDSS_INT_MEM (0x03000000) +#define EXYNOS4_AUDSSREG(x) (S5P_VA_AUDSS + (x)) + +#define S5P_CLKSRC_AUDSS EXYNOS4_AUDSSREG(0x0) +#define S5P_CLKDIV_AUDSS EXYNOS4_AUDSSREG(0x4) +#define S5P_CLKGATE_AUDSS EXYNOS4_AUDSSREG(0x8) + #endif /* _PLAT_REGS_AUDSS_H */ diff --git a/arch/arm/mach-exynos/mach-hkdk4412.c b/arch/arm/mach-exynos/mach-hkdk4412.c index 35c4412f20b33d..0063d50262e28d 100644 --- a/arch/arm/mach-exynos/mach-hkdk4412.c +++ b/arch/arm/mach-exynos/mach-hkdk4412.c @@ -877,6 +877,11 @@ static struct i2c_board_info hkdk4412_i2c_devs0[] __initdata = { }; static struct i2c_board_info hkdk4412_i2c_devs1[] __initdata = { +#if defined(CONFIG_SND_SOC_MAX98090) + { + I2C_BOARD_INFO("max98090", (0x20>>1)), + }, +#endif }; static struct i2c_board_info hkdk4412_i2c_devs3[] __initdata = { @@ -999,6 +1004,13 @@ static struct platform_device hkdk4412_gpio_keys = { }, }; +#if defined(CONFIG_SND_SOC_HKDK_MAX98090) +static struct platform_device hardkernel_audio_device = { + .name = "hkdk-snd-max89090", + .id = -1, +}; +#endif + /* USB EHCI */ static struct s5p_ehci_platdata hkdk4412_ehci_pdata; @@ -1060,6 +1072,9 @@ static struct platform_device *hkdk4412_devices[] __initdata = { &s3c_device_usb_hsotg, &s3c_device_wdt, &s5p_device_ehci, +#ifdef CONFIG_SND_SAMSUNG_I2S + &exynos4_device_i2s0, +#endif &s5p_device_fimc0, &s5p_device_fimc1, &s5p_device_fimc2, @@ -1082,6 +1097,11 @@ static struct platform_device *hkdk4412_devices[] __initdata = { &hkdk4412_lcd_lp101wh1, #endif &hkdk4412_gpio_keys, + &samsung_asoc_dma, + &samsung_asoc_idma, +#if defined(CONFIG_SND_SOC_HKDK_MAX98090) + &hardkernel_audio_device, +#endif }; static void __init hkdk4412_map_io(void) diff --git a/arch/arm/plat-samsung/include/plat/map-s5p.h b/arch/arm/plat-samsung/include/plat/map-s5p.h index c2d7bdae589106..3558277ff2a913 100644 --- a/arch/arm/plat-samsung/include/plat/map-s5p.h +++ b/arch/arm/plat-samsung/include/plat/map-s5p.h @@ -40,6 +40,8 @@ #define S5P_VA_GIC_CPU S3C_ADDR(0x02810000) #define S5P_VA_GIC_DIST S3C_ADDR(0x02820000) +#define S5P_VA_AUDSS S3C_ADDR(0x02910000) + #define VA_VIC(x) (S3C_VA_IRQ + ((x) * 0x10000)) #define VA_VIC0 VA_VIC(0) #define VA_VIC1 VA_VIC(1) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 9f8e8594aeb904..111545e21e5c95 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -43,6 +43,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_LM4857 if I2C select SND_SOC_LM49453 if I2C select SND_SOC_MAX98088 if I2C + select SND_SOC_MAX98090 if I2C select SND_SOC_MAX98095 if I2C select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9768 if I2C @@ -253,6 +254,9 @@ config SND_SOC_LM49453 config SND_SOC_MAX98088 tristate +config SND_SOC_MAX98090 + tristate + config SND_SOC_MAX98095 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 34148bb59c68dc..a4f870e97baac7 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -33,6 +33,7 @@ snd-soc-lm4857-objs := lm4857.o snd-soc-lm49453-objs := lm49453.o snd-soc-max9768-objs := max9768.o snd-soc-max98088-objs := max98088.o +snd-soc-max98090-objs := max98090.o max98090_mixer.o snd-soc-max98095-objs := max98095.o snd-soc-max9850-objs := max9850.o snd-soc-mc13783-objs := mc13783.o @@ -152,6 +153,7 @@ obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o +obj-$(CONFIG_SND_SOC_MAX98090) += snd-soc-max98090.o obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c new file mode 100644 index 00000000000000..48534201eb85cd --- /dev/null +++ b/sound/soc/codecs/max98090.c @@ -0,0 +1,1329 @@ +/* + * max98090.c -- MAX98090 ALSA SoC Audio driver + * + * Copyright 2011 Maxim Integrated Products + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98090.h" + +static const u8 max98090_reg_def[M98090_REG_CNT] = { + 0x00, /* 00 */ + 0x00, /* 01 */ + 0x00, /* 02 */ + 0x00, /* 03 */ + 0x00, /* 04 */ + 0x00, /* 05 */ + 0x00, /* 06 */ + 0x00, /* 07 */ + 0x00, /* 08 */ + 0x00, /* 09 */ + 0x00, /* 0A */ + 0x00, /* 0B */ + 0x00, /* 0C */ + 0x00, /* 0D */ + 0x00, /* 0E */ + 0x00, /* 0F */ + 0x00, /* 10 */ + 0x00, /* 11 */ + 0x00, /* 12 */ + 0x00, /* 13 */ + 0x00, /* 14 */ + 0x00, /* 15 */ + 0x00, /* 16 */ + 0x00, /* 17 */ + 0x00, /* 18 */ + 0x00, /* 19 */ + 0x00, /* 1A */ + 0x00, /* 1B */ + 0x00, /* 1C */ + 0x00, /* 1D */ + 0x00, /* 1E */ + 0x00, /* 1F */ + 0x00, /* 20 */ + 0x00, /* 21 */ + 0x00, /* 22 */ + 0x00, /* 23 */ + 0x00, /* 24 */ + 0x00, /* 25 */ + 0x00, /* 26 */ + 0x00, /* 27 */ + 0x00, /* 28 */ + 0x00, /* 29 */ + 0x00, /* 2A */ + 0x00, /* 2B */ + 0x00, /* 2C */ + 0x00, /* 2D */ + 0x00, /* 2E */ + 0x00, /* 2F */ + 0x00, /* 30 */ + 0x00, /* 31 */ + 0x00, /* 32 */ + 0x00, /* 33 */ + 0x00, /* 34 */ + 0x00, /* 35 */ + 0x00, /* 36 */ + 0x00, /* 37 */ + 0x00, /* 38 */ + 0x00, /* 39 */ + 0x00, /* 3A */ + 0x00, /* 3B */ + 0x00, /* 3C */ + 0x00, /* 3D */ + 0x00, /* 3E */ + 0x00, /* 3F */ + 0x00, /* 40 */ + 0x00, /* 41 */ + 0x00, /* 42 */ + 0x00, /* 43 */ + 0x00, /* 44 */ + 0x00, /* 45 */ + 0x00, /* 46 */ + 0x00, /* 47 */ + 0x00, /* 48 */ + 0x00, /* 49 */ + 0x00, /* 4A */ + 0x00, /* 4B */ + 0x00, /* 4C */ + 0x00, /* 4D */ + 0x00, /* 4E */ + 0x00, /* 4F */ + 0x00, /* 50 */ + 0x00, /* 51 */ + 0x00, /* 52 */ + 0x00, /* 53 */ + 0x00, /* 54 */ + 0x00, /* 55 */ + 0x00, /* 56 */ + 0x00, /* 57 */ + 0x00, /* 58 */ + 0x00, /* 59 */ + 0x00, /* 5A */ + 0x00, /* 5B */ + 0x00, /* 5C */ + 0x00, /* 5D */ + 0x00, /* 5E */ + 0x00, /* 5F */ + 0x00, /* 60 */ + 0x00, /* 61 */ + 0x00, /* 62 */ + 0x00, /* 63 */ + 0x00, /* 64 */ + 0x00, /* 65 */ + 0x00, /* 66 */ + 0x00, /* 67 */ + 0x00, /* 68 */ + 0x00, /* 69 */ + 0x00, /* 6A */ + 0x00, /* 6B */ + 0x00, /* 6C */ + 0x00, /* 6D */ + 0x00, /* 6E */ + 0x00, /* 6F */ + 0x00, /* 70 */ + 0x00, /* 71 */ + 0x00, /* 72 */ + 0x00, /* 73 */ + 0x00, /* 74 */ + 0x00, /* 75 */ + 0x00, /* 76 */ + 0x00, /* 77 */ + 0x00, /* 78 */ + 0x00, /* 79 */ + 0x00, /* 7A */ + 0x00, /* 7B */ + 0x00, /* 7C */ + 0x00, /* 7D */ + 0x00, /* 7E */ + 0x00, /* 7F */ + 0x00, /* 80 */ + 0x00, /* 81 */ + 0x00, /* 82 */ + 0x00, /* 83 */ + 0x00, /* 84 */ + 0x00, /* 85 */ + 0x00, /* 86 */ + 0x00, /* 87 */ + 0x00, /* 88 */ + 0x00, /* 89 */ + 0x00, /* 8A */ + 0x00, /* 8B */ + 0x00, /* 8C */ + 0x00, /* 8D */ + 0x00, /* 8E */ + 0x00, /* 8F */ + 0x00, /* 90 */ + 0x00, /* 91 */ + 0x30, /* 92 */ + 0xF0, /* 93 */ + 0x00, /* 94 */ + 0x00, /* 95 */ + 0x3F, /* 96 */ + 0x00, /* 97 */ + 0x00, /* 98 */ + 0x00, /* 99 */ + 0x00, /* 9A */ + 0x00, /* 9B */ + 0x00, /* 9C */ + 0x00, /* 9D */ + 0x00, /* 9E */ + 0x00, /* 9F */ + 0x00, /* A0 */ + 0x00, /* A1 */ + 0x00, /* A2 */ + 0x00, /* A3 */ + 0x00, /* A4 */ + 0x00, /* A5 */ + 0x00, /* A6 */ + 0x00, /* A7 */ + 0x00, /* A8 */ + 0x00, /* A9 */ + 0x00, /* AA */ + 0x00, /* AB */ + 0x00, /* AC */ + 0x00, /* AD */ + 0x00, /* AE */ + 0x00, /* AF */ + 0x00, /* B0 */ + 0x00, /* B1 */ + 0x00, /* B2 */ + 0x00, /* B3 */ + 0x00, /* B4 */ + 0x00, /* B5 */ + 0x00, /* B6 */ + 0x00, /* B7 */ + 0x00, /* B8 */ + 0x00, /* B9 */ + 0x00, /* BA */ + 0x00, /* BB */ + 0x00, /* BC */ + 0x00, /* BD */ + 0x00, /* BE */ + 0x00, /* BF */ + 0x00, /* C0 */ + 0x00, /* C1 */ + 0x00, /* C2 */ + 0x00, /* C3 */ + 0x00, /* C4 */ + 0x00, /* C5 */ + 0x00, /* C6 */ + 0x00, /* C7 */ + 0x00, /* C8 */ + 0x00, /* C9 */ + 0x00, /* CA */ + 0x00, /* CB */ + 0x00, /* CC */ + 0x00, /* CD */ + 0x00, /* CE */ + 0x00, /* CF */ + 0x00, /* D0 */ + 0x00, /* D1 */ + 0x00, /* D2 */ + 0x00, /* D3 */ + 0x00, /* D4 */ + 0x00, /* D5 */ + 0x00, /* D6 */ + 0x00, /* D7 */ + 0x00, /* D8 */ + 0x00, /* D9 */ + 0x00, /* DA */ + 0x00, /* DB */ + 0x00, /* DC */ + 0x00, /* DD */ + 0x00, /* DE */ + 0x00, /* DF */ + 0x00, /* E0 */ + 0x00, /* E1 */ + 0x00, /* E2 */ + 0x00, /* E3 */ + 0x00, /* E4 */ + 0x00, /* E5 */ + 0x00, /* E6 */ + 0x00, /* E7 */ + 0x00, /* E8 */ + 0x00, /* E9 */ + 0x00, /* EA */ + 0x00, /* EB */ + 0x00, /* EC */ + 0x00, /* ED */ + 0x00, /* EE */ + 0x00, /* EF */ + 0x00, /* F0 */ + 0x00, /* F1 */ + 0x00, /* F2 */ + 0x00, /* F3 */ + 0x00, /* F4 */ + 0x00, /* F5 */ + 0x00, /* F6 */ + 0x00, /* F7 */ + 0x00, /* F8 */ + 0x00, /* F9 */ + 0x00, /* FA */ + 0x00, /* FB */ + 0x00, /* FC */ + 0x00, /* FD */ + 0x00, /* FE */ + 0x00, /* FF */ +}; + +static struct { + int readable; + int writable; +} max98090_access[M98090_REG_CNT] = { + { 0x00, 0x80 }, /* 00 */ + { 0xFF, 0x00 }, /* 01 */ + { 0xFF, 0x00 }, /* 02 */ + { 0xFF, 0xE7 }, /* 03 */ + { 0xFF, 0xFD }, /* 04 */ + { 0xFF, 0x3F }, /* 05 */ + { 0xFF, 0x3F }, /* 06 */ + { 0xFF, 0xF0 }, /* 07 */ + { 0xFF, 0xFE }, /* 08 */ + { 0xFF, 0xF8 }, /* 09 */ + { 0xFF, 0xFF }, /* 0A */ + { 0xFF, 0xFF }, /* 0B */ + { 0xFF, 0xF0 }, /* 0C */ + { 0xFF, 0xFF }, /* 0D */ + { 0xFF, 0xFF }, /* 0E */ + { 0xFF, 0xD3 }, /* 0F */ + { 0xFF, 0x7F }, /* 10 */ + { 0xFF, 0x7F }, /* 11 */ + { 0xFF, 0x00 }, /* 12 */ + { 0xFF, 0x00 }, /* 13 */ + { 0xFF, 0x00 }, /* 14 */ + { 0xFF, 0xFF }, /* 15 */ + { 0xFF, 0xFF }, /* 16 */ + { 0xFF, 0x3F }, /* 17 */ + { 0xFF, 0x3F }, /* 18 */ + { 0xFF, 0x0F }, /* 19 */ + { 0xFF, 0xDF }, /* 1A */ + { 0xFF, 0x30 }, /* 1B */ + { 0xFF, 0xF1 }, /* 1C */ + { 0xFF, 0x7F }, /* 1D */ + { 0xFF, 0xFF }, /* 1E */ + { 0xFF, 0xFF }, /* 1F */ + { 0xFF, 0xFF }, /* 20 */ + { 0xFF, 0x87 }, /* 21 */ + { 0xFF, 0x3F }, /* 22 */ + { 0xFF, 0x03 }, /* 23 */ + { 0xFF, 0xFF }, /* 24 */ + { 0xFF, 0x3F }, /* 25 */ + { 0xFF, 0xF0 }, /* 26 */ + { 0xFF, 0xBF }, /* 27 */ + { 0xFF, 0x1F }, /* 28 */ + { 0xFF, 0x3F }, /* 29 */ + { 0xFF, 0x3F }, /* 2A */ + { 0xFF, 0x3F }, /* 2B */ + { 0xFF, 0x9F }, /* 2C */ + { 0xFF, 0x9F }, /* 2D */ + { 0xFF, 0x3F }, /* 2E */ + { 0xFF, 0x3F }, /* 2F */ + { 0xFF, 0x0F }, /* 30 */ + { 0xFF, 0xBF }, /* 31 */ + { 0xFF, 0xBF }, /* 32 */ + { 0xFF, 0xF7 }, /* 33 */ + { 0xFF, 0xFF }, /* 34 */ + { 0xFF, 0xFF }, /* 35 */ + { 0xFF, 0x1F }, /* 36 */ + { 0xFF, 0x3F }, /* 37 */ + { 0xFF, 0x03 }, /* 38 */ + { 0xFF, 0x9F }, /* 39 */ + { 0xFF, 0xBF }, /* 3A */ + { 0xFF, 0x03 }, /* 3B */ + { 0xFF, 0x9F }, /* 3C */ + { 0xFF, 0xC3 }, /* 3D */ + { 0xFF, 0x1F }, /* 3E */ + { 0xFF, 0xFF }, /* 3F */ + { 0xFF, 0x07 }, /* 40 */ + { 0xFF, 0x0F }, /* 41 */ + { 0xFF, 0x01 }, /* 42 */ + { 0xFF, 0x03 }, /* 43 */ + { 0xFF, 0x05 }, /* 44 */ + { 0xFF, 0x80 }, /* 45 */ + { 0x00, 0x00 }, /* 46 */ + { 0x00, 0x00 }, /* 47 */ + { 0x00, 0x00 }, /* 48 */ + { 0x00, 0x00 }, /* 49 */ + { 0x00, 0x00 }, /* 4A */ + { 0x00, 0x00 }, /* 4B */ + { 0x00, 0x00 }, /* 4C */ + { 0x00, 0x00 }, /* 4D */ + { 0x00, 0x00 }, /* 4E */ + { 0x00, 0x00 }, /* 4F */ + { 0x00, 0x00 }, /* 50 */ + { 0x00, 0x00 }, /* 51 */ + { 0x00, 0x00 }, /* 52 */ + { 0x00, 0x00 }, /* 53 */ + { 0x00, 0x00 }, /* 54 */ + { 0x00, 0x00 }, /* 55 */ + { 0x00, 0x00 }, /* 56 */ + { 0x00, 0x00 }, /* 57 */ + { 0x00, 0x00 }, /* 58 */ + { 0x00, 0x00 }, /* 59 */ + { 0x00, 0x00 }, /* 5A */ + { 0x00, 0x00 }, /* 5B */ + { 0x00, 0x00 }, /* 5C */ + { 0x00, 0x00 }, /* 5D */ + { 0x00, 0x00 }, /* 5E */ + { 0x00, 0x00 }, /* 5F */ + { 0x00, 0x00 }, /* 60 */ + { 0x00, 0x00 }, /* 61 */ + { 0x00, 0x00 }, /* 62 */ + { 0x00, 0x00 }, /* 63 */ + { 0x00, 0x00 }, /* 64 */ + { 0x00, 0x00 }, /* 65 */ + { 0x00, 0x00 }, /* 66 */ + { 0x00, 0x00 }, /* 67 */ + { 0x00, 0x00 }, /* 68 */ + { 0x00, 0x00 }, /* 69 */ + { 0x00, 0x00 }, /* 6A */ + { 0x00, 0x00 }, /* 6B */ + { 0x00, 0x00 }, /* 6C */ + { 0x00, 0x00 }, /* 6D */ + { 0x00, 0x00 }, /* 6E */ + { 0x00, 0x00 }, /* 6F */ + { 0x00, 0x00 }, /* 70 */ + { 0x00, 0x00 }, /* 71 */ + { 0x00, 0x00 }, /* 72 */ + { 0x00, 0x00 }, /* 73 */ + { 0x00, 0x00 }, /* 74 */ + { 0x00, 0x00 }, /* 75 */ + { 0x00, 0x00 }, /* 76 */ + { 0x00, 0x00 }, /* 77 */ + { 0x00, 0x00 }, /* 78 */ + { 0x00, 0x00 }, /* 79 */ + { 0x00, 0x00 }, /* 7A */ + { 0x00, 0x00 }, /* 7B */ + { 0x00, 0x00 }, /* 7C */ + { 0x00, 0x00 }, /* 7D */ + { 0x00, 0x00 }, /* 7E */ + { 0x00, 0x00 }, /* 7F */ + { 0x00, 0x00 }, /* 80 */ + { 0x00, 0x00 }, /* 81 */ + { 0x00, 0x00 }, /* 82 */ + { 0x00, 0x00 }, /* 83 */ + { 0x00, 0x00 }, /* 84 */ + { 0x00, 0x00 }, /* 85 */ + { 0x00, 0x00 }, /* 86 */ + { 0x00, 0x00 }, /* 87 */ + { 0x00, 0x00 }, /* 88 */ + { 0x00, 0x00 }, /* 89 */ + { 0x00, 0x00 }, /* 8A */ + { 0x00, 0x00 }, /* 8B */ + { 0x00, 0x00 }, /* 8C */ + { 0x00, 0x00 }, /* 8D */ + { 0x00, 0x00 }, /* 8E */ + { 0x00, 0x00 }, /* 8F */ + { 0x00, 0x00 }, /* 90 */ + { 0x00, 0x00 }, /* 91 */ + { 0x00, 0x00 }, /* 92 */ + { 0x00, 0x00 }, /* 93 */ + { 0x00, 0x00 }, /* 94 */ + { 0x00, 0x00 }, /* 95 */ + { 0x00, 0x00 }, /* 96 */ + { 0x00, 0x00 }, /* 97 */ + { 0x00, 0x00 }, /* 98 */ + { 0x00, 0x00 }, /* 99 */ + { 0x00, 0x00 }, /* 9A */ + { 0x00, 0x00 }, /* 9B */ + { 0x00, 0x00 }, /* 9C */ + { 0x00, 0x00 }, /* 9D */ + { 0x00, 0x00 }, /* 9E */ + { 0x00, 0x00 }, /* 9F */ + { 0x00, 0x00 }, /* A0 */ + { 0x00, 0x00 }, /* A1 */ + { 0x00, 0x00 }, /* A2 */ + { 0x00, 0x00 }, /* A3 */ + { 0x00, 0x00 }, /* A4 */ + { 0x00, 0x00 }, /* A5 */ + { 0x00, 0x00 }, /* A6 */ + { 0x00, 0x00 }, /* A7 */ + { 0x00, 0x00 }, /* A8 */ + { 0x00, 0x00 }, /* A9 */ + { 0x00, 0x00 }, /* AA */ + { 0x00, 0x00 }, /* AB */ + { 0x00, 0x00 }, /* AC */ + { 0x00, 0x00 }, /* AD */ + { 0x00, 0x00 }, /* AE */ + { 0x00, 0x00 }, /* AF */ + { 0x00, 0x00 }, /* B0 */ + { 0x00, 0x00 }, /* B1 */ + { 0x00, 0x00 }, /* B2 */ + { 0x00, 0x00 }, /* B3 */ + { 0x00, 0x00 }, /* B4 */ + { 0x00, 0x00 }, /* B5 */ + { 0x00, 0x00 }, /* B6 */ + { 0x00, 0x00 }, /* B7 */ + { 0x00, 0x00 }, /* B8 */ + { 0x00, 0x00 }, /* B9 */ + { 0x00, 0x00 }, /* BA */ + { 0x00, 0x00 }, /* BB */ + { 0x00, 0x00 }, /* BC */ + { 0x00, 0x00 }, /* BD */ + { 0x00, 0x00 }, /* BE */ + { 0x00, 0x00 }, /* BF */ + { 0x00, 0x00 }, /* C0 */ + { 0x00, 0x00 }, /* C1 */ + { 0x00, 0x00 }, /* C2 */ + { 0x00, 0x00 }, /* C3 */ + { 0x00, 0x00 }, /* C4 */ + { 0x00, 0x00 }, /* C5 */ + { 0x00, 0x00 }, /* C6 */ + { 0x00, 0x00 }, /* C7 */ + { 0x00, 0x00 }, /* C8 */ + { 0x00, 0x00 }, /* C9 */ + { 0x00, 0x00 }, /* CA */ + { 0x00, 0x00 }, /* CB */ + { 0x00, 0x00 }, /* CC */ + { 0x00, 0x00 }, /* CD */ + { 0x00, 0x00 }, /* CE */ + { 0x00, 0x00 }, /* CF */ + { 0x00, 0x00 }, /* D0 */ + { 0x00, 0x00 }, /* D1 */ + { 0x00, 0x00 }, /* D2 */ + { 0x00, 0x00 }, /* D3 */ + { 0x00, 0x00 }, /* D4 */ + { 0x00, 0x00 }, /* D5 */ + { 0x00, 0x00 }, /* D6 */ + { 0x00, 0x00 }, /* D7 */ + { 0x00, 0x00 }, /* D8 */ + { 0x00, 0x00 }, /* D9 */ + { 0x00, 0x00 }, /* DA */ + { 0x00, 0x00 }, /* DB */ + { 0x00, 0x00 }, /* DC */ + { 0x00, 0x00 }, /* DD */ + { 0x00, 0x00 }, /* DE */ + { 0x00, 0x00 }, /* DF */ + { 0x00, 0x00 }, /* E0 */ + { 0x00, 0x00 }, /* E1 */ + { 0x00, 0x00 }, /* E2 */ + { 0x00, 0x00 }, /* E3 */ + { 0x00, 0x00 }, /* E4 */ + { 0x00, 0x00 }, /* E5 */ + { 0x00, 0x00 }, /* E6 */ + { 0x00, 0x00 }, /* E7 */ + { 0x00, 0x00 }, /* E8 */ + { 0x00, 0x00 }, /* E9 */ + { 0x00, 0x00 }, /* EA */ + { 0x00, 0x00 }, /* EB */ + { 0x00, 0x00 }, /* EC */ + { 0x00, 0x00 }, /* ED */ + { 0x00, 0x00 }, /* EE */ + { 0x00, 0x00 }, /* EF */ + { 0x00, 0x00 }, /* F0 */ + { 0x00, 0x00 }, /* F1 */ + { 0x00, 0x00 }, /* F2 */ + { 0x00, 0x00 }, /* F3 */ + { 0x00, 0x00 }, /* F4 */ + { 0x00, 0x00 }, /* F5 */ + { 0x00, 0x00 }, /* F6 */ + { 0x00, 0x00 }, /* F7 */ + { 0x00, 0x00 }, /* F8 */ + { 0x00, 0x00 }, /* F9 */ + { 0x00, 0x00 }, /* FA */ + { 0x00, 0x00 }, /* FB */ + { 0x00, 0x00 }, /* FC */ + { 0x00, 0x00 }, /* FD */ + { 0x00, 0x00 }, /* FE */ + { 0xFF, 0x00 }, /* FF */ +}; + +struct max98090_priv { + struct snd_soc_codec *codec; + void *control_data; + struct max98090_pdata *pdata; + unsigned int sysclk; + struct max98090_cdata dai; + const char **eq_texts; + const char **bq_texts; + struct soc_enum eq_enum; + struct soc_enum bq_enum; + int eq_textcnt; + int bq_textcnt; + u8 lin_state; + unsigned int mic1pre; + unsigned int mic2pre; + + struct delayed_work work; + enum playback_path cur_path; + enum record_path rec_path; +}; + +static int max98090_readable(struct snd_soc_codec *codec, unsigned int reg) +{ + if (reg >= M98090_REG_CNT) + return 0; + return max98090_access[reg].readable != 0; +} + +static int max98090_volatile(struct snd_soc_codec *codec, unsigned int reg) +{ + if (reg > M98090_REG_MAX_CACHED) + return 1; + + switch (reg) { + case M98090_001_INT_STS: + case M98090_002_JACK_STS: + return 1; + } + + return 0; +} + +inline unsigned int max98090_i2c_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(codec->control_data, (u8)(reg & 0xFF)); + + if (ret < 0) + printk("DEBUG -> %s error!!! [%d]\n",__FUNCTION__,__LINE__); + return ret; +} + +/* + * write max98090 register cache + */ +static inline void max98090_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + /* Reset register and reserved registers are uncached */ + if (reg == 0 || reg > ARRAY_SIZE(max98090_reg_def) - 1) + return; + + cache[reg] = value; +} + +/* + * write to the max98090 register space + */ +static int max98090_i2c_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + max98090_write_reg_cache (codec, reg, value); + + if (i2c_smbus_write_byte_data(codec->control_data, + (u8)(reg & 0xFF), (u8)(value & 0xFF)) < 0) { + printk("%s error!!! [%d]\n",__FUNCTION__,__LINE__); + return -EIO; + } + + return 0; +} + +static const char *playback_path[] = { + "OFF", + "RCV", + "SPK", + "HP", + "SPK_HP", + "TV_OUT", +}; + +static const char *record_path[] = { + "Main Mic", + "Headset Mic", +}; + +static const struct soc_enum path_control_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(playback_path), playback_path), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(record_path), record_path), +}; + +#if defined(CONFIG_MAX98090_HEADSET) +enum { + JACK_NO_DEVICE = 0, + HEADSET_4_POLE_DEVICE = (1 << 0), + HEADSET_3_POLE_DEVICE = (1 << 1), + TVOUT_DEVICE = (1 << 2), + UNKNOWN_DEVICE = (1 << 3), +}; + +enum { + JACK_DETACHED = 0, + JACK_ATTACHED = 1, +}; + +static unsigned int current_jack_type_status = UNKNOWN_DEVICE; +#endif + +static int max98090_set_playback_path(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); + + int path_num = ucontrol->value.integer.value[0]; + + max98090->cur_path = path_num; + + printk(" %s [%d] param %d\n",__FUNCTION__,__LINE__, path_num); + + switch(path_num) { + case 0 : // headset + case 1 : // earpiece +#if 0 // FIXME: + max98090_set_playback_headset(codec); +#endif + max98090_set_playback_speaker_headset(codec); + break; + case 2 : // speaker +#if 0 // FIXME: + max98090_set_playback_speaker(codec); +#endif + max98090_set_playback_speaker_headset(codec); + break; + case 3 : + max98090_set_playback_speaker_headset(codec); + break; + case 4 : + break; + default : + break; + } +#if 0 // FIXME: + max98090_set_record_main_mic(codec); +#endif + return 0; +} + +static int max98090_get_playback_path(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); + + printk(" %s [%d] current playback path %d\n",__FUNCTION__,__LINE__, max98090->cur_path); + + ucontrol->value.integer.value[0] = max98090->cur_path; + + return 0; +} + +static int max98090_set_record_path(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); + + int path_num = ucontrol->value.integer.value[0]; + + max98090->rec_path = path_num; + printk(" %s [%d] param %d\n",__FUNCTION__,__LINE__, path_num); + + switch(path_num) { + case 0 : + max98090_set_record_main_mic(codec); + break; + case 1 : + max98090_set_record_main_mic(codec); + break; + case 2 : + max98090_set_record_main_mic(codec); + break; + default : + break; + } + + return 0; +} + +static int max98090_get_record_path(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); + + printk(" %s [%d] current record path %d\n",__FUNCTION__,__LINE__, max98090->rec_path); + + ucontrol->value.integer.value[0] = max98090->rec_path; + + return 0; +} + +static const struct snd_kcontrol_new max98090_snd_controls[] = { + /* Path Control */ + SOC_ENUM_EXT("Playback Path", path_control_enum[0], + max98090_get_playback_path, max98090_set_playback_path), + + SOC_ENUM_EXT("MIC Path", path_control_enum[1], + max98090_get_record_path, max98090_set_record_path), +}; + +static int max98090_add_widgets(struct snd_soc_codec *codec) +{ + int ret; + + ret = snd_soc_add_codec_controls(codec, max98090_snd_controls, + ARRAY_SIZE(max98090_snd_controls)); + + if (ret != 0) + printk("max98090_add_widgets: %d\n", ret); + + return 0; +} + +#if defined(CONFIG_MAX98090_HEADSET) +#define MAX98090_DELAY msecs_to_jiffies(1000) + +static void max98090_work(struct work_struct *work) +{ + struct max98090_priv *max98090 = container_of(work, struct max98090_priv, work.work); + struct snd_soc_codec *codec= max98090->codec; + int jack_auto_sts=0; + int read_value=0; + +#if 0 // FIXME: + jack_auto_sts = snd_soc_read(codec, M98090_002_JACK_STS); + jack_man_sts = snd_soc_read(codec, M98090_008_JACK_MANUAL_STS); + printk("CKKIM -> %s[%d] : jack_auto_sts = 0x%02x \n\n",__func__,__LINE__, jack_auto_sts); + if(jack_man_sts&0x18){ + read_value = HEADSET_3_POLE_DEVICE; + } + else if(jack_auto_sts&0x08){ + read_value = HEADSET_4_POLE_DEVICE; + } + else { + read_value = JACK_NO_DEVICE; + } + printk(" %s [%d] current_jack_type_status %d\n", + __FUNCTION__, __LINE__, current_jack_type_status); +#endif + + if (read_value != current_jack_type_status) { + current_jack_type_status=read_value; +#if 0 // FIXME: + switch_set_state(&switch_jack_detection, + current_jack_type_status); + mdelay(500); +#endif + switch(current_jack_type_status) + { + case HEADSET_3_POLE_DEVICE : + case HEADSET_4_POLE_DEVICE : + max98090_disable_playback_path(codec, SPK); + max98090_set_playback_speaker_headset(codec); + max98090->cur_path = HP; + break; + case JACK_NO_DEVICE : + max98090_disable_playback_path(codec, HP); + max98090_set_playback_speaker_headset(codec); + max98090->cur_path = SPK; + break; + default : + max98090_disable_playback_path(codec, SPK); + max98090_set_playback_speaker_headset(codec); + max98090->cur_path = HP; + break; + } + schedule_delayed_work(&max98090->work, msecs_to_jiffies(2000)); + } + else schedule_delayed_work(&max98090->work, msecs_to_jiffies(1000)); + +} + +void odroid_audio_tvout(bool tvout) +{ + return; +} +#endif + +/* codec mclk clock divider coefficients */ +static const struct { + u32 rate; + u8 sr; +} rate_table[] = { + {8000, M98090_QS_SR_8K}, + {16000, M98090_QS_SR_16K}, + {32000, M98090_QS_SR_32K}, + {44100, M98090_QS_SR_44K1}, + {48000, M98090_QS_SR_48K}, + {96000, M98090_QS_SR_96K}, +}; + +static int rate_value(int rate, u8 *value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rate_table); i++) { + if (rate_table[i].rate >= rate) { + *value = rate_table[i].sr; + return 0; + } + } + *value = rate_table[0].sr; + return -EINVAL; +} + +static int max98090_dai1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); + struct max98090_cdata *cdata; + unsigned long long ni; + unsigned int rate; + u8 regval; + + cdata = &max98090->dai; + printk("### max98090_dai1_hw_params###\n"); + snd_soc_write(codec, M98090_006_IF_QS, M98090_QS_DAI_I2S_SLV); + snd_soc_write(codec, M98090_005_SAMPLERATE_QS, M98090_QS_SR_44K1); + snd_soc_write(codec, M98090_007_DAC_PATH_QS, 0x80); + +#if 0 // FIXME: + rate = params_rate(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, M98090_022_DAI_IF_FORMAT, + M98090_DAI_WS, 0); + break; + case SNDRV_PCM_FORMAT_S24_LE: + snd_soc_update_bits(codec, M98090_022_DAI_IF_FORMAT, + M98090_DAI_WS, 1); + break; + default: + return -EINVAL; + } + + if (rate_value(rate, ®val)) + return -EINVAL; + + snd_soc_update_bits(codec, M98090_005_SAMPLERATE_QS, + M98090_QS_SR_MASK, M98090_QS_SR_44K1); + + cdata->rate = rate; + + /* Configure NI when operating as master */ + if (snd_soc_read(codec, M98090_021_MASTER_MODE_CLK) & M98090_DAI_MAS) { + if (max98090->sysclk == 0) { + dev_err(codec->dev, "Invalid system clock frequency\n"); + return -EINVAL; + } + ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL) + * (unsigned long long int)rate; + do_div(ni, (unsigned long long int)max98090->sysclk); + snd_soc_write(codec, M98090_01D_ANYCLK_CNTL1, + (ni >> 8) & 0x7F); + snd_soc_write(codec, M98090_01E_ANYCLK_CNTL2, + ni & 0xFF); + } + + /* Update sample rate mode */ + if (rate < 50000) + snd_soc_update_bits(codec, M98090_026_FILTER_CONFIG, + M98090_FILTER_DHF, 0); + else + snd_soc_update_bits(codec, M98090_026_FILTER_CONFIG, + M98090_FILTER_DHF, M98090_FILTER_DHF); + max98090_set_playback_speaker_headset(codec); +#endif + + return 0; +} + +static int max98090_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); + + /* Requested clock frequency is already setup */ + if (freq == max98090->sysclk) + return 0; + + max98090->sysclk = freq; /* remember current sysclk */ + +#if 0 // FIXME: + /* Setup clocks for slave mode, and using the PLL + * PSCLK = 0x01 (when master clk is 10MHz to 20MHz) + * 0x02 (when master clk is 20MHz to 40MHz).. + * 0x03 (when master clk is 40MHz to 60MHz).. + */ + if ((freq >= 10000000) && (freq < 20000000)) { + snd_soc_write(codec, M98090_01B_SYS_CLK, 0x10); + } else if ((freq >= 20000000) && (freq < 40000000)) { + snd_soc_write(codec, M98090_01B_SYS_CLK, 0x20); + } else if ((freq >= 40000000) && (freq < 60000000)) { + snd_soc_write(codec, M98090_01B_SYS_CLK, 0x30); + } else { + dev_err(codec->dev, "Invalid master clock frequency\n"); + return -EINVAL; + } +#endif + snd_soc_write(codec, M98090_004_SYS_CLK_QS, M98090_QS_MCLK_11P2896M); + dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); + + return 0; +} + +static int max98090_dai1_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); + struct max98090_cdata *cdata; + u8 regval = 0; + u8 master = 0; + + cdata = &max98090->dai; + + if (fmt != cdata->fmt) { + cdata->fmt = fmt; +#if 0 // FIXME: + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Slave mode PLL */ + snd_soc_write(codec, M98090_01D_ANYCLK_CNTL1, + 0x60); + snd_soc_write(codec, M98090_01E_ANYCLK_CNTL2, + 0x00); + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Set to master mode */ + master = M98090_DAI_MAS; + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(codec->dev, "Clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + regval |= M98090_DAI_DLY; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + regval |= M98090_DAI_WCI; + break; + case SND_SOC_DAIFMT_IB_NF: + regval |= M98090_DAI_BCI; + break; + case SND_SOC_DAIFMT_IB_IF: + regval |= M98090_DAI_BCI | M98090_DAI_WCI; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, M98090_021_MASTER_MODE_CLK, master); + + snd_soc_update_bits(codec, M98090_022_DAI_IF_FORMAT, + M98090_DAI_DLY | M98090_DAI_BCI | + M98090_DAI_RJ | M98090_DAI_WCI, regval); +#endif + + snd_soc_write(codec, M98090_006_IF_QS, M98090_QS_DAI_I2S_SLV); + } + + return 0; +} + +static int max98090_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; +#if 0 // FIXME: + case SND_SOC_BIAS_STANDBY: + snd_soc_update_bits(codec, M98090_03E_IPUT_ENABLE, + M98090_MBEN, M98090_MBEN); + break; + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, M98090_03E_IPUT_ENABLE, + M98090_MBEN, 0); + codec->cache_sync = 1; +#endif + break; + } + codec->dapm.bias_level = level; + return 0; +} + +#define MAX98090_RATES SNDRV_PCM_RATE_8000_96000 +#define MAX98090_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops max98090_dai1_ops = { + .set_sysclk = max98090_dai_set_sysclk, + .set_fmt = max98090_dai1_set_fmt, + .hw_params = max98090_dai1_hw_params, +}; + +struct snd_soc_dai_driver max98090_dai[] = { + { + .name = "max98090-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98090_RATES, + .formats = MAX98090_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98090_RATES, + .formats = MAX98090_FORMATS, + }, + .ops = &max98090_dai1_ops, + }, +}; +EXPORT_SYMBOL_GPL(max98090_dai); + +#ifdef CONFIG_PM +static int max98090_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + max98090_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int max98090_resume(struct snd_soc_codec *codec) +{ + max98090_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} +#else +#define max98090_suspend NULL +#define max98090_resume NULL +#endif + +static int max98090_reset(struct snd_soc_codec *codec) +{ + int i, ret; + + ret = snd_soc_write(codec, M98090_045_PWR_SYS, 0); + if (ret < 0) { + dev_err(codec->dev, "Failed to reset codec: %d\n", ret); + return ret; + } + + ret = snd_soc_write(codec, M98090_000_SW_RESET, M98090_SWRST); + if (ret < 0) { + dev_err(codec->dev, "Failed to reset codec: %d\n", ret); + return ret; + } + + for (i = M98090_003_INT_MASK; i < M98090_REG_MAX_CACHED; i++) { + ret = snd_soc_write(codec, i, max98090_reg_def[i]); + if (ret < 0) { + dev_err(codec->dev, "Failed to reset: %d\n", ret); + return ret; + } + } + + return ret; +} + +static int max98090_probe(struct snd_soc_codec *codec) +{ + struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); + struct max98090_cdata *cdata; + int ret = 0; + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + codec->control_data = max98090->control_data; + codec->cache_sync = 1; + codec->write = max98090_i2c_write; + codec->read = max98090_i2c_read; + max98090->codec = codec; + + /* reset the codec, the DSP core, and disable all interrupts */ + max98090_reset(codec); + + /* initialize private data */ + max98090->sysclk = (unsigned)-1; + max98090->eq_textcnt = 0; + max98090->bq_textcnt = 0; + + cdata = &max98090->dai; + cdata->rate = (unsigned)-1; + cdata->fmt = (unsigned)-1; + cdata->eq_sel = 0; + cdata->bq_sel = 0; + + max98090->lin_state = 0; + max98090->mic1pre = 0; + max98090->mic2pre = 0; + + ret = snd_soc_read(codec, M98090_0FF_REV_ID); + if (ret < 0) { + dev_err(codec->dev, "Failed to read device revision: %d\n", + ret); + goto err_access; + } + dev_info(codec->dev, "revision 0x%02x\n", ret); + + snd_soc_write(codec, M98090_045_PWR_SYS, M98090_SHDNRUN); + + /* initialize registers cache to hardware default */ + max98090_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + snd_soc_write(codec, M98090_042_BIAS_CNTL, 0xF0); + snd_soc_write(codec, M98090_025_DAI_IOCFG, 0x07); + + snd_soc_write(codec, M98090_03D_JACK_DETECT_CONFIG, 0x80); + snd_soc_update_bits(codec, M98090_003_INT_MASK, (1<<2), (1<<2)); + +#if 0 // FIXME: + snd_soc_write(codec, M98090_014_JACK_INT_EN, 0x99); +#endif + max98090_set_record_main_mic(codec); +#if 0 // FIXME: + max98090_set_playback_speaker_headset(codec); +#endif + snd_soc_update_bits(codec, M98090_045_PWR_SYS, + M98090_SHDNRUN, M98090_SHDNRUN); + + max98090_add_widgets(codec); + +#if defined(CONFIG_MAX98090_HEADSET) +#if 0 // FIXME: + switch_dev_register(&switch_jack_detection); +#endif + + INIT_DELAYED_WORK_DEFERRABLE(&max98090->work, max98090_work); + schedule_delayed_work(&max98090->work, msecs_to_jiffies(6000)); +#endif + +err_access: + return ret; +} + +static int max98090_remove(struct snd_soc_codec *codec) +{ + max98090_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +struct snd_soc_codec_driver soc_codec_dev_max98090 = { + .probe = max98090_probe, + .remove = max98090_remove, + .suspend = max98090_suspend, + .resume = max98090_resume, + + .reg_cache_size = ARRAY_SIZE(max98090_reg_def), + .reg_word_size = sizeof(u8), + .reg_cache_default = max98090_reg_def, + .readable_register = max98090_readable, + .volatile_register = max98090_volatile, +}; + +static int max98090_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max98090_priv *max98090; + int ret; + + max98090 = devm_kzalloc(&i2c->dev, sizeof(struct max98090_priv), + GFP_KERNEL); + if (max98090 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max98090); + + max98090->control_data = i2c; + max98090->pdata = i2c->dev.platform_data; + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_max98090, &max98090_dai[0], 1); + if (ret < 0) { + kfree(max98090); + } + + return ret; +} + +static int __devexit max98090_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + +#if defined(CONFIG_MAX98090_HEADSET) +#if 0 // FIXME: + switch_dev_unregister(&switch_jack_detection); +#endif +#endif + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id max98090_i2c_id[] = { + { "max98090", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98090_i2c_id); + +static struct i2c_driver max98090_i2c_driver = { + .driver = { + .name = "max98090", + .owner = THIS_MODULE, + }, + .probe = max98090_i2c_probe, + .remove = __devexit_p(max98090_i2c_remove), + .id_table = max98090_i2c_id, +}; + +static int __init max98090_init(void) +{ + int ret; + + ret = i2c_add_driver(&max98090_i2c_driver); + if (ret) + pr_err("Failed to register max98090 I2C driver: %d\n", ret); + + return ret; +} +module_init(max98090_init); + +static void __exit max98090_exit(void) +{ + i2c_del_driver(&max98090_i2c_driver); +} +module_exit(max98090_exit); + +MODULE_DESCRIPTION("ALSA SoC MAX98090 driver"); +MODULE_AUTHOR("Peter Hsiang"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98090.h b/sound/soc/codecs/max98090.h new file mode 100644 index 00000000000000..d5d5dc9d42510e --- /dev/null +++ b/sound/soc/codecs/max98090.h @@ -0,0 +1,309 @@ +/* + * max98090.h -- MAX98090 ALSA SoC Audio driver + * + * Copyright 2011 Maxim Integrated Products + * + * 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 _MAX98090_H +#define _MAX98090_H + +/* + * MAX98090 Registers Definition + */ +#define M98090_000_SW_RESET 0x00 + +// Status/Interrupt +#define M98090_001_INT_STS 0x01 +#define M98090_002_JACK_STS 0x02 +#define M98090_003_INT_MASK 0x03 + +// Quick Setup +#define M98090_004_SYS_CLK_QS 0x04 +#define M98090_005_SAMPLERATE_QS 0x05 +#define M98090_006_IF_QS 0x06 +#define M98090_007_DAC_PATH_QS 0x07 +#define M98090_008_MIC_TO_ADC_QS 0x08 +#define M98090_009_LINE_TO_QS 0x09 +#define M98090_00A_ANALOG_MIC_LOOP_QS 0x0A +#define M98090_00B_ANALOG_LINE_LOOP_QS 0x0B + +// Digital Microphone +#define M98090_00C_DIGITAL_MIC 0x0C + +// Input Configuration +#define M98090_00D_INPUT_CONFIG 0x0D +#define M98090_00E_LINEIN_LVL 0x0E +#define M98090_00F_LINIIN_CONFIG 0x0F +#define M98090_010_MIC1_LVL 0x10 +#define M98090_011_MIC2_LVL 0x11 +#define M98090_012_MIC_BIAS_VOL 0x12 + +#define M98090_013_RESERVED 0x13 +#define M98090_014_RESERVED 0x14 + +// ADC Path +#define M98090_015_MIX_ADC_L 0x15 +#define M98090_016_MIX_ADC_R 0x16 +#define M98090_017_ADC_L_LVL 0x17 +#define M98090_018_ADC_R_LVL 0x18 +#define M98090_019_ADC_BIQUAD_LVL 0x19 +#define M98090_01A_SIDETONE 0x1A + +// Clock control +#define M98090_01B_SYS_CLK 0x1B +#define M98090_01C_CLK_MODE 0x1C +#define M98090_01D_ANYCLK_CNTL1 0x1D +#define M98090_01E_ANYCLK_CNTL2 0x1E +#define M98090_01F_ANYCLK_CNTL3 0x1F +#define M98090_020_ANYCLK_CNTL4 0x20 +#define M98090_021_MASTER_MODE_CLK 0x21 + +// Interface control +#define M98090_022_DAI_IF_FORMAT 0x22 +#define M98090_023_DAI_TDM_FORMAT1 0x23 +#define M98090_024_DAI_TDM_FORMAT2 0x24 +#define M98090_025_DAI_IOCFG 0x25 +#define M98090_026_FILTER_CONFIG 0x26 +#define M98090_027_DAI_PLAYBACK_LVL1 0x27 +#define M98090_028_DAI_PLAYBACK_LVL2 0x28 + +// Headphone Configuration +#define M98090_029_MIX_HP_L 0x29 +#define M98090_02A_MIX_HP_R 0x2A +#define M98090_02B_MIX_HP_CNTL 0x2B +#define M98090_02C_HP_L_VOL 0x2C +#define M98090_02D_HP_R_VOL 0x2D + +// Speaker Configuration +#define M98090_02E_MIX_SPK_L 0x2E +#define M98090_02F_MIX_SPK_R 0x2F +#define M98090_030_MIX_SPK_CNTL 0x30 +#define M98090_031_SPK_L_VOL 0x31 +#define M98090_032_SPK_R_VOL 0x32 + +// ALC Configuration +#define M98090_033_ALC_TIMING 0x33 +#define M98090_034_ALC_COMPRESSOR 0x34 +#define M98090_035_ALC_EXPANDER 0x35 +#define M98090_036_ALC_GAIN 0x36 + +// LINE_OUTPUT +#define M98090_037_LOUT_L_MIX 0x37 +#define M98090_038_LOUT_L_CNTL 0x38 +#define M98090_039_LOUT_L_VOL 0x39 +#define M98090_03A_LOUT_R_MIX 0x3A +#define M98090_03B_LOUT_R_CNTL 0x3B +#define M98090_03C_LOUT_R_VOL 0x3C + +// Enable +#define M98090_03D_JACK_DETECT_CONFIG 0x3D +#define M98090_03E_IPUT_ENABLE 0x3E +#define M98090_03F_OUTPUT_ENABLE 0x3F +#define M98090_040_LVL_CNTL 0x40 +#define M98090_041_FILTER_ENABLE 0x41 + +// Power +#define M98090_042_BIAS_CNTL 0x42 +#define M98090_043_DAC_POWER 0x43 +#define M98090_044_ADC_POWER 0x44 +#define M98090_045_PWR_SYS 0x45 + +#define M98090_0FF_REV_ID 0xFF + +#define M98090_REG_CNT (0xFF + 1) +#define M98090_REG_MAX_CACHED 0x45 + +/* MAX98090 Registers Bit Fields */ + +/* M98090_000_SW_RESET */ +#define M98090_SWRST (1 << 7) + +/* M98090_004_SYS_CLK_QS */ +#define M98090_QS_MCLK_26M (1 << 7) +#define M98090_QS_MCLK_19P2M (1 << 6) +#define M98090_QS_MCLK_13M (1 << 5) +#define M98090_QS_MCLK_12P288M (1 << 4) +#define M98090_QS_MCLK_12M (1 << 3) +#define M98090_QS_MCLK_11P2896M (1 << 2) +#define M98090_QS_MCLK_256FS (1 << 0) +#define M98090_QS_MCLK_MASK 0xFD + +/* M98090_005_SAMPLERATE_QS */ +#define M98090_QS_SR_96K (1 << 5) +#define M98090_QS_SR_32K (1 << 4) +#define M98090_QS_SR_48K (1 << 3) +#define M98090_QS_SR_44K1 (1 << 2) +#define M98090_QS_SR_16K (1 << 1) +#define M98090_QS_SR_8K (1 << 0) +#define M98090_QS_SR_MASK 0x3F + +/* M98090_006_IF_QS */ +#define M98090_QS_DAI_RJ_MAS (1 << 5) +#define M98090_QS_DAI_RJ_SLV (1 << 4) +#define M98090_QS_DAI_LJ_MAS (1 << 3) +#define M98090_QS_DAI_LJ_SLV (1 << 2) +#define M98090_QS_DAI_I2S_MAS (1 << 1) +#define M98090_QS_DAI_I2S_SLV (1 << 0) +#define M98090_QS_DAI_MASK 0x3F + + +/* M98090_015_MIX_ADC_L */ +#define M98090_IN12_TO_BYPASSL (1 << 7) +#define M98090_MIC2_TO_ADCL (1 << 6) +#define M98090_MIC1_TO_ADCL (1 << 5) +#define M98090_LINE2_TO_ADCL (1 << 4) +#define M98090_LINE1_TO_ADCL (1 << 3) +#define M98090_IN56_TO_ADCL (1 << 2) +#define M98090_IN34_TO_ADCL (1 << 1) +#define M98090_IN12_TO_ADCL (1 << 0) + +/* M98090_016_MIX_ADC_R */ +#define M98090_IN12_TO_BYPASSR (1 << 7) +#define M98090_MIC2_TO_ADCR (1 << 6) +#define M98090_MIC1_TO_ADCR (1 << 5) +#define M98090_LINE2_TO_ADCR (1 << 4) +#define M98090_LINE1_TO_ADCR (1 << 3) +#define M98090_IN56_TO_ADCR (1 << 2) +#define M98090_IN34_TO_ADCR (1 << 1) +#define M98090_IN12_TO_ADCR (1 << 0) + +/* M98090_007_DAC_PATH_QS */ +/* M98090_008_MIC_TO_ADC_QS */ +/* M98090_009_LINE_TO_QS */ +/* M98090_00A_ANALOG_MIC_LOOP_QS */ +/* M98090_00B_ANALOG_LINE_LOOP_QS */ + +/* M98090_021_MASTER_MODE_CLK */ +#define M98090_DAI_MAS (1 << 7) + +/* M98090_022_DAI_IF_FORMAT */ +#define M98090_DAI_RJ (1 << 2) +#define M98090_DAI_WCI (1 << 6) +#define M98090_DAI_BCI (1 << 5) +#define M98090_DAI_DLY (1 << 4) +#define M98090_DAI_WS (1 << 0) + +/* M98090_023_DAI_TDM_FORMAT1 */ + +/* M98090_024_DAI_TDM_FORMAT2 */ + +/* M98090_026_FILTER_CONFIG */ +#define M98090_FILTER_MODE (1 << 7) +#define M98090_FILTER_AHPF (1 << 6) +#define M98090_FILTER_DHPF (1 << 5) +#define M98090_FILTER_DHF (1 << 4) + +/* M98090_029_MIX_HP_L */ +#define M98090_MIC2_TO_HPL (1 << 5) +#define M98090_MIC1_TO_HPL (1 << 4) +#define M98090_IN2_TO_HPL (1 << 3) +#define M98090_IN1_TO_HPL (1 << 2) +#define M98090_DACR_TO_HPL (1 << 1) +#define M98090_DACL_TO_HPL (1 << 0) + +/* M98090_02A_MIX_HP_R */ +#define M98090_MIC2_TO_HPR (1 << 5) +#define M98090_MIC1_TO_HPR (1 << 4) +#define M98090_IN2_TO_HPR (1 << 3) +#define M98090_IN1_TO_HPR (1 << 2) +#define M98090_DACR_TO_HPR (1 << 1) +#define M98090_DACL_TO_HPR (1 << 0) + +/* M98090_02B_MIX_HP_CNTL */ +#define M98090_HPNORMAL (3 << 4) + +/* M98090_02E_MIX_SPK_L */ +#define M98090_MIC2_TO_SPKL (1 << 5) +#define M98090_MIC1_TO_SPKL (1 << 4) +#define M98090_IN2_TO_SPKL (1 << 3) +#define M98090_IN1_TO_SPKL (1 << 2) +#define M98090_DACR_TO_SPKL (1 << 1) +#define M98090_DACL_TO_SPKL (1 << 0) + +/* M98090_02F_MIX_SPK_R */ +#define M98090_MIC2_TO_SPKR (1 << 5) +#define M98090_MIC1_TO_SPKR (1 << 4) +#define M98090_IN2_TO_SPKR (1 << 3) +#define M98090_IN1_TO_SPKR (1 << 2) +#define M98090_DACR_TO_SPKR (1 << 1) +#define M98090_DACL_TO_SPKR (1 << 0) + +/* M98090_03E_IPUT_ENABLE */ +#define M98090_MBEN (1 << 4) +#define M98090_LINEAEN (1 << 3) +#define M98090_LINEBEN (1 << 2) +#define M98090_ADREN (1 << 1) +#define M98090_ADLEN (1 << 0) + +/* M98090_03F_OUTPUT_ENABLE */ +#define M98090_HPREN (1 << 7) +#define M98090_HPLEN (1 << 6) +#define M98090_SPREN (1 << 5) +#define M98090_SPLEN (1 << 4) +#define M98090_RCVLEN (1 << 3) +#define M98090_RCVREN (1 << 2) +#define M98090_DAREN (1 << 1) +#define M98090_DALEN (1 << 0) + +/* M98090_045_PWR_SYS */ +#define M98090_SHDNRUN (1 << 7) + +#define M98090_COEFS_PER_BAND 5 + +#define M98090_BYTE1(w) ((w >> 8) & 0xff) +#define M98090_BYTE0(w) (w & 0xff) + +/* Equalizer filter coefficients */ +#define M98090_110_DAI1_EQ_BASE 0x10 +#define M98090_142_DAI2_EQ_BASE 0x42 + +/* Biquad filter coefficients */ +#define M98090_174_DAI1_BQ_BASE 0x74 +#define M98090_17E_DAI2_BQ_BASE 0x7E + +enum playback_path { + OFF, + REV, + SPK, + HP, + SPK_HP, + TV_OUT +}; + +enum record_path { + MAIN, + EAR, + MIC_OFF +}; + +struct max98090_cdata { + unsigned int rate; + unsigned int fmt; + int eq_sel; + int bq_sel; +}; + +#define MAX98090_NONE 0 +#define MAX98090_SPK 1 +#define MAX98090_HP 2 + +extern struct snd_soc_codec *max98090_codec; + +void max98090_set_playback_speaker(struct snd_soc_codec *codec); +void max98090_set_playback_headset(struct snd_soc_codec *codec); +void max98090_set_playback_earpiece(struct snd_soc_codec *codec); +void max98090_set_playback_speaker_headset(struct snd_soc_codec *codec); +void max98090_disable_playback_path(struct snd_soc_codec *codec, + enum playback_path path); + +void max98090_set_record_main_mic(struct snd_soc_codec *codec); +void max98090_set_record_headset_mic(struct snd_soc_codec *codec); + +void max98090_disable_record_path(struct snd_soc_codec *codec, + enum record_path rec_path); + +#endif diff --git a/sound/soc/codecs/max98090_mixer.c b/sound/soc/codecs/max98090_mixer.c new file mode 100644 index 00000000000000..570fcfa74709d1 --- /dev/null +++ b/sound/soc/codecs/max98090_mixer.c @@ -0,0 +1,168 @@ +/* + * max98090.c -- MAX98090 ALSA SoC Audio driver + * + * Copyright 2010 Maxim Integrated Products + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "max98090.h" + +//---------------------------------------------------------------------------------------- +// audio gain parameter +//---------------------------------------------------------------------------------------- +#define TUNNING_DAI1_VOL 0x00 // 0dB +#define TUNNING_DAI1_RCV_VOL 0x00 // 0dB + +#define TUNNING_SPKMIX_VOL 0x00 // 0dB +#define TUNNING_HPMIX_VOL 0x00 // 0dB + +#define TUNNING_RCV_VOL 0x1F // +8dB +#define TUNNING_SPK_VOL 0x2C // 0dB +#define TUNNING_HP_VOL 0x1A // 0dB + +#define MIC_PRE_AMP_GAIN (3<<5) // +30dB +#define MIC_PGA_GAIN (0x00) // +20dB +#define TUNNING_MIC_PGA_GAIN 0x40 + +#define TUNNING_MIC_BIAS_VOL 0x00 // 2.2V + +#define MIC_ADC_GAIN (3<<4) // +18dB +#define MIC_ADC_VOLUME (0x03) // 0dB +#define TUNNING_ADC_GAIN 0x30 + +//---------------------------------------------------------------------------------------- +// playback function +//---------------------------------------------------------------------------------------- +void max98090_set_playback_speaker(struct snd_soc_codec *codec) +{ + printk("\t[MAX98090] %s(%d)\n",__FUNCTION__,__LINE__); + return; +} + +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +void max98090_set_playback_headset(struct snd_soc_codec *codec) +{ + printk("\t[MAX98090] %s(%d)\n",__FUNCTION__,__LINE__); + return; +} + +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +void max98090_set_playback_earpiece(struct snd_soc_codec *codec) +{ + + printk("\t[MAX98090] %s(%d)\n",__FUNCTION__,__LINE__); + return; +} + +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +void max98090_set_playback_speaker_headset(struct snd_soc_codec *codec) +{ + int reg; + + // 0x92 Power DAC L/R, SPLEN + reg = snd_soc_read(codec, M98090_03F_OUTPUT_ENABLE); + reg &= ~(M98090_DAREN|M98090_DALEN|M98090_RCVREN|M98090_RCVLEN|M98090_SPREN|M98090_SPLEN|M98090_HPREN|M98090_HPLEN); + reg |= (M98090_DAREN|M98090_DALEN|M98090_HPREN|M98090_HPLEN); + snd_soc_write(codec, M98090_03F_OUTPUT_ENABLE, reg); + + // // Stereo DAC to SPK_L, SPK_R Mute + // reg = M98090_DACL_TO_SPKL; + // snd_soc_write(codec, M98090_02E_MIX_SPK_L, reg); + // reg = M98090_DACR_TO_SPKR; + // snd_soc_write(codec, M98090_02F_MIX_SPK_R, reg); + + // DAC L/R to HP L/R + reg = M98090_DACL_TO_HPL; + snd_soc_write(codec, M98090_029_MIX_HP_L, reg); + reg = M98090_DACR_TO_HPR; + snd_soc_write(codec, M98090_02A_MIX_HP_R, reg); + + // reg = TUNNING_SPK_VOL; + // snd_soc_write(codec, M98090_031_SPK_L_VOL, reg); + // snd_soc_write(codec, M98090_032_SPK_R_VOL, reg); + // + // reg = TUNNING_SPKMIX_VOL; + // snd_soc_write(codec, M98090_030_MIX_SPK_CNTL, reg); + + // HPMIX L/R to HP_AMP L/R + // reg = M98090_HPNORMAL; + reg = TUNNING_HPMIX_VOL; + snd_soc_write(codec, M98090_02B_MIX_HP_CNTL, reg); + + reg = TUNNING_HP_VOL; + snd_soc_write(codec, M98090_02C_HP_L_VOL, reg); + snd_soc_write(codec, M98090_02D_HP_R_VOL, reg); + + printk("\t[MAX98090] %s(%d)\n",__FUNCTION__,__LINE__); + return; +} + +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +void max98090_disable_playback_path(struct snd_soc_codec *codec, enum playback_path path) +{ + + return; +} + +//---------------------------------------------------------------------------------------- +// recording function +//---------------------------------------------------------------------------------------- +void max98090_set_record_main_mic(struct snd_soc_codec *codec) +{ + int reg; + + // 0x4C Mic bias, ADC L enable + reg = snd_soc_read(codec, M98090_03E_IPUT_ENABLE); + reg &= ~(M98090_ADLEN | M98090_ADREN | M98090_MBEN | M98090_LINEAEN | M98090_LINEBEN); + reg |= (M98090_ADLEN | M98090_ADREN | M98090_MBEN); + snd_soc_write(codec, M98090_03E_IPUT_ENABLE, reg); + + reg = TUNNING_MIC_BIAS_VOL; + snd_soc_write(codec, M98090_012_MIC_BIAS_VOL, reg); + + reg = M98090_MIC1_TO_ADCL; // MIC2 to ADC L/R Mixer + snd_soc_write(codec, M98090_015_MIX_ADC_L, reg); + snd_soc_write(codec, M98090_016_MIX_ADC_R, reg); + + reg = TUNNING_MIC_PGA_GAIN; + snd_soc_write(codec, M98090_010_MIC1_LVL, reg); + + reg = TUNNING_ADC_GAIN; + snd_soc_write(codec, M98090_017_ADC_L_LVL, reg); + snd_soc_write(codec, M98090_018_ADC_R_LVL, reg); + + printk("\t[MAX98090] %s(%d)\n",__FUNCTION__,__LINE__); + return; +} + +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +void max98090_set_record_headset_mic(struct snd_soc_codec *codec) +{ + printk("\t[MAX98090] %s(%d)\n",__FUNCTION__,__LINE__); + return; +} + +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +void max98090_disable_record_path(struct snd_soc_codec *codec, enum record_path rec_path) +{ + printk("\t[MAX98090] %s(%d)\n",__FUNCTION__,__LINE__); + return; +} diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index fe3995ce9b380f..f261586dcfb0cd 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -212,3 +212,15 @@ config SND_SOC_LITTLEMILL select SND_SAMSUNG_I2S select MFD_WM8994 select SND_SOC_WM8994 + +config SND_SOC_HKDK_MAX98090 + tristate "Audio support for MAX98090 on HKDK4412" + depends on SND_SOC_SAMSUNG && MACH_HKDK4412 + select SND_SOC_MAX98090 + select SND_SAMSUNG_I2S + +config MAX98090_HEADSET + bool "MAX98090 HeadSet detect" + depends on SND_SOC_HKDK_MAX98090 + help + Provides support for detecting max98090 jack_sw diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index 9d03beb40c8690..9c8913f13fa757 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -42,6 +42,7 @@ 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-hkdk-max98090-objs := hkdk_max98090.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 @@ -65,3 +66,4 @@ 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_HKDK_MAX98090) += snd-soc-hkdk-max98090.o \ No newline at end of file diff --git a/sound/soc/samsung/hkdk_max98090.c b/sound/soc/samsung/hkdk_max98090.c new file mode 100644 index 00000000000000..ec6a98e31a7675 --- /dev/null +++ b/sound/soc/samsung/hkdk_max98090.c @@ -0,0 +1,257 @@ +/* + * hkdk-uda1380.c -- ALSA Soc Audio Layer + * + * Copyright (c) 2012 CK Kim + * + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "i2s.h" +#include "s3c-i2s-v2.h" +#include "../codecs/max98090.h" + +static struct platform_device *odroid_snd_device; + +static int set_epll_rate(unsigned long rate) +{ + struct clk *fout_epll; + + fout_epll = clk_get(NULL, "fout_epll"); + if (IS_ERR(fout_epll)) { + printk(KERN_ERR "%s: failed to get fout_epll\n", __func__); + return PTR_ERR(fout_epll); + } + + if (rate == clk_get_rate(fout_epll)) + goto out; + + clk_set_rate(fout_epll, rate); +out: + clk_put(fout_epll); + + return 0; +} + +static int odroid_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 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_epll_rate(rclk * psr); + + 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(codec_dai, 0, rclk, SND_SOC_CLOCK_IN); + 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_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs); + if (ret < 0) + return ret; + return 0; +} + +static struct snd_soc_ops odroid_ops = { + .hw_params = odroid_hw_params, +}; + +static int max98090_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + snd_soc_dapm_sync(dapm); + return 0; +} + +static struct snd_soc_dai_driver voice_dai = { + .name = "samsung-i2s.0", + .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 struct snd_soc_dai_link odroid_dai[] = { + { /* Primary DAI i/f */ + .name = "MAX98090 AIF1", + .stream_name = "Playback", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "max98090-aif1", + .platform_name = "samsung-audio", + .codec_name = "max98090.1-0010", + .init = max98090_init, + .ops = &odroid_ops, + }, + { /* Sec_Fifo DAI i/f */ + .name = "MAX98090 AIF2", + .stream_name = "Capture", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "max98090-aif1", + .platform_name = "samsung-audio", + .codec_name = "max98090.1-0010", + .init = max98090_init, + .ops = &odroid_ops, + }, +}; + +static struct snd_soc_card snd_soc_hkdk_max98090 = { + .name = "hkdk-max98090", + .owner = THIS_MODULE, + .dai_link = odroid_dai, + .num_links = ARRAY_SIZE(odroid_dai), +}; + +static __devinit int hkdk_max98090_driver_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_hkdk_max98090; + int ret; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + return ret; + } + + return 0; +} + +static int __devexit hkdk_max98090_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +static struct platform_driver hkdk_max98090_driver = { + .driver = { + .name = "hkdk-snd-max89090", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, + .probe = hkdk_max98090_driver_probe, + .remove = __devexit_p(hkdk_max98090_driver_remove), +}; + +module_platform_driver(hkdk_max98090_driver); + +MODULE_DESCRIPTION("ALSA SoC ODROID max98090"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:hkdk-audio");