From c1403bcff9ed8f07e50809a24399df6e3868ce5a Mon Sep 17 00:00:00 2001 From: Peipeng Zhao Date: Fri, 12 May 2017 18:38:18 +0800 Subject: [PATCH] audio: add pcm186x, ssm3515, ssm3525 codec driver PD#145486: audio: add codec driver Change-Id: Ief27ff9892ffabba81f676f4bed2ec6b4ead74f5 Signed-off-by: Peipeng Zhao --- MAINTAINERS | 11 +- arch/arm64/configs/meson64_defconfig | 3 + drivers/amlogic/pinctrl/pinctrl_gxl.c | 10 +- sound/soc/amlogic/meson/meson.c | 36 +- sound/soc/codecs/amlogic/Kconfig | 35 + sound/soc/codecs/amlogic/Makefile | 8 +- sound/soc/codecs/amlogic/pcm186x-i2c.c | 80 ++ sound/soc/codecs/amlogic/pcm186x-spi.c | 79 ++ sound/soc/codecs/amlogic/pcm186x.c | 1358 ++++++++++++++++++++++++++++++++ sound/soc/codecs/amlogic/pcm186x.h | 265 +++++++ sound/soc/codecs/amlogic/ssm3515.c | 652 +++++++++++++++ sound/soc/codecs/amlogic/ssm3525.c | 602 ++++++++++++++ 12 files changed, 3126 insertions(+), 13 deletions(-) create mode 100644 sound/soc/codecs/amlogic/pcm186x-i2c.c create mode 100644 sound/soc/codecs/amlogic/pcm186x-spi.c create mode 100644 sound/soc/codecs/amlogic/pcm186x.c create mode 100644 sound/soc/codecs/amlogic/pcm186x.h create mode 100644 sound/soc/codecs/amlogic/ssm3515.c create mode 100644 sound/soc/codecs/amlogic/ssm3525.c diff --git a/MAINTAINERS b/MAINTAINERS index 46d24af..cf40807 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13929,4 +13929,13 @@ AMLOGIC Security Support M: Peifu Jiang F: include/linux/amlogic/meson-secure.h F: arch/arm/mach-meson/meson-smc.S -F: arch/arm/mach-meson/meson-secure.c \ No newline at end of file +F: arch/arm/mach-meson/meson-secure.c + +AMLOGIC Audio codec driver +M: Peipeng Zhao +F: sound/soc/codecs/amlogic/pcm186x-i2c.c +F: sound/soc/codecs/amlogic/pcm186x-spi.c +F: sound/soc/codecs/amlogic/pcm186x.c +F: sound/soc/codecs/amlogic/pcm186x.h +F: sound/soc/codecs/amlogic/ssm3515.c +F: sound/soc/codecs/amlogic/ssm3525.c diff --git a/arch/arm64/configs/meson64_defconfig b/arch/arm64/configs/meson64_defconfig index 758f651..ab66204 100644 --- a/arch/arm64/configs/meson64_defconfig +++ b/arch/arm64/configs/meson64_defconfig @@ -376,6 +376,9 @@ CONFIG_AMLOGIC_SND_CODEC_PDM_DUMMY_CODEC=y CONFIG_AMLOGIC_SND_CODEC_AMLT9015=y CONFIG_AMLOGIC_SND_SOC_TAS5707=y CONFIG_AMLOGIC_SND_SOC_TLV320ADC3101=y +CONFIG_AMLOGIC_SND_SOC_PCM186X=y +CONFIG_AMLOGIC_SND_SOC_SSM3515=y +CONFIG_AMLOGIC_SND_SOC_SSM3525=y CONFIG_AMLOGIC_SND_SOC=y CONFIG_AMLOGIC_SND_SOC_MESON=y CONFIG_AMLOGIC_SND_SOC_AUGE=y diff --git a/drivers/amlogic/pinctrl/pinctrl_gxl.c b/drivers/amlogic/pinctrl/pinctrl_gxl.c index 6dcf154..59deb54 100644 --- a/drivers/amlogic/pinctrl/pinctrl_gxl.c +++ b/drivers/amlogic/pinctrl/pinctrl_gxl.c @@ -304,6 +304,7 @@ static const unsigned int i2sout_ch45_pins[] = { PIN(GPIOZ_6, EE_OFF) }; static const unsigned int dvp_d4_pins[] = { PIN(GPIOZ_6, EE_OFF) }; +static const unsigned int i2sout_ch67_pins[] = { PIN(GPIOZ_7, EE_OFF) }; static const unsigned int eth_rxd3_pins[] = { PIN(GPIOZ_7, EE_OFF) }; static const unsigned int eth_rgmii_tx_clk_pins[] = { PIN(GPIOZ_8, EE_OFF) }; static const unsigned int eth_tx_en_pins[] = { PIN(GPIOZ_9, EE_OFF) }; @@ -551,6 +552,7 @@ static struct meson_pmx_group meson_gxl_periphs_groups[] = { GROUP(i2sout_ch45, 3, 25), /*z6*/ GROUP(dvp_d4, 3, 11), /*z6*/ + GROUP(i2sout_ch67, 3, 24), /*z7*/ GROUP(spi_sclk_0, 4, 4),/*z11*/ GROUP(spi_miso_0, 4, 3),/*z12*/ @@ -859,10 +861,15 @@ static const char * const i2c_d_groups[] = { }; static const char * const i2s_groups[] = { - "i2s_am_clk", "i2s_ao_clk_out", "i2s_lr_clk_out", "i2sout_ch01", + "i2s_am_clk", "i2s_ao_clk_out", "i2s_lr_clk_out", + "i2sout_ch01", "i2sout_ch23_z5", "i2sout_ch45", "i2sout_ch67", "i2sin_ch23", "i2sin_ch45", "i2sin_ch67", }; +static const char * const pdm_groups[] = { + "pdm_in", "pdm_clk", +}; + static const char * const sdio_groups[] = { "sdio_d0", "sdio_d1", "sdio_d2", "sdio_d3", "sdio_clk", "sdio_cmd", }; @@ -886,6 +893,7 @@ static struct meson_pmx_func meson_gxl_periphs_functions[] = { FUNCTION(spdif_in), FUNCTION(spdif_in_1), FUNCTION(i2s), + FUNCTION(pdm), FUNCTION(spi), FUNCTION(i2c_a), FUNCTION(i2c_b), diff --git a/sound/soc/amlogic/meson/meson.c b/sound/soc/amlogic/meson/meson.c index 11d46ca..e8c977b 100644 --- a/sound/soc/amlogic/meson/meson.c +++ b/sound/soc/amlogic/meson/meson.c @@ -574,16 +574,32 @@ static int aml_card_dai_parse_of(struct device *dev, if (ret < 0) goto parse_error; - /* get codec dai->name */ - ret = snd_soc_of_get_dai_name(codec_node, &dai_link->codec_dai_name); - if (ret < 0) - goto parse_error; - - dai_link->name = dai_link->stream_name = dai_link->cpu_dai_name; - dai_link->codec_of_node = of_parse_phandle(codec_node, "sound-dai", 0); - dai_link->platform_of_node = plat_node; - dai_link->init = init; - + ret = of_count_phandle_with_args(codec_node, "sound-dai", + "#sound-dai-cells"); + pr_info("%s codec_node count:%d\n", __func__, ret); + if (ret <= 1) { + ret = snd_soc_of_get_dai_name(codec_node, + &dai_link->codec_dai_name); + if (ret < 0) + goto parse_error; + + dai_link->name = dai_link->stream_name = + dai_link->cpu_dai_name; + dai_link->codec_of_node = + of_parse_phandle(codec_node, "sound-dai", 0); + dai_link->platform_of_node = plat_node; + dai_link->init = init; + } else { + ret = snd_soc_of_get_dai_link_codecs(dev, codec_node, dai_link); + if (ret < 0) { + pr_err("failed get_dai_link_codecs from codec_node\n"); + goto parse_error; + } + dai_link->init = init; + dai_link->platform_of_node = plat_node; + /*"multicodec";*/ + dai_link->name = dai_link->stream_name = dai_link->cpu_dai_name; + } return 0; parse_error: diff --git a/sound/soc/codecs/amlogic/Kconfig b/sound/soc/codecs/amlogic/Kconfig index 7273c7f..4dcd663 100644 --- a/sound/soc/codecs/amlogic/Kconfig +++ b/sound/soc/codecs/amlogic/Kconfig @@ -78,5 +78,40 @@ config AMLOGIC_SND_SOC_TLV320ADC3101 help Enable Support for Texas INstruments TLV320ADC3101 CODEC. Select this if your TLV320ADC3101 is connected via an I2C bus. + Enable Support for Texas INstruments TLV320ADC3101 CODEC. + Select this if your TLV320ADC3101 is connected via an I2C bus. + +config AMLOGIC_SND_SOC_PCM186X + bool "Texas Instruments PCM186X " + depends on AMLOGIC_SND_SOC_CODECS + depends on I2C + default n + help + Enable support for Texas Instruments PCM186X CODEC. + Select this if your PCM186X is connected via an I2C bus. + Enable support for Texas Instruments PCM186X CODEC. + Select this if your PCM186X is connected via an I2C bus. + +config AMLOGIC_SND_SOC_SSM3525 + bool "Analog Devices SSM3525 " + depends on AMLOGIC_SND_SOC_CODECS + depends on I2C + default n + help + Enable support for SSM3525 CODEC. + Select this if your SSM3525 is connected via an I2C bus. + Enable support for SSM3525 CODEC. + Select this if your SSM3525 is connected via an I2C bus. + +config AMLOGIC_SND_SOC_SSM3515 + bool "Analog Devices SSM3515 " + depends on AMLOGIC_SND_SOC_CODECS + depends on I2C + default n + help + Enable support for SSM3515 CODEC. + Select this if SSM3515 is connected via an I2C bus. + Enable support for SSM3515 CODEC. + Select this if SSM3515 is connected via an I2C bus. #endif #AMLOGIC_SND_SOC_CODECS diff --git a/sound/soc/codecs/amlogic/Makefile b/sound/soc/codecs/amlogic/Makefile index d38d347..9c3fc357 100644 --- a/sound/soc/codecs/amlogic/Makefile +++ b/sound/soc/codecs/amlogic/Makefile @@ -8,6 +8,9 @@ snd-soc-pmu3-objs := aml_pmu3.o #Third part codecs snd-soc-tas5707-objs := tas5707.o snd-soc-tlv320adc3101-objs := tlv320adc3101.o +snd-soc-pcm186x-objs := pcm186x.o pcm186x-i2c.o pcm186x-spi.o +snd-soc-ssm3515-objs := ssm3515.o +snd-soc-ssm3525-objs := ssm3525.o # Amlogic obj-$(CONFIG_AMLOGIC_SND_CODEC_DUMMY_CODEC) += snd-soc-dummy_codec.o @@ -18,4 +21,7 @@ obj-$(CONFIG_AMLOGIC_SND_CODEC_PMU3) += snd-soc-pmu3.o #Third part codecs obj-$(CONFIG_AMLOGIC_SND_SOC_TAS5707) += snd-soc-tas5707.o -obj-$(CONFIG_AMLOGIC_SND_SOC_TLV320ADC3101) += snd-soc-tlv320adc3101.o \ No newline at end of file +obj-$(CONFIG_AMLOGIC_SND_SOC_TLV320ADC3101) += snd-soc-tlv320adc3101.o +obj-$(CONFIG_AMLOGIC_SND_SOC_PCM186X) += snd-soc-pcm186x.o +obj-$(CONFIG_AMLOGIC_SND_SOC_SSM3515) += snd-soc-ssm3515.o +obj-$(CONFIG_AMLOGIC_SND_SOC_SSM3525) += snd-soc-ssm3525.o diff --git a/sound/soc/codecs/amlogic/pcm186x-i2c.c b/sound/soc/codecs/amlogic/pcm186x-i2c.c new file mode 100644 index 0000000..84f0f92 --- /dev/null +++ b/sound/soc/codecs/amlogic/pcm186x-i2c.c @@ -0,0 +1,80 @@ +/* + * pcm186x.c - Texas Instruments PCM186x Universal Audio ADC - I2C + * + * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg + * + * 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 +#include +#include + +#include "pcm186x.h" + +static int pcm186x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + const enum pcm186x_type type = (enum pcm186x_type)id->driver_data; + int irq = i2c->irq; + struct regmap *regmap; + + dev_info(&i2c->dev, "%s() i2c->addr=%d\n", __func__, i2c->addr); + + regmap = devm_regmap_init_i2c(i2c, &pcm186x_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm186x_probe(&i2c->dev, type, irq, regmap); +} + +static int pcm186x_i2c_remove(struct i2c_client *i2c) +{ + pcm186x_remove(&i2c->dev); + return 0; +} + +static const struct i2c_device_id pcm186x_i2c_id[] = { + { " pcm1862", PCM1862 }, + { " pcm1863", PCM1863 }, + { " pcm1864", PCM1864 }, + { " pcm1865", PCM1865 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcm186x_i2c_id); + +static const struct of_device_id pcm186x_of_match[] = { + { .compatible = "ti, pcm1862", }, + { .compatible = "ti, pcm1863", }, + { .compatible = "ti, pcm1864", }, + { .compatible = "ti, pcm1865", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm186x_of_match); + +static struct i2c_driver pcm186x_i2c_driver = { + .probe = pcm186x_i2c_probe, + .remove = pcm186x_i2c_remove, + .id_table = pcm186x_i2c_id, + .driver = { + .name = "pcm186x", + .of_match_table = pcm186x_of_match, + .pm = &pcm186x_pm_ops, + }, +}; + +module_i2c_driver(pcm186x_i2c_driver); + +MODULE_DESCRIPTION("PCM186x Universal Audio ADC driver - I2C"); +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/codecs/amlogic/pcm186x-spi.c b/sound/soc/codecs/amlogic/pcm186x-spi.c new file mode 100644 index 0000000..fd52797 --- /dev/null +++ b/sound/soc/codecs/amlogic/pcm186x-spi.c @@ -0,0 +1,79 @@ +/* + * pcm186x.c - Texas Instruments PCM186x Universal Audio ADC - SPI + * + * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg + * + * 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 +#include +#include + +#include "pcm186x.h" + +static int pcm186x_spi_probe(struct spi_device *spi) +{ + const enum pcm186x_type type = + (enum pcm186x_type)spi_get_device_id(spi)->driver_data; + int irq = spi->irq; + struct regmap *regmap; + + dev_info(&spi->dev, "%s()\n", __func__); + + regmap = devm_regmap_init_spi(spi, &pcm186x_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm186x_probe(&spi->dev, type, irq, regmap); +} + +static int pcm186x_spi_remove(struct spi_device *spi) +{ + pcm186x_remove(&spi->dev); + return 0; +} + +static const struct spi_device_id pcm186x_spi_id[] = { + { " pcm1862", PCM1862 }, + { " pcm1863", PCM1863 }, + { " pcm1864", PCM1864 }, + { " pcm1865", PCM1865 }, + { } +}; +MODULE_DEVICE_TABLE(spi, pcm186x_spi_id); + +static const struct of_device_id pcm186x_of_match[] = { + { .compatible = "ti, pcm1862", }, + { .compatible = "ti, pcm1863", }, + { .compatible = "ti, pcm1864", }, + { .compatible = "ti, pcm1865", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm186x_of_match); + +static struct spi_driver pcm186x_spi_driver = { + .probe = pcm186x_spi_probe, + .remove = pcm186x_spi_remove, + .id_table = pcm186x_spi_id, + .driver = { + .name = "pcm186x", + .of_match_table = pcm186x_of_match, + .pm = &pcm186x_pm_ops, + }, +}; + +module_spi_driver(pcm186x_spi_driver); + +MODULE_DESCRIPTION("PCM186x Universal Audio ADC driver - SPI"); +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/amlogic/pcm186x.c b/sound/soc/codecs/amlogic/pcm186x.c new file mode 100644 index 0000000..ad9e99d --- /dev/null +++ b/sound/soc/codecs/amlogic/pcm186x.c @@ -0,0 +1,1358 @@ +/* + * pcm186x.c - Texas Instruments PCM186x Universal Audio ADC + * + * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcm186x.h" + +#define PCM186X_MAX_NR_BUSY_CHECKS 16 + +static const char * const pcm186x_supply_names[] = { + "avdd", /* Analog power supply. Connect to 3.3-V supply. */ + "dvdd", /* Digital power supply. Connect to 3.3-V supply. */ + "iovdd" /* I/O power supply. Connect to 3.3-V or 1.8-V. */ +}; +#define PCM186x_NUM_SUPPLIES ARRAY_SIZE(pcm186x_supply_names) + +struct pcm186x_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[PCM186x_NUM_SUPPLIES]; + enum pcm186x_type type; + int irq; + unsigned int sysclk; + unsigned int pllclk; /* 0 if PLL is not used */ + unsigned int dai_format; + unsigned int tdm_offset; + unsigned int slots_mask; +}; + +/* + * The PCM186x DSP register map has holes in it, hence we define a table so we + * can process all locations elements in an iterative manner. + */ +static const u8 pcm186x_dsp_registers[] = { + PCM186X_DSP_MIX1_CH1L, + PCM186X_DSP_MIX1_CH1R, + PCM186X_DSP_MIX1_CH2L, + PCM186X_DSP_MIX1_CH2R, + PCM186X_DSP_MIX1_I2SL, + PCM186X_DSP_MIX1_I2SR, + PCM186X_DSP_MIX2_CH1L, + PCM186X_DSP_MIX2_CH1R, + PCM186X_DSP_MIX2_CH2L, + PCM186X_DSP_MIX2_CH2R, + PCM186X_DSP_MIX2_I2SL, + PCM186X_DSP_MIX2_I2SR, + PCM186X_DSP_MIX3_CH1L, + PCM186X_DSP_MIX3_CH1R, + PCM186X_DSP_MIX3_CH2L, + PCM186X_DSP_MIX3_CH2R, + PCM186X_DSP_MIX3_I2SL, + PCM186X_DSP_MIX3_I2SR, + PCM186X_DSP_MIX4_CH1L, + PCM186X_DSP_MIX4_CH1R, + PCM186X_DSP_MIX4_CH2L, + PCM186X_DSP_MIX4_CH2R, + PCM186X_DSP_MIX4_I2SL, + PCM186X_DSP_MIX4_I2SR, + PCM186X_DSP_LPF_B0, + PCM186X_DSP_LPF_B1, + PCM186X_DSP_LPF_B2, + PCM186X_DSP_LPF_A1, + PCM186X_DSP_LPF_A2, + PCM186X_DSP_HPF_B0, + PCM186X_DSP_HPF_B1, + PCM186X_DSP_HPF_B2, + PCM186X_DSP_HPF_A1, + PCM186X_DSP_HPF_A2, + PCM186X_DSP_LOSS_THRESH, + PCM186X_DSP_RES_THRESH, +}; + +#define PCM186X_DSP_NR_REGISTERS ARRAY_SIZE(pcm186x_dsp_registers) + +static int pcm186x_dsp_access_enable(struct device *dev, struct regmap *map) +{ + + int i, ret; + unsigned int val; + + dev_dbg(dev, "%s()\n", __func__); + + /* + * Switch page using the datasheet-recommended special flow for + * accessing the memory-mapped DSP registers. + */ + for (i = 0; i < PCM186X_MAX_NR_BUSY_CHECKS; i++) { + ret = regmap_write(map, PCM186X_PAGE, PCM186X_PAGE_1); + if (ret) { + dev_err(dev, "Error writing device: %d\n", ret); + return ret; + } + + /* + * Writing the page register at least twice before start + * checking the MMAP status register for it to become zero. + */ + if (i < 1) + continue; + ret = regmap_read(map, PCM186X_MMAP_STAT_CTRL, &val); + if (ret) { + dev_err(dev, "Error reading device: %d\n", ret); + return ret; + } + if (!val) + return 0; + } + + /* + * One reason for encountering a timeout error can be that the PCM186x + * DSPs are not being clocked because the driving clock source has been + * turned off which can happen in a number of different operating + * scenarios. + */ + dev_err(dev, "Timeout accessing DSP memory\n"); + + return -EBUSY; + return 0; +} + +static int pcm186x_dsp_coefficients_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct device *dev = codec->dev; + struct pcm186x_priv *priv = snd_soc_codec_get_drvdata(codec); + struct regmap *map = priv->regmap; + int i, j, ret; + unsigned int val; + + dev_dbg(dev, "%s()\n", __func__); + + ret = pcm186x_dsp_access_enable(dev, map); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(pcm186x_dsp_registers); i++) { + /* Set virtual address */ + ret = regmap_write(map, PCM186X_MMAP_ADDRESS, + pcm186x_dsp_registers[i]); + if (ret) { + dev_err(dev, "Error writing device: %d\n", ret); + return ret; + } + /* Issue read request */ + ret = regmap_write(map, PCM186X_MMAP_STAT_CTRL, + PCM186X_MMAP_STAT_R_REQ); + if (ret) { + dev_err(dev, "Error writing device: %d\n", ret); + return ret; + } + + /* Wait for the DSP register read to finish but not forever */ + for (j = 0; j < PCM186X_MAX_NR_BUSY_CHECKS; j++) { + ret = regmap_read(map, PCM186X_MMAP_STAT_CTRL, &val); + if (ret) { + dev_err(dev, "Error reading device: %d\n", ret); + return ret; + } + if (!val) + break; + } + + if (j == PCM186X_MAX_NR_BUSY_CHECKS) { + dev_err(dev, "Timeout reading DSP coefficients\n"); + return -EBUSY; + } + + /* Get, assemble, and populate return data - 24 bits each */ + ret = regmap_read(map, PCM186X_MEM_RDATA0, &val); + if (ret) { + dev_err(dev, "Error reading device: %d\n", ret); + return ret; + } + ucontrol->value.integer.value[i] = val; + + ret = regmap_read(map, PCM186X_MEM_RDATA1, &val); + if (ret) { + dev_err(dev, "Error reading device: %d\n", ret); + return ret; + } + ucontrol->value.integer.value[i] |= val << 8; + + ret = regmap_read(map, PCM186X_MEM_RDATA2, &val); + if (ret) { + dev_err(dev, "Error reading device: %d\n", ret); + return ret; + } + ucontrol->value.integer.value[i] |= val << 16; + } + return 0; +} + +static int pcm186x_dsp_coefficients_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct device *dev = codec->dev; + struct pcm186x_priv *priv = snd_soc_codec_get_drvdata(codec); + struct regmap *map = priv->regmap; + int i, j, ret; + unsigned int val; + + dev_dbg(dev, "%s()\n", __func__); + + ret = pcm186x_dsp_access_enable(dev, map); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(pcm186x_dsp_registers); i++) { + /* Set virtual address */ + ret = regmap_write(map, PCM186X_MMAP_ADDRESS, + pcm186x_dsp_registers[i]); + if (ret) { + dev_err(dev, "Error writing device: %d\n", ret); + return ret; + } + + /* Set data to write - 24 bits each */ + ret = regmap_write(map, PCM186X_MEM_WDATA0, + ucontrol->value.integer.value[i] & 0xff); + if (ret) { + dev_err(dev, "Error writing device: %d\n", ret); + return ret; + } + + ret = regmap_write(priv->regmap, PCM186X_MEM_WDATA1, + (ucontrol->value.integer.value[i] & + 0xff00) >> 8); + if (ret) { + dev_err(codec->dev, "Error writing device: %d\n", ret); + return ret; + } + + ret = regmap_write(map, PCM186X_MEM_WDATA2, + (ucontrol->value.integer.value[i] & + 0xff0000) >> 16); + if (ret) { + dev_err(dev, "Error writing device: %d\n", ret); + return ret; + } + + /* + * Issue write request. Note that DSP Internal memory can only + * be written to when the DSP is running, requesting a WREQ=1 + * will have no effect otherwise. This is of relevance if the + * codec is running as a clock slave, and the clocks stop. + */ + ret = regmap_write(map, PCM186X_MMAP_STAT_CTRL, + PCM186X_MMAP_STAT_W_REQ); + if (ret) { + dev_err(dev, "Error to write device: %d\n", ret); + return ret; + } + + /* Wait for the DSP register write to finish but not forever */ + for (j = 0; j < PCM186X_MAX_NR_BUSY_CHECKS; j++) { + ret = regmap_read(map, PCM186X_MMAP_STAT_CTRL, &val); + if (ret) { + dev_err(dev, "Error reading device: %d\n", ret); + return ret; + } + if (!val) + break; + } + + if (j == PCM186X_MAX_NR_BUSY_CHECKS) { + dev_err(dev, "Timeout writing DSP coefficients\n"); + return -EBUSY; + } + } + return 0; +} + +static const DECLARE_TLV_DB_SCALE(pcm186x_pga_tlv, -1200, 4000, 50); + +static const struct snd_kcontrol_new pcm1863_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("Analog Gain", PCM186X_PGA_VAL_CH1_L, + PCM186X_PGA_VAL_CH1_R, 0, -24, 80, 7, 0, + pcm186x_pga_tlv), + SND_SOC_BYTES_EXT("DSP Coefficients", PCM186X_DSP_NR_REGISTERS * 4, + pcm186x_dsp_coefficients_get, + pcm186x_dsp_coefficients_put), +}; + +static const struct snd_kcontrol_new pcm1865_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("ADC1 Analog Gain", PCM186X_PGA_VAL_CH1_L, + PCM186X_PGA_VAL_CH1_R, 0, -24, 80, 7, 0, + pcm186x_pga_tlv), + SOC_DOUBLE_R_S_TLV("ADC2 Analog Gain", PCM186X_PGA_VAL_CH2_L, + PCM186X_PGA_VAL_CH2_R, 0, -24, 80, 7, 0, + pcm186x_pga_tlv), + SND_SOC_BYTES_EXT("DSP Coefficients", PCM186X_DSP_NR_REGISTERS * 4, + pcm186x_dsp_coefficients_get, + pcm186x_dsp_coefficients_put), +}; + +const unsigned int pcm186x_adc_input_channel_sel_value[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x20, 0x30 +}; + +static const char * const pcm186x_adcl_input_channel_sel_text[] = { + "No Select", + "VINL1[SE]", /* Default for ADC1L */ + "VINL2[SE]", /* Default for ADC2L */ + "VINL2[SE] + VINL1[SE]", + "VINL3[SE]", + "VINL3[SE] + VINL1[SE]", + "VINL3[SE] + VINL2[SE]", + "VINL3[SE] + VINL2[SE] + VINL1[SE]", + "VINL4[SE]", + "VINL4[SE] + VINL1[SE]", + "VINL4[SE] + VINL2[SE]", + "VINL4[SE] + VINL2[SE] + VINL1[SE]", + "VINL4[SE] + VINL3[SE]", + "VINL4[SE] + VINL3[SE] + VINL1[SE]", + "VINL4[SE] + VINL3[SE] + VINL2[SE]", + "VINL4[SE] + VINL3[SE] + VINL2[SE] + VINL1[SE]", + "{VIN1P, VIN1M}[DIFF]", + "{VIN4P, VIN4M}[DIFF]", + "{VIN1P, VIN1M}[DIFF] + {VIN4P, VIN4M}[DIFF]" +}; + +static const char * const pcm186x_adcr_input_channel_sel_text[] = { + "No Select", + "VINR1[SE]", /* Default for ADC1R */ + "VINR2[SE]", /* Default for ADC2R */ + "VINR2[SE] + VINR1[SE]", + "VINR3[SE]", + "VINR3[SE] + VINR1[SE]", + "VINR3[SE] + VINR2[SE]", + "VINR3[SE] + VINR2[SE] + VINR1[SE]", + "VINR4[SE]", + "VINR4[SE] + VINR1[SE]", + "VINR4[SE] + VINR2[SE]", + "VINR4[SE] + VINR2[SE] + VINR1[SE]", + "VINR4[SE] + VINR3[SE]", + "VINR4[SE] + VINR3[SE] + VINR1[SE]", + "VINR4[SE] + VINR3[SE] + VINR2[SE]", + "VINR4[SE] + VINR3[SE] + VINR2[SE] + VINR1[SE]", + "{VIN2P, VIN2M}[DIFF]", + "{VIN3P, VIN3M}[DIFF]", + "{VIN2P, VIN2M}[DIFF] + {VIN3P, VIN3M}[DIFF]" +}; + +static const struct soc_enum pcm186x_adc_input_channel_sel[] = { + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_L, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcl_input_channel_sel_text), + pcm186x_adcl_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_R, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcr_input_channel_sel_text), + pcm186x_adcr_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC2_INPUT_SEL_L, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcl_input_channel_sel_text), + pcm186x_adcl_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC2_INPUT_SEL_R, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcr_input_channel_sel_text), + pcm186x_adcr_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), +}; + +static const struct snd_kcontrol_new pcm186x_adc_mux_controls[] = { + SOC_DAPM_ENUM("ADC1 Left Input", pcm186x_adc_input_channel_sel[0]), + SOC_DAPM_ENUM("ADC1 Right Input", pcm186x_adc_input_channel_sel[1]), + SOC_DAPM_ENUM("ADC2 Left Input", pcm186x_adc_input_channel_sel[2]), + SOC_DAPM_ENUM("ADC2 Right Input", pcm186x_adc_input_channel_sel[3]), +}; + +static const struct snd_soc_dapm_widget pcm1863_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("VINL1"), + SND_SOC_DAPM_INPUT("VINR1"), + SND_SOC_DAPM_INPUT("VINL2"), + SND_SOC_DAPM_INPUT("VINR2"), + SND_SOC_DAPM_INPUT("VINL3"), + SND_SOC_DAPM_INPUT("VINR3"), + SND_SOC_DAPM_INPUT("VINL4"), + SND_SOC_DAPM_INPUT("VINR4"), + + SND_SOC_DAPM_MUX("ADC Left Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[0]), + SND_SOC_DAPM_MUX("ADC Right Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[1]), + + /* + * Put the codec into SLEEP mode when not in use, allowing the + * Energysense mechanism to operate. + */ + SND_SOC_DAPM_ADC("ADC", "HiFi Capture", PCM186X_POWER_CTRL, 1, 0), +}; + +static const struct snd_soc_dapm_widget pcm1865_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("VINL1"), + SND_SOC_DAPM_INPUT("VINR1"), + SND_SOC_DAPM_INPUT("VINL2"), + SND_SOC_DAPM_INPUT("VINR2"), + SND_SOC_DAPM_INPUT("VINL3"), + SND_SOC_DAPM_INPUT("VINR3"), + SND_SOC_DAPM_INPUT("VINL4"), + SND_SOC_DAPM_INPUT("VINR4"), + + SND_SOC_DAPM_MUX("ADC1 Left Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[0]), + SND_SOC_DAPM_MUX("ADC1 Right Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[1]), + SND_SOC_DAPM_MUX("ADC2 Left Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[2]), + SND_SOC_DAPM_MUX("ADC2 Right Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[3]), + + /* + * Put the codec into SLEEP mode when not in use, allowing the + * Energysense mechanism to operate. + */ + SND_SOC_DAPM_ADC("ADC1", "HiFi Capture 1", PCM186X_POWER_CTRL, 1, 0), + SND_SOC_DAPM_ADC("ADC2", "HiFi Capture 2", PCM186X_POWER_CTRL, 1, 0), +}; + +static const struct snd_soc_dapm_route pcm1863_dapm_routes[] = { + { "ADC Left Capture Source", NULL, "VINL1" }, + { "ADC Left Capture Source", NULL, "VINR1" }, + { "ADC Left Capture Source", NULL, "VINL2" }, + { "ADC Left Capture Source", NULL, "VINR2" }, + { "ADC Left Capture Source", NULL, "VINL3" }, + { "ADC Left Capture Source", NULL, "VINR3" }, + { "ADC Left Capture Source", NULL, "VINL4" }, + { "ADC Left Capture Source", NULL, "VINR4" }, + + { "ADC", NULL, "ADC Left Capture Source" }, + + { "ADC Right Capture Source", NULL, "VINL1" }, + { "ADC Right Capture Source", NULL, "VINR1" }, + { "ADC Right Capture Source", NULL, "VINL2" }, + { "ADC Right Capture Source", NULL, "VINR2" }, + { "ADC Right Capture Source", NULL, "VINL3" }, + { "ADC Right Capture Source", NULL, "VINR3" }, + { "ADC Right Capture Source", NULL, "VINL4" }, + { "ADC Right Capture Source", NULL, "VINR4" }, + + { "ADC", NULL, "ADC Right Capture Source" }, +}; + +static const struct snd_soc_dapm_route pcm1865_dapm_routes[] = { + { "ADC1 Left Capture Source", NULL, "VINL1" }, + { "ADC1 Left Capture Source", NULL, "VINR1" }, + { "ADC1 Left Capture Source", NULL, "VINL2" }, + { "ADC1 Left Capture Source", NULL, "VINR2" }, + { "ADC1 Left Capture Source", NULL, "VINL3" }, + { "ADC1 Left Capture Source", NULL, "VINR3" }, + { "ADC1 Left Capture Source", NULL, "VINL4" }, + { "ADC1 Left Capture Source", NULL, "VINR4" }, + + { "ADC1", NULL, "ADC1 Left Capture Source" }, + + { "ADC1 Right Capture Source", NULL, "VINL1" }, + { "ADC1 Right Capture Source", NULL, "VINR1" }, + { "ADC1 Right Capture Source", NULL, "VINL2" }, + { "ADC1 Right Capture Source", NULL, "VINR2" }, + { "ADC1 Right Capture Source", NULL, "VINL3" }, + { "ADC1 Right Capture Source", NULL, "VINR3" }, + { "ADC1 Right Capture Source", NULL, "VINL4" }, + { "ADC1 Right Capture Source", NULL, "VINR4" }, + + { "ADC1", NULL, "ADC1 Right Capture Source" }, + + { "ADC2 Left Capture Source", NULL, "VINL1" }, + { "ADC2 Left Capture Source", NULL, "VINR1" }, + { "ADC2 Left Capture Source", NULL, "VINL2" }, + { "ADC2 Left Capture Source", NULL, "VINR2" }, + { "ADC2 Left Capture Source", NULL, "VINL3" }, + { "ADC2 Left Capture Source", NULL, "VINR3" }, + { "ADC2 Left Capture Source", NULL, "VINL4" }, + { "ADC2 Left Capture Source", NULL, "VINR4" }, + + { "ADC2", NULL, "ADC2 Left Capture Source" }, + + { "ADC2 Right Capture Source", NULL, "VINL1" }, + { "ADC2 Right Capture Source", NULL, "VINR1" }, + { "ADC2 Right Capture Source", NULL, "VINL2" }, + { "ADC2 Right Capture Source", NULL, "VINR2" }, + { "ADC2 Right Capture Source", NULL, "VINL3" }, + { "ADC2 Right Capture Source", NULL, "VINR3" }, + { "ADC2 Right Capture Source", NULL, "VINL4" }, + { "ADC2 Right Capture Source", NULL, "VINR4" }, + + { "ADC2", NULL, "ADC2 Right Capture Source" }, +}; + +#define PCM186X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static int pcm186x_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + + dev_dbg(codec->dev, "%s()\n", __func__); + + return 0; +} + +static int pcm186x_set_pll(struct snd_soc_codec *codec, unsigned int Fref, + unsigned int Fout) +{ + struct pcm186x_priv *priv = snd_soc_codec_get_drvdata(codec); + u32 P, R, J, D; + int ret = 0; + + /* Ensure automatic clock detection is disabled so we can set PLL */ + ret = regmap_update_bits(priv->regmap, PCM186X_CLK_CTRL, + PCM186X_CLK_CTRL_CLKDET_EN, 0); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + } + /* + * TODO: Implement algorithm to set FLL based on an arbitrary Fref + * rather than using specific sets of fixed values. The below is just + * a stop-gap measure while the overall clock setup approach is being + * finalized. + */ + if (Fref != 12000000) { + dev_err(codec->dev, "unsupported FLL reference frequency: %u\n", + Fref); + return -EINVAL; + } + + if (Fout == 90316800) { + /** Fout = (Fref x R x J.D) / P **/ + /** Example:*/ + /** Fref = 12MHz*/ + /** Fout = (12MHz x 1 x 7.5264) / 1 = 90.3168MHz*/ + R = 1; + J = 7; + D = 5264; + P = 1; + } else if (Fout == 98304000) { + R = 1; + J = 16; + D = 3840; + P = 2; + } else { + dev_err(codec->dev, "unsupported FLL target frequency: %u\n", + Fout); + return -EINVAL; + } + + dev_dbg(codec->dev, "%s() R=%u J=%u D=%u P=%u\n", __func__, R, J, D, P); + /* Program integer divider */ + ret = regmap_write(priv->regmap, PCM186X_PLL_P_DIV, P - 1); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + } + + /* Program integer multiplier */ + ret = regmap_write(priv->regmap, PCM186X_PLL_R_DIV, R - 1); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + } + + /* Program integer part of the fractional multiplier */ + ret = regmap_write(priv->regmap, PCM186X_PLL_J_DIV, J); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + } + + /* + * Program 1/1000th fractional multiplier (0-9999), with the MSB written + * last as required by the device. + */ + ret = regmap_write(priv->regmap, PCM186X_PLL_D_DIV_LSB, D & 0xff); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + } + + ret = regmap_write(priv->regmap, PCM186X_PLL_D_DIV_MSB, D >> 8); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + } + + /* Source PLL by SCK and enable it */ + ret = regmap_write(priv->regmap, PCM186X_PLL_CTRL, PCM186X_PLL_CTRL_EN); + if (ret < 0) + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + + return 0; +} + +static int pcm186x_set_fmt(struct snd_soc_dai *dai, unsigned int format) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm186x_priv *priv = snd_soc_codec_get_drvdata(codec); + unsigned int clk_ctrl, pcm_cfg; + int ret; + + dev_dbg(codec->dev, "%s() format=0x%x\n", __func__, format); + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + if (!priv->sysclk) { + dev_err(codec->dev, "operating in master mode requires sysclock to be configured!\n"); + return -EINVAL; + } + + /* Codec is bit/frame clock master */ + clk_ctrl = PCM186X_CLK_CTRL_MST_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* Codec is bit/frame clock slave */ + clk_ctrl = PCM186X_CLK_CTRL_SCK_SRC_PLL; + break; + default: + return -EINVAL; + } + + if (priv->pllclk) { + if (!priv->sysclk) { + dev_err(codec->dev, "using PLL requires sysclock to be configured!\n"); + return -EINVAL; + } + + ret = pcm186x_set_pll(codec, priv->sysclk, priv->pllclk); + if (ret < 0) + return ret; + + /* Switch all clock trees to be sourced from the PLL */ + clk_ctrl |= PCM186X_CLK_CTRL_SCK_SRC_PLL | + PCM186X_CLK_CTRL_ADC_SRC_PLL | + PCM186X_CLK_CTRL_DSP2_SRC_PLL | + PCM186X_CLK_CTRL_DSP1_SRC_PLL; + } else { + /* + * If PLL is not explicitly configured we rely on the automatic + * clock detection mechanism in both master and slave modes that + * will use either XTAL, SCKI, or BCK as a reference clock. Note + * that unused clock inputs should be connected to GND for the + * automatic clock detection mechanism to work properly. + */ + clk_ctrl |= PCM186X_CLK_CTRL_CLKDET_EN; + } + + ret = regmap_write(priv->regmap, PCM186X_CLK_CTRL, clk_ctrl); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + } + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + pcm_cfg = PCM186X_PCM_CFG_FMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + pcm_cfg = PCM186X_PCM_CFG_FMT_LEFTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + /* + * Fall through... DSP_A uses the same basic config as DSP_B + * except we need to shift the TDM output by one BCK cycle + * which we'll still need to configure. + */ + case SND_SOC_DAIFMT_DSP_B: + pcm_cfg = PCM186X_PCM_CFG_FMT_TDM; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, PCM186X_PCM_CFG, + PCM186X_PCM_CFG_FMT_MASK, pcm_cfg); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + } + /* + * We only support the non-inverted clocks. Note that clock polarity + * depends on the actual FORMAT. + */ + if ((format & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) + return -EINVAL; + + priv->dai_format = format; + + return 0; +} + +static int pcm186x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm186x_priv *priv = snd_soc_codec_get_drvdata(codec); + unsigned int first_slot, last_slot, tdm_offset; + int ret; + + dev_dbg(codec->dev, + "%s() tx_mask=0x%x rx_mask=0x%x slots=%d slot_width=%d\n", + __func__, tx_mask, rx_mask, slots, slot_width); + + if (!tx_mask) { + dev_err(codec->dev, "tdm tx mask must not be 0\n"); + return -EINVAL; + } + tx_mask = priv->slots_mask; + first_slot = __ffs(tx_mask); + last_slot = __fls(tx_mask); + + if (last_slot - first_slot != hweight32(tx_mask) - 1) { + dev_err(codec->dev, "tdm tx mask must be contiguous\n"); + return -EINVAL; + } + + tdm_offset = first_slot * slot_width; + + dev_info(codec->dev, "tdm_offset:0x%x\n", tdm_offset); + + if (tdm_offset > 255) { + dev_err(codec->dev, "tdm tx slot selection out of bounds\n"); + return -EINVAL; + } + + priv->tdm_offset = tdm_offset; + tdm_offset += 1; + ret = regmap_write(priv->regmap, PCM186X_TDM_TX_OFFSET, tdm_offset); + if (ret < 0) + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; +} + +static int pcm186x_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 pcm186x_priv *priv = snd_soc_codec_get_drvdata(codec); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int rate = params_rate(params); + unsigned int format = params_format(params); + unsigned int width = params_width(params); + unsigned int channels = params_channels(params); + unsigned int pcm_cfg; + unsigned int master_clk; + unsigned int div_lrck; + unsigned int div_bck; + bool is_tdm_mode; + unsigned int tdm_offset; + unsigned int tdm_tx_sel; + int ret; + unsigned int fmt; + + dev_dbg(codec->dev, "%s() rate=%u format=0x%x width=%u channels=%u\n", + __func__, rate, format, width, channels); + + switch (width) { + case 16: + pcm_cfg = PCM186X_PCM_CFG_RX_WLEN_16 | + PCM186X_PCM_CFG_TX_WLEN_16; + break; + case 20: + pcm_cfg = PCM186X_PCM_CFG_RX_WLEN_20 | + PCM186X_PCM_CFG_TX_WLEN_20; + break; + case 24: + pcm_cfg = PCM186X_PCM_CFG_RX_WLEN_24 | + PCM186X_PCM_CFG_TX_WLEN_24; + break; + case 32: + pcm_cfg = PCM186X_PCM_CFG_RX_WLEN_32 | + PCM186X_PCM_CFG_TX_WLEN_32; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, PCM186X_PCM_CFG, + PCM186X_PCM_CFG_RX_WLEN_MASK | + PCM186X_PCM_CFG_TX_WLEN_MASK, + pcm_cfg); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", ret); + return ret; + } + + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CONT; + + ret = snd_soc_runtime_set_dai_fmt(rtd, fmt); + if (ret) + return ret; + /* + * In DSP/TDM mode, the LRCLK divider must be 256 as per datasheet. + * Also, complete the codec serial audio interface format configuration + * by setting the appropriate TDM offset. For all other operating modes, + * size the LRCLK period just long enough to fit the used bits. + */ + switch (priv->dai_format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + is_tdm_mode = true; + tdm_offset = priv->tdm_offset + 1; + div_lrck = 256; + break; + case SND_SOC_DAIFMT_DSP_B: + is_tdm_mode = true; + tdm_offset = priv->tdm_offset; + div_lrck = 256; + break; + default: + is_tdm_mode = false; + tdm_offset = 0; + div_lrck = width * channels; + } + + if (is_tdm_mode) { + /* Select TDM transmission data */ + switch (channels) { + case 2: + tdm_tx_sel = PCM186X_TDM_TX_SEL_2CH; + break; + case 4: + tdm_tx_sel = PCM186X_TDM_TX_SEL_4CH; + break; + case 6: + tdm_tx_sel = PCM186X_TDM_TX_SEL_6CH; + break; + case 8: + tdm_tx_sel = PCM186X_TDM_TX_SEL_2CH; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, PCM186X_TDM_TX_SEL, + PCM186X_TDM_TX_SEL_MASK, tdm_tx_sel); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", + ret); + return ret; + } + + /* Configure 1/256 duty cycle for LRCK */ + ret = regmap_update_bits(priv->regmap, PCM186X_PCM_CFG, + PCM186X_PCM_CFG_TDM_LRCK_MODE, + PCM186X_PCM_CFG_TDM_LRCK_MODE); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", + ret); + return ret; + } + /* (Re-)configure the final TDM offset */ + ret = regmap_write(priv->regmap, PCM186X_TDM_TX_OFFSET, + tdm_offset); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", + ret); + return ret; + } + } + + /* Only configure clock dividers in master mode. */ + if ((priv->dai_format & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBM_CFM) { + /* Use PLL output frequency if PLL is enabled */ + master_clk = priv->pllclk ? priv->pllclk : priv->sysclk; + + div_bck = master_clk / (div_lrck * rate); + + dev_dbg(codec->dev, + "%s() master_clk=%u div_bck=%u div_lrck=%u\n", + __func__, master_clk, div_bck, div_lrck); + ret = regmap_write(priv->regmap, PCM186X_BCK_DIV, div_bck - 1); + if (ret < 0) { + dev_err(codec->dev, + "failed to write register: %d\n", ret); + return ret; + } + + ret = regmap_write(priv->regmap, PCM186X_LRK_DIV, + div_lrck - 1); + if (ret < 0) { + dev_err(codec->dev, "failed to write register: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int pcm186x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm186x_priv *priv = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "%s() clk_id=%d freq=%u dir=%d\n", + __func__, clk_id, freq, dir); + + priv->sysclk = freq; + + return 0; +} + +const struct snd_soc_dai_ops pcm186x_dai_ops = { + .startup = pcm186x_startup, + .set_fmt = pcm186x_set_fmt, + .set_tdm_slot = pcm186x_set_tdm_slot, + .hw_params = pcm186x_hw_params, + .set_sysclk = pcm186x_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver pcm1863_dai = { + .name = "pcm1863-aif", + .capture = { + .stream_name = "AIF Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = PCM186X_FORMATS, + }, + .playback = { /*dummy for device node*/ + .stream_name = "AIF Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = PCM186X_FORMATS, + }, + .ops = &pcm186x_dai_ops, +}; + +static struct snd_soc_dai_driver pcm1865_dai = { + .name = "pcm1865-aif", + .capture = { + .stream_name = "AIF Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = PCM186X_FORMATS, + }, + .playback = { /*dummy for device node*/ + .stream_name = "AIF Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = PCM186X_FORMATS, + }, + .ops = &pcm186x_dai_ops, +}; + +static int pcm186x_codec_probe(struct snd_soc_codec *codec) +{ + dev_dbg(codec->dev, "%s()\n", __func__); + + return 0; +} + +static int pcm186x_codec_remove(struct snd_soc_codec *codec) +{ + dev_dbg(codec->dev, "%s()\n", __func__); + + return 0; +} + +static struct regmap *pcm186x_get_regmap(struct device *dev) +{ + struct pcm186x_priv *priv = dev_get_drvdata(dev); + + return priv->regmap; +} + +static int pcm186x_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + dev_dbg(codec->dev, "%s() level=%d\n", __func__, level); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + break; + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_pcm1863 = { + .probe = pcm186x_codec_probe, + .remove = pcm186x_codec_remove, + .get_regmap = pcm186x_get_regmap, + + .set_bias_level = pcm186x_set_bias_level, + + .component_driver = { + .controls = pcm1863_snd_controls, + .num_controls = ARRAY_SIZE(pcm1863_snd_controls), + .dapm_widgets = pcm1863_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm1863_dapm_widgets), + .dapm_routes = pcm1863_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm1863_dapm_routes), + } +}; + +static struct snd_soc_codec_driver soc_codec_dev_pcm1865 = { + .probe = pcm186x_codec_probe, + .remove = pcm186x_codec_remove, + .get_regmap = pcm186x_get_regmap, + + .set_bias_level = pcm186x_set_bias_level, + + .component_driver = { + .controls = pcm1865_snd_controls, + .num_controls = ARRAY_SIZE(pcm1865_snd_controls), + .dapm_widgets = pcm1865_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm1865_dapm_widgets), + .dapm_routes = pcm1865_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm1865_dapm_routes), + } +}; + +static bool pcm186x_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PCM186X_PAGE: + case PCM186X_DEVICE_STATUS: + case PCM186X_FSAMPLE_STATUS: + case PCM186X_DIV_STATUS: + case PCM186X_CLK_STATUS: + case PCM186X_SUPPLY_STATUS: + case PCM186X_MMAP_STAT_CTRL: + case PCM186X_MMAP_ADDRESS: + return true; + } + + return false; +} + +/* + * The PCM186x's page register is located on every page, allowing to program it + * without having to switch pages. Take advantage of this by defining the range + * such to have this register located inside the data window. + */ +static const struct regmap_range_cfg pcm186x_range = { + .name = "Pages", + .range_max = PCM186X_MAX_REGISTER, + .selector_reg = PCM186X_PAGE, + .selector_mask = 0xff, + .window_len = PCM186X_PAGE_LEN, +}; + +const struct regmap_config pcm186x_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .volatile_reg = pcm186x_volatile, + + .ranges = &pcm186x_range, + .num_ranges = 1, + + .max_register = PCM186X_MAX_REGISTER, +/* + * TODO: Activate register map cache. It has been temporarily deactivated to + * eliminate a potential source of trouble during driver development. + */ + /*.reg_defaults = pcm186x_reg_defaults,*/ + /*.num_reg_defaults = ARRAY_SIZE(pcm186x_reg_defaults),*/ + /*.cache_type = REGCACHE_RBTREE,*/ +}; +EXPORT_SYMBOL_GPL(pcm186x_regmap); + +static irqreturn_t pcm186x_irq(int irq, void *_dev) +{ + struct device *dev = _dev; + + dev_dbg(dev, "%s()\n", __func__); + + return IRQ_HANDLED; +} + +int pcm186x_probe(struct device *dev, enum pcm186x_type type, int irq, + struct regmap *regmap) +{ + struct pcm186x_priv *priv; + int i, ret; + + dev_dbg(dev, "%s()\n", __func__); + + priv = devm_kzalloc(dev, sizeof(struct pcm186x_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->type = type; + priv->irq = irq; + priv->regmap = regmap; + + for (i = 0; i < ARRAY_SIZE(priv->supplies); i++) + priv->supplies[i].supply = pcm186x_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret != 0) { + dev_err(dev, "failed to request supplies: %d\n", ret); + return ret; + } + + /* Reset device registers for a consistent power-on like state */ + ret = regmap_write(regmap, PCM186X_PAGE, PCM186X_RESET); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + ret = regmap_write(regmap, PCM186X_PAGE, 0x00); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + + /*default L/R GAIN*/ + ret = regmap_write(regmap, PCM186X_PGA_VAL_CH1_L, 0x0C); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + ret = regmap_write(regmap, PCM186X_PGA_VAL_CH1_R, 0x0C); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + + /*default ADC1 Input Channel Select*/ + ret = regmap_write(regmap, PCM186X_ADC1_INPUT_SEL_L, 0x50); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + ret = regmap_write(regmap, PCM186X_ADC1_INPUT_SEL_R, 0x50); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + + /*default ADC2 Input Channel Select*/ + ret = regmap_write(regmap, PCM186X_ADC2_INPUT_SEL_L, 0x40); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + ret = regmap_write(regmap, PCM186X_ADC2_INPUT_SEL_R, 0x40); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + + /*default GPIO0-1 Config*/ + ret = regmap_write(regmap, PCM186X_GPIO1_0_CTRL, 0x00); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + + /*default GPIO02-3 Config*/ + ret = regmap_write(regmap, PCM186X_GPIO3_2_CTRL, 0x00); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + + /*default PULL DOWN Config*/ + ret = regmap_write(regmap, PCM186X_GPIO_PULL_CTRL, 0xf0); + if (ret != 0) { + dev_err(dev, "failed to write device: %d\n", ret); + goto err_disable_reg; + } + +#ifdef CONFIG_OF + if (dev->of_node) { + const struct device_node *np = dev->of_node; + u32 val; + + /* + * If 'pllclk' is provided, we'll configure and use the PLL as + * the main clock source rather than the specified system clock. + * Note that this property may become obsolete once the final + * (automatic) clock setup algorithm is implemented. + */ + if (of_property_read_u32(np, "pllclk", &val) >= 0) + priv->pllclk = val; + + if (of_property_read_u32(np, "slots_mask", &val) >= 0) { + priv->slots_mask = val; + dev_info(dev, "slots_mask:0x%x\n", val); + } + + + } +#endif + + if (irq > 0) { + ret = request_threaded_irq(irq, NULL, pcm186x_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "pcm186x", dev); + if (ret) { + dev_err(dev, "failed to request IRQ #%d\n", irq); + goto err_disable_reg; + } + dev_info(dev, "using IRQ #%d", irq); + } + + switch (priv->type) { + case PCM1865: + case PCM1864: + ret = snd_soc_register_codec(dev, &soc_codec_dev_pcm1865, + &pcm1865_dai, 1); + break; + case PCM1863: + case PCM1862: + /* Fall through... */ + default: + ret = snd_soc_register_codec(dev, &soc_codec_dev_pcm1863, + &pcm1863_dai, 1); + } + if (ret != 0) { + dev_err(dev, "failed to register CODEC: %d\n", ret); + goto err_free_irq; + } + + dev_dbg(dev, "registered codec type: %d\n", priv->type); + + return 0; + +err_free_irq: + if (irq > 0) + free_irq(irq, dev); + +err_disable_reg: + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + + return ret; +} +EXPORT_SYMBOL_GPL(pcm186x_probe); + +int pcm186x_remove(struct device *dev) +{ + struct pcm186x_priv *priv = dev_get_drvdata(dev); + + snd_soc_unregister_codec(dev); + + if (priv->irq > 0) + free_irq(priv->irq, dev); + + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + + return 0; +} +EXPORT_SYMBOL_GPL(pcm186x_remove); + +#ifdef CONFIG_PM +static int pcm186x_suspend(struct device *dev) +{ + struct pcm186x_priv *priv = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "%s()\n", __func__); + ret = regmap_update_bits(priv->regmap, PCM186X_POWER_CTRL, + PCM186X_PWR_CTRL_PWRDN, + PCM186X_PWR_CTRL_PWRDN); + if (ret != 0) { + dev_err(dev, "failed to write register: %d\n", ret); + return ret; + } + ret = regulator_bulk_disable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret != 0) { + dev_err(dev, "failed to disable supplies: %d\n", ret); + return ret; + } + + return 0; +} + +static int pcm186x_resume(struct device *dev) +{ + struct pcm186x_priv *priv = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret != 0) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(priv->regmap, PCM186X_POWER_CTRL, + PCM186X_PWR_CTRL_PWRDN, 0); + if (ret != 0) { + dev_err(dev, "failed to write register: %d\n", ret); + return ret; + } + + return 0; +} +#endif + +const struct dev_pm_ops pcm186x_pm_ops = { + SET_RUNTIME_PM_OPS(pcm186x_suspend, pcm186x_resume, NULL) +}; +EXPORT_SYMBOL_GPL(pcm186x_pm_ops); + +MODULE_DESCRIPTION("PCM186x Universal Audio ADC driver"); +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/amlogic/pcm186x.h b/sound/soc/codecs/amlogic/pcm186x.h new file mode 100644 index 0000000..e9f8025 --- /dev/null +++ b/sound/soc/codecs/amlogic/pcm186x.h @@ -0,0 +1,265 @@ +/* + * pcm186x.h - Texas Instruments PCM186x Universal Audio ADC + * + * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg + * + * 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. + */ + +#ifndef _PCM186X_H_ +#define _PCM186X_H_ + +#include +#include + +enum pcm186x_type { + PCM1862, + PCM1863, + PCM1864, + PCM1865, +}; + +#define PCM186X_PAGE_LEN 0x0100 +#define PCM186X_PAGE_BASE(n) (PCM186X_PAGE_LEN * n) + +/* The page selection register address is the same on all pages */ +#define PCM186X_PAGE 0 + +/* Register Definitions - Page 0 */ +#define PCM186X_PGA_VAL_CH1_L (PCM186X_PAGE_BASE(0) + 1) +#define PCM186X_PGA_VAL_CH1_R (PCM186X_PAGE_BASE(0) + 2) +#define PCM186X_PGA_VAL_CH2_L (PCM186X_PAGE_BASE(0) + 3) +#define PCM186X_PGA_VAL_CH2_R (PCM186X_PAGE_BASE(0) + 4) +#define PCM186X_PGA_CTRL (PCM186X_PAGE_BASE(0) + 5) +#define PCM186X_ADC1_INPUT_SEL_L (PCM186X_PAGE_BASE(0) + 6) +#define PCM186X_ADC1_INPUT_SEL_R (PCM186X_PAGE_BASE(0) + 7) +#define PCM186X_ADC2_INPUT_SEL_L (PCM186X_PAGE_BASE(0) + 8) +#define PCM186X_ADC2_INPUT_SEL_R (PCM186X_PAGE_BASE(0) + 9) +#define PCM186X_AUXADC_INPUT_SEL (PCM186X_PAGE_BASE(0) + 10) +#define PCM186X_PCM_CFG (PCM186X_PAGE_BASE(0) + 11) +#define PCM186X_TDM_TX_SEL (PCM186X_PAGE_BASE(0) + 12) +#define PCM186X_TDM_TX_OFFSET (PCM186X_PAGE_BASE(0) + 13) +#define PCM186X_TDM_RX_OFFSET (PCM186X_PAGE_BASE(0) + 14) +#define PCM186X_DPGA_VAL_CH1_L (PCM186X_PAGE_BASE(0) + 15) +#define PCM186X_GPIO1_0_CTRL (PCM186X_PAGE_BASE(0) + 16) +#define PCM186X_GPIO3_2_CTRL (PCM186X_PAGE_BASE(0) + 17) +#define PCM186X_GPIO1_0_DIR_CTRL (PCM186X_PAGE_BASE(0) + 18) +#define PCM186X_GPIO3_2_DIR_CTRL (PCM186X_PAGE_BASE(0) + 19) +#define PCM186X_GPIO_IN_OUT (PCM186X_PAGE_BASE(0) + 20) +#define PCM186X_GPIO_PULL_CTRL (PCM186X_PAGE_BASE(0) + 21) +#define PCM186X_DPGA_VAL_CH1_R (PCM186X_PAGE_BASE(0) + 22) +#define PCM186X_DPGA_VAL_CH2_L (PCM186X_PAGE_BASE(0) + 23) +#define PCM186X_DPGA_VAL_CH2_R (PCM186X_PAGE_BASE(0) + 24) +#define PCM186X_DPGA_GAIN_CTRL (PCM186X_PAGE_BASE(0) + 25) +#define PCM186X_DPGA_MIC_CTRL (PCM186X_PAGE_BASE(0) + 26) +#define PCM186X_DIN_RESAMP_CTRL (PCM186X_PAGE_BASE(0) + 27) +#define PCM186X_CLK_CTRL (PCM186X_PAGE_BASE(0) + 32) +#define PCM186X_DSP1_CLK_DIV (PCM186X_PAGE_BASE(0) + 33) +#define PCM186X_DSP2_CLK_DIV (PCM186X_PAGE_BASE(0) + 34) +#define PCM186X_ADC_CLK_DIV (PCM186X_PAGE_BASE(0) + 35) +#define PCM186X_PLL_SCK_DIV (PCM186X_PAGE_BASE(0) + 37) +#define PCM186X_BCK_DIV (PCM186X_PAGE_BASE(0) + 38) +#define PCM186X_LRK_DIV (PCM186X_PAGE_BASE(0) + 39) +#define PCM186X_PLL_CTRL (PCM186X_PAGE_BASE(0) + 40) +#define PCM186X_PLL_P_DIV (PCM186X_PAGE_BASE(0) + 41) +#define PCM186X_PLL_R_DIV (PCM186X_PAGE_BASE(0) + 42) +#define PCM186X_PLL_J_DIV (PCM186X_PAGE_BASE(0) + 43) +#define PCM186X_PLL_D_DIV_LSB (PCM186X_PAGE_BASE(0) + 44) +#define PCM186X_PLL_D_DIV_MSB (PCM186X_PAGE_BASE(0) + 45) +#define PCM186X_SIGDET_MODE (PCM186X_PAGE_BASE(0) + 48) +#define PCM186X_SIGDET_MASK (PCM186X_PAGE_BASE(0) + 49) +#define PCM186X_SIGDET_STAT (PCM186X_PAGE_BASE(0) + 50) +#define PCM186X_SIGDET_LOSS_TIME (PCM186X_PAGE_BASE(0) + 52) +#define PCM186X_SIGDET_SCAN_TIME (PCM186X_PAGE_BASE(0) + 53) +#define PCM186X_SIGDET_INT_INTVL (PCM186X_PAGE_BASE(0) + 54) +#define PCM186X_SIGDET_DC_REF_CH1_L (PCM186X_PAGE_BASE(0) + 64) +#define PCM186X_SIGDET_DC_DIFF_CH1_L (PCM186X_PAGE_BASE(0) + 65) +#define PCM186X_SIGDET_DC_LEV_CH1_L (PCM186X_PAGE_BASE(0) + 66) +#define PCM186X_SIGDET_DC_REF_CH1_R (PCM186X_PAGE_BASE(0) + 67) +#define PCM186X_SIGDET_DC_DIFF_CH1_R (PCM186X_PAGE_BASE(0) + 68) +#define PCM186X_SIGDET_DC_LEV_CH1_R (PCM186X_PAGE_BASE(0) + 69) +#define PCM186X_SIGDET_DC_REF_CH2_L (PCM186X_PAGE_BASE(0) + 70) +#define PCM186X_SIGDET_DC_DIFF_CH2_L (PCM186X_PAGE_BASE(0) + 71) +#define PCM186X_SIGDET_DC_LEV_CH2_L (PCM186X_PAGE_BASE(0) + 72) +#define PCM186X_SIGDET_DC_REF_CH2_R (PCM186X_PAGE_BASE(0) + 73) +#define PCM186X_SIGDET_DC_DIFF_CH2_R (PCM186X_PAGE_BASE(0) + 74) +#define PCM186X_SIGDET_DC_LEV_CH2_R (PCM186X_PAGE_BASE(0) + 75) +#define PCM186X_SIGDET_DC_REF_CH3_L (PCM186X_PAGE_BASE(0) + 76) +#define PCM186X_SIGDET_DC_DIFF_CH3_L (PCM186X_PAGE_BASE(0) + 77) +#define PCM186X_SIGDET_DC_LEV_CH3_L (PCM186X_PAGE_BASE(0) + 78) +#define PCM186X_SIGDET_DC_REF_CH3_R (PCM186X_PAGE_BASE(0) + 79) +#define PCM186X_SIGDET_DC_DIFF_CH3_R (PCM186X_PAGE_BASE(0) + 80) +#define PCM186X_SIGDET_DC_LEV_CH3_R (PCM186X_PAGE_BASE(0) + 81) +#define PCM186X_SIGDET_DC_REF_CH4_L (PCM186X_PAGE_BASE(0) + 82) +#define PCM186X_SIGDET_DC_DIFF_CH4_L (PCM186X_PAGE_BASE(0) + 83) +#define PCM186X_SIGDET_DC_LEV_CH4_L (PCM186X_PAGE_BASE(0) + 84) +#define PCM186X_SIGDET_DC_REF_CH4_R (PCM186X_PAGE_BASE(0) + 85) +#define PCM186X_SIGDET_DC_DIFF_CH4_R (PCM186X_PAGE_BASE(0) + 86) +#define PCM186X_SIGDET_DC_LEV_CH4_R (PCM186X_PAGE_BASE(0) + 87) +#define PCM186X_AUXADC_DATA_CTRL (PCM186X_PAGE_BASE(0) + 88) +#define PCM186X_AUXADC_DATA_LSB (PCM186X_PAGE_BASE(0) + 89) +#define PCM186X_AUXADC_DATA_MSB (PCM186X_PAGE_BASE(0) + 90) +#define PCM186X_INT_ENABLE (PCM186X_PAGE_BASE(0) + 96) +#define PCM186X_INT_FLAG (PCM186X_PAGE_BASE(0) + 97) +#define PCM186X_INT_POL_WIDTH (PCM186X_PAGE_BASE(0) + 98) +#define PCM186X_POWER_CTRL (PCM186X_PAGE_BASE(0) + 112) +#define PCM186X_FILTER_MUTE_CTRL (PCM186X_PAGE_BASE(0) + 113) +#define PCM186X_DEVICE_STATUS (PCM186X_PAGE_BASE(0) + 114) +#define PCM186X_FSAMPLE_STATUS (PCM186X_PAGE_BASE(0) + 115) +#define PCM186X_DIV_STATUS (PCM186X_PAGE_BASE(0) + 116) +#define PCM186X_CLK_STATUS (PCM186X_PAGE_BASE(0) + 117) +#define PCM186X_SUPPLY_STATUS (PCM186X_PAGE_BASE(0) + 120) + +/* Register Definitions - Page 1 */ +#define PCM186X_MMAP_STAT_CTRL (PCM186X_PAGE_BASE(1) + 1) +#define PCM186X_MMAP_ADDRESS (PCM186X_PAGE_BASE(1) + 2) +#define PCM186X_MEM_WDATA0 (PCM186X_PAGE_BASE(1) + 4) +#define PCM186X_MEM_WDATA1 (PCM186X_PAGE_BASE(1) + 5) +#define PCM186X_MEM_WDATA2 (PCM186X_PAGE_BASE(1) + 6) +#define PCM186X_MEM_WDATA3 (PCM186X_PAGE_BASE(1) + 7) +#define PCM186X_MEM_RDATA0 (PCM186X_PAGE_BASE(1) + 8) +#define PCM186X_MEM_RDATA1 (PCM186X_PAGE_BASE(1) + 9) +#define PCM186X_MEM_RDATA2 (PCM186X_PAGE_BASE(1) + 10) +#define PCM186X_MEM_RDATA3 (PCM186X_PAGE_BASE(1) + 11) + +/* Register Definitions - Page 3 */ +#define PCM186X_OSC_PWR_DOWN_CTRL (PCM186X_PAGE_BASE(3) + 18) +#define PCM186X_MIC_BIAS_CTRL (PCM186X_PAGE_BASE(3) + 21) + +/* Register Definitions - Page 253 */ +#define PCM186X_CURR_TRIM_CTRL (PCM186X_PAGE_BASE(253) + 20) + +#define PCM186X_MAX_REGISTER PCM186X_CURR_TRIM_CTRL + +/* + * Register definitions for DSP accessed indirectly through PCM186X_MEM_X using + * a special sequence. Each of the registers holds 24 bits. See device data + * sheet for details. + */ +#define PCM186X_DSP_MIX1_CH1L 0x00 +#define PCM186X_DSP_MIX1_CH1R 0x01 +#define PCM186X_DSP_MIX1_CH2L 0x02 +#define PCM186X_DSP_MIX1_CH2R 0x03 +#define PCM186X_DSP_MIX1_I2SL 0x04 +#define PCM186X_DSP_MIX1_I2SR 0x05 +#define PCM186X_DSP_MIX2_CH1L 0x06 +#define PCM186X_DSP_MIX2_CH1R 0x07 +#define PCM186X_DSP_MIX2_CH2L 0x08 +#define PCM186X_DSP_MIX2_CH2R 0x09 +#define PCM186X_DSP_MIX2_I2SL 0x0a +#define PCM186X_DSP_MIX2_I2SR 0x0b +#define PCM186X_DSP_MIX3_CH1L 0x0c +#define PCM186X_DSP_MIX3_CH1R 0x0d +#define PCM186X_DSP_MIX3_CH2L 0x0e +#define PCM186X_DSP_MIX3_CH2R 0x0f +#define PCM186X_DSP_MIX3_I2SL 0x10 +#define PCM186X_DSP_MIX3_I2SR 0x11 +#define PCM186X_DSP_MIX4_CH1L 0x12 +#define PCM186X_DSP_MIX4_CH1R 0x13 +#define PCM186X_DSP_MIX4_CH2L 0x14 +#define PCM186X_DSP_MIX4_CH2R 0x15 +#define PCM186X_DSP_MIX4_I2SL 0x16 +#define PCM186X_DSP_MIX4_I2SR 0x17 +#define PCM186X_DSP_LPF_B0 0x20 +#define PCM186X_DSP_LPF_B1 0x21 +#define PCM186X_DSP_LPF_B2 0x22 +#define PCM186X_DSP_LPF_A1 0x23 +#define PCM186X_DSP_LPF_A2 0x24 +#define PCM186X_DSP_HPF_B0 0x25 +#define PCM186X_DSP_HPF_B1 0x26 +#define PCM186X_DSP_HPF_B2 0x27 +#define PCM186X_DSP_HPF_A1 0x28 +#define PCM186X_DSP_HPF_A2 0x29 +#define PCM186X_DSP_LOSS_THRESH 0x2c +#define PCM186X_DSP_RES_THRESH 0x2d + +/* PCM186X_PAGE */ +#define PCM186X_PAGE_0 0x00 +#define PCM186X_PAGE_1 0x01 +#define PCM186X_PAGE_3 0x03 +#define PCM186X_RESET 0xff + +/* PCM186X_ADCX_INPUT_SEL_X */ +#define PCM186X_ADC_INPUT_SEL_POL BIT(7) +#define PCM186X_ADC_INPUT_SEL_MASK GENMASK(5, 0) + +/* PCM186X_PCM_CFG */ +#define PCM186X_PCM_CFG_RX_WLEN_32 (0x0 << 6) +#define PCM186X_PCM_CFG_RX_WLEN_24 (0x1 << 6) +#define PCM186X_PCM_CFG_RX_WLEN_20 (0x2 << 6) +#define PCM186X_PCM_CFG_RX_WLEN_16 (0x3 << 6) +#define PCM186X_PCM_CFG_RX_WLEN_MASK (0x3 << 6) +#define PCM186X_PCM_CFG_TDM_LRCK_MODE BIT(4) +#define PCM186X_PCM_CFG_TX_WLEN_32 (0x0 << 2) +#define PCM186X_PCM_CFG_TX_WLEN_24 (0x1 << 2) +#define PCM186X_PCM_CFG_TX_WLEN_20 (0x2 << 2) +#define PCM186X_PCM_CFG_TX_WLEN_16 (0x3 << 2) +#define PCM186X_PCM_CFG_TX_WLEN_MASK (0x3 << 2) +#define PCM186X_PCM_CFG_FMT_I2S 0x00 +#define PCM186X_PCM_CFG_FMT_LEFTJ 0x01 +#define PCM186X_PCM_CFG_FMT_RIGHTJ 0x02 +#define PCM186X_PCM_CFG_FMT_TDM 0x03 +#define PCM186X_PCM_CFG_FMT_MASK 0x03 + +/* PCM186X_TDM_TX_SEL */ +#define PCM186X_TDM_TX_SEL_2CH 0x00 +#define PCM186X_TDM_TX_SEL_4CH 0x01 +#define PCM186X_TDM_TX_SEL_6CH 0x02 +#define PCM186X_TDM_TX_SEL_MASK 0x03 + +/* PCM186X_CLK_CTRL */ +#define PCM186X_CLK_CTRL_SCK_XI_SEL1 BIT(7) +#define PCM186X_CLK_CTRL_SCK_XI_SEL0 BIT(6) +#define PCM186X_CLK_CTRL_SCK_SRC_PLL BIT(5) +#define PCM186X_CLK_CTRL_MST_MODE BIT(4) +#define PCM186X_CLK_CTRL_ADC_SRC_PLL BIT(3) +#define PCM186X_CLK_CTRL_DSP2_SRC_PLL BIT(2) +#define PCM186X_CLK_CTRL_DSP1_SRC_PLL BIT(1) +#define PCM186X_CLK_CTRL_CLKDET_EN BIT(0) + +/* PCM186X_PLL_CTRL */ +#define PCM186X_PLL_CTRL_LOCK BIT(4) +#define PCM186X_PLL_CTRL_REF_SEL BIT(1) +#define PCM186X_PLL_CTRL_EN BIT(0) + +/* PCM186X_POWER_CTRL */ +#define PCM186X_PWR_CTRL_PWRDN BIT(2) +#define PCM186X_PWR_CTRL_SLEEP BIT(1) +#define PCM186X_PWR_CTRL_STBY BIT(0) + +/* PCM186X_CLK_STATUS */ +#define PCM186X_CLK_STATUS_LRCKHLT BIT(6) +#define PCM186X_CLK_STATUS_BCKHLT BIT(5) +#define PCM186X_CLK_STATUS_SCKHLT BIT(4) +#define PCM186X_CLK_STATUS_LRCKERR BIT(2) +#define PCM186X_CLK_STATUS_BCKERR BIT(1) +#define PCM186X_CLK_STATUS_SCKERR BIT(0) + +/* PCM186X_SUPPLY_STATUS */ +#define PCM186X_SUPPLY_STATUS_DVDD BIT(2) +#define PCM186X_SUPPLY_STATUS_AVDD BIT(1) +#define PCM186X_SUPPLY_STATUS_LDO BIT(0) + +/* PCM186X_MMAP_STAT_CTRL */ +#define PCM186X_MMAP_STAT_DONE BIT(4) +#define PCM186X_MMAP_STAT_BUSY BIT(2) +#define PCM186X_MMAP_STAT_R_REQ BIT(1) +#define PCM186X_MMAP_STAT_W_REQ BIT(0) + +extern const struct dev_pm_ops pcm186x_pm_ops; +extern const struct regmap_config pcm186x_regmap; + +int pcm186x_probe(struct device *dev, enum pcm186x_type type, int irq, + struct regmap *regmap); +int pcm186x_remove(struct device *dev); + +#endif /* _PCM186X_H_ */ diff --git a/sound/soc/codecs/amlogic/ssm3515.c b/sound/soc/codecs/amlogic/ssm3515.c new file mode 100644 index 0000000..d4552d4 --- /dev/null +++ b/sound/soc/codecs/amlogic/ssm3515.c @@ -0,0 +1,652 @@ +/* + * SSM3515 amplifier audio driver + * + * Copyright 2014 Google Chromium project. + * Author: Anatol Pomozov + * + * Based on code copyright/by: + * Copyright 2013 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSM3515_REG_POWER_CTRL 0x00 +#define SSM3515_REG_AMP_SNS_CTRL 0x01 +#define SSM3515_REG_DAC_CTRL 0x02 +#define SSM3515_REG_DAC_VOLUME 0x03 +#define SSM3515_REG_SAI_CTRL_1 0x04 +#define SSM3515_REG_SAI_CTRL_2 0x05 +#define SSM3515_REG_BATTERY_V_OUT 0x06 +#define SSM3515_REG_LIMITER_CTRL_1 0x07 +#define SSM3515_REG_LIMITER_CTRL_2 0x08 +#define SSM3515_REG_LIMITER_CTRL_3 0x09 +#define SSM3515_REG_STATUS_1 0x0A +#define SSM3515_REG_FAULT_CTRL 0x0B + +/* POWER_CTRL */ +#define SSM3515_POWER_APWDN_EN BIT(7) +#define SSM3515_POWER_BSNS_PWDN BIT(6) +#define SSM3515_POWER_S_RESET BIT(1) +#define SSM3515_POWER_SPWDN BIT(0) + +/* DAC_CTRL */ +#define SSM3515_DAC_HV BIT(7) +#define SSM3515_DAC_MUTE BIT(6) +#define SSM3515_DAC_HPF BIT(5) +#define SSM3515_DAC_LPM BIT(4) +#define SSM3515_DAC_FS_MASK 0x7 +#define SSM3515_DAC_FS_8000_12000 0x0 +#define SSM3515_DAC_FS_16000_24000 0x1 +#define SSM3515_DAC_FS_32000_48000 0x2 +#define SSM3515_DAC_FS_64000_96000 0x3 +#define SSM3515_DAC_FS_128000_192000 0x4 + +/* SAI_CTRL_1 */ +#define SSM3515_SAI_CTRL_1_BCLK BIT(6) +#define SSM3515_SAI_CTRL_1_TDM_BLCKS_MASK (0x3 << 3) +#define SSM3515_SAI_CTRL_1_TDM_BLCKS_32 (0x0 << 3) +#define SSM3515_SAI_CTRL_1_TDM_BLCKS_48 (0x1 << 3) +#define SSM3515_SAI_CTRL_1_TDM_BLCKS_64 (0x2 << 3) +#define SSM3515_SAI_CTRL_1_FSYNC BIT(2) +#define SSM3515_SAI_CTRL_1_LJ BIT(1) +#define SSM3515_SAI_CTRL_1_TDM BIT(0) + +/* SAI_CTRL_2 */ +#define SSM3515_SAI_CTRL_2_AUTO_SLOT BIT(3) +#define SSM3515_SAI_CTRL_2_TDM_SLOT_MASK 0x7 +#define SSM3515_SAI_CTRL_2_TDM_SLOT(x) (x) + +static int device_num; +struct ssm3515 *g_ssm3515; + +struct ssm3515 { + struct regmap *regmap; + unsigned int slots_mask; + const char *amp_cfg; + int total_num; +}; + +static const struct reg_default ssm3515_reg_defaults[] = { + { SSM3515_REG_POWER_CTRL, 0x81 }, + { SSM3515_REG_AMP_SNS_CTRL, 0x03 }, + { SSM3515_REG_DAC_CTRL, 0x22 }, + { SSM3515_REG_DAC_VOLUME, 0x40 }, + { SSM3515_REG_SAI_CTRL_1, 0x11 }, +}; + + +static bool ssm3515_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM3515_REG_POWER_CTRL ... SSM3515_REG_FAULT_CTRL: + return true; + default: + return false; + } + +} + +static bool ssm3515_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM3515_REG_POWER_CTRL ... SSM3515_REG_SAI_CTRL_2: + case SSM3515_REG_LIMITER_CTRL_1 ... SSM3515_REG_LIMITER_CTRL_3: + case SSM3515_REG_FAULT_CTRL: + return true; + /*The datasheet states that soft reset register is read-only,*/ + /*but logically it is write-only. */ + default: + return false; + } +} + +static bool ssm3515_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM3515_REG_BATTERY_V_OUT: + case SSM3515_REG_STATUS_1: + default: + return false; + } +} + +static int WL2_amplifier_power_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = 0; + int ret; + int i; + struct ssm3515 *ssm3515 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3515[i].amp_cfg, "WL2")) { + ssm3515 = g_ssm3515+i; + break; + } + } + ret = regmap_read(ssm3515->regmap, SSM3515_REG_POWER_CTRL, &value); + if (ret) + return ret; + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int WL2_amplifier_power_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value, ret, i; + struct ssm3515 *ssm3515 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3515[i].amp_cfg, "WL2")) { + ssm3515 = g_ssm3515+i; + break; + } + } + value = ucontrol->value.integer.value[0]; + ret = regmap_write(ssm3515->regmap, SSM3515_REG_POWER_CTRL, value); + if (ret) + return ret; + return 0; +} + +static int WR2_amplifier_power_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = 0; + int ret, i; + struct ssm3515 *ssm3515 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3515[i].amp_cfg, "WR2")) { + ssm3515 = g_ssm3515+i; + break; + } + } + ret = regmap_read(ssm3515->regmap, SSM3515_REG_POWER_CTRL, &value); + if (ret) + return ret; + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int WR2_amplifier_power_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value, ret, i; + struct ssm3515 *ssm3515 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3515[i].amp_cfg, "WR2")) { + ssm3515 = g_ssm3515+i; + break; + } + } + value = ucontrol->value.integer.value[0]; + ret = regmap_write(ssm3515->regmap, SSM3515_REG_POWER_CTRL, value); + if (ret) + return ret; + return 0; +} + +static int TL1_amplifier_power_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = 0; + int ret, i; + struct ssm3515 *ssm3515 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3515[i].amp_cfg, "TL1")) { + ssm3515 = g_ssm3515+i; + break; + } + } + ret = regmap_read(ssm3515->regmap, SSM3515_REG_POWER_CTRL, &value); + if (ret) + return ret; + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int TL1_amplifier_power_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value, ret, i; + struct ssm3515 *ssm3515 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3515[i].amp_cfg, "TL1")) { + ssm3515 = g_ssm3515+i; + break; + } + } + value = ucontrol->value.integer.value[0]; + ret = regmap_write(ssm3515->regmap, SSM3515_REG_POWER_CTRL, value); + if (ret) + return ret; + return 0; +} + +static int TR1_amplifier_power_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = 0; + int ret, i; + struct ssm3515 *ssm3515 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3515[i].amp_cfg, "TR1")) { + ssm3515 = g_ssm3515+i; + break; + } + } + ret = regmap_read(ssm3515->regmap, SSM3515_REG_POWER_CTRL, &value); + if (ret) + return ret; + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int TR1_amplifier_power_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value, ret, i; + struct ssm3515 *ssm3515 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3515[i].amp_cfg, "TR1")) { + ssm3515 = g_ssm3515+i; + break; + } + } + value = ucontrol->value.integer.value[0]; + ret = regmap_write(ssm3515->regmap, SSM3515_REG_POWER_CTRL, value); + if (ret) + return ret; + return 0; +} + +static struct snd_kcontrol_new ssm3515_snd_controls[] = { + SOC_SINGLE_EXT("WR2 Amp Power Control", SND_SOC_NOPM, 0, 0xff, 0, + WR2_amplifier_power_get, WR2_amplifier_power_put), + SOC_SINGLE_EXT("WL2 Amp Power Control", SND_SOC_NOPM, 0, 0xff, 0, + WL2_amplifier_power_get, WL2_amplifier_power_put), + SOC_SINGLE_EXT("TL1 Amp Power Control", SND_SOC_NOPM, 0, 0xff, 0, + TL1_amplifier_power_get, TL1_amplifier_power_put), + SOC_SINGLE_EXT("TR1 Amp Power Control", SND_SOC_NOPM, 0, 0xff, 0, + TR1_amplifier_power_get, TR1_amplifier_power_put), +}; + +static int ssm3515_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 ssm3515 *ssm3515 = snd_soc_codec_get_drvdata(codec); + unsigned int rate = params_rate(params); + unsigned int dacfs; + + if (rate >= 8000 && rate <= 12000) + dacfs = SSM3515_DAC_FS_8000_12000; + else if (rate >= 16000 && rate <= 24000) + dacfs = SSM3515_DAC_FS_16000_24000; + else if (rate >= 32000 && rate <= 48000) + dacfs = SSM3515_DAC_FS_32000_48000; + else if (rate >= 64000 && rate <= 96000) + dacfs = SSM3515_DAC_FS_64000_96000; + else if (rate >= 128000 && rate <= 192000) + dacfs = SSM3515_DAC_FS_128000_192000; + else + return -EINVAL; + + return regmap_update_bits(ssm3515->regmap, SSM3515_REG_DAC_CTRL, + SSM3515_DAC_FS_MASK, dacfs); +} + +static int ssm3515_mute(struct snd_soc_dai *dai, int mute) +{ + struct ssm3515 *ssm3515 = snd_soc_codec_get_drvdata(dai->codec); + unsigned int val; + + val = mute ? SSM3515_DAC_MUTE : 0; + return regmap_update_bits(ssm3515->regmap, SSM3515_REG_DAC_CTRL, + SSM3515_DAC_MUTE, val); +} + +static int ssm3515_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct ssm3515 *ssm3515 = snd_soc_dai_get_drvdata(dai); + unsigned int blcks; + int slot; + int ret; + + tx_mask = ssm3515->slots_mask; + if (tx_mask == 0) + return -EINVAL; + + slot = __ffs(tx_mask); + if (tx_mask != BIT(slot)) + return -EINVAL; + + switch (width) { + case 32: + blcks = SSM3515_SAI_CTRL_1_TDM_BLCKS_32; + break; + case 48: + blcks = SSM3515_SAI_CTRL_1_TDM_BLCKS_48; + break; + case 64: + blcks = SSM3515_SAI_CTRL_1_TDM_BLCKS_64; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(ssm3515->regmap, SSM3515_REG_SAI_CTRL_2, + SSM3515_SAI_CTRL_2_AUTO_SLOT | SSM3515_SAI_CTRL_2_TDM_SLOT_MASK, + SSM3515_SAI_CTRL_2_TDM_SLOT(slot)); + if (ret) + return ret; + + return regmap_update_bits(ssm3515->regmap, SSM3515_REG_SAI_CTRL_1, + SSM3515_SAI_CTRL_1_TDM_BLCKS_MASK, blcks); +} + +static int ssm3515_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ +#if 0 + struct ssm3515 *ssm3515 = snd_soc_dai_get_drvdata(dai); + unsigned int ctrl1 = 0; + bool invert_fclk; + + fmt = fmt | SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CONT; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + invert_fclk = false; + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1 |= SSM3515_SAI_CTRL_1_BCLK; + invert_fclk = false; + break; + case SND_SOC_DAIFMT_NB_IF: + ctrl1 |= SSM3515_SAI_CTRL_1_FSYNC; + invert_fclk = true; + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl1 |= SSM3515_SAI_CTRL_1_BCLK; + invert_fclk = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1 |= SSM3515_SAI_CTRL_1_LJ; + invert_fclk = !invert_fclk; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1 |= SSM3515_SAI_CTRL_1_TDM; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1 |= SSM3515_SAI_CTRL_1_TDM | SSM3515_SAI_CTRL_1_LJ; + break; + default: + return -EINVAL; + } + + if (invert_fclk) + ctrl1 |= SSM3515_SAI_CTRL_1_FSYNC; + + return regmap_update_bits(ssm3515->regmap, SSM3515_REG_SAI_CTRL_1, + SSM3515_SAI_CTRL_1_BCLK | + SSM3515_SAI_CTRL_1_FSYNC | + SSM3515_SAI_CTRL_1_LJ | + SSM3515_SAI_CTRL_1_TDM, + ctrl1); +#endif + return 0; +} + +static int ssm3515_set_power(struct ssm3515 *ssm3515, bool enable) +{ + int ret = 0; + + if (!enable) { + ret = regmap_update_bits(ssm3515->regmap, + SSM3515_REG_POWER_CTRL, + SSM3515_POWER_SPWDN, SSM3515_POWER_SPWDN); + } + + if (enable) { + ret = regmap_write(ssm3515->regmap, SSM3515_REG_POWER_CTRL, + SSM3515_POWER_S_RESET); + if (ret) + return ret; + + ret = regmap_update_bits(ssm3515->regmap, + SSM3515_REG_POWER_CTRL, + SSM3515_POWER_SPWDN, 0x00); + } + + return ret; +} + +static int ssm3515_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct ssm3515 *ssm3515 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) + ret = ssm3515_set_power(ssm3515, true); + break; + case SND_SOC_BIAS_OFF: + ret = ssm3515_set_power(ssm3515, false); + break; + } + + return ret; +} + +static const struct snd_soc_dai_ops ssm3515_dai_ops = { + .hw_params = ssm3515_hw_params, + .digital_mute = ssm3515_mute, + .set_fmt = ssm3515_set_dai_fmt, + .set_tdm_slot = ssm3515_set_tdm_slot, +}; + +static struct snd_soc_dai_driver ssm3515_dai = { + .name = "ssm3515-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32, + }, + .capture = { + .stream_name = "Capture ", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32, + }, + .ops = &ssm3515_dai_ops, +}; + +static struct snd_soc_codec_driver ssm3515_codec_nocomponent = { + .set_bias_level = ssm3515_set_bias_level, + .idle_bias_off = true, +}; + +static struct snd_soc_codec_driver ssm3515_codec_driver = { + .set_bias_level = ssm3515_set_bias_level, + .idle_bias_off = true, + + .component_driver = { + .controls = ssm3515_snd_controls, + .num_controls = ARRAY_SIZE(ssm3515_snd_controls), + }, +}; + +static const struct regmap_config ssm3515_regmap_config = { + .val_bits = 8, + .reg_bits = 8, + + .max_register = SSM3515_REG_FAULT_CTRL, + .readable_reg = ssm3515_readable_reg, + .writeable_reg = ssm3515_writeable_reg, + .volatile_reg = ssm3515_volatile_reg, +/* + * TODO: Activate register map cache. It has been temporarily deactivated to + * eliminate a potential source of trouble during driver development. + */ + /*.cache_type = REGCACHE_RBTREE,*/ + /*.reg_defaults = ssm3515_reg_defaults,*/ + /*.num_reg_defaults = ARRAY_SIZE(ssm3515_reg_defaults),*/ +}; + +static int ssm3515_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ssm3515 *ssm3515; + int ret, i; + + device_num++; + + ssm3515 = devm_kzalloc(&i2c->dev, sizeof(*ssm3515), GFP_KERNEL); + if (ssm3515 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, ssm3515); + + ssm3515->regmap = devm_regmap_init_i2c(i2c, &ssm3515_regmap_config); + if (IS_ERR(ssm3515->regmap)) + return PTR_ERR(ssm3515->regmap); + + ret = ssm3515_set_power(ssm3515, false); + if (ret) + return ret; + + ret = regmap_write(ssm3515->regmap, SSM3515_REG_POWER_CTRL, + SSM3515_POWER_S_RESET); + if (ret) + return ret; + + ret = regmap_write(ssm3515->regmap, SSM3515_REG_POWER_CTRL, + SSM3515_POWER_S_RESET); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(ssm3515_reg_defaults); i++) { + ret = regmap_write(ssm3515->regmap, ssm3515_reg_defaults[i].reg, + ssm3515_reg_defaults[i].def); + if (ret) + return ret; + } + +#ifdef CONFIG_OF + if (i2c->dev.of_node) { + const struct device_node *np = i2c->dev.of_node; + u32 val; + + if (of_property_read_u32(np, "slots_mask", &val) >= 0) + ssm3515->slots_mask = val; + + of_property_read_string(np, "amp_config", &ssm3515->amp_cfg); + + if (of_property_read_u32(np, "total_num", &val) >= 0) + ssm3515->total_num = val; + } +#endif + if (device_num == 1) { + g_ssm3515 = kzalloc(sizeof(struct ssm3515) * + ssm3515->total_num, GFP_KERNEL); + if (g_ssm3515 == NULL) + return -ENOMEM; + } + + g_ssm3515[device_num-1] = *ssm3515; + + if (device_num == ssm3515->total_num) { + ret = snd_soc_register_codec(&i2c->dev, &ssm3515_codec_driver, + &ssm3515_dai, 1); + } else { + ret = snd_soc_register_codec(&i2c->dev, + &ssm3515_codec_nocomponent, &ssm3515_dai, 1); + } + if (ret) + return ret; + return 0; +} + +static int ssm3515_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id ssm3515_dt_ids[] = { + { .compatible = "adi, ssm3515", }, + { } +}; +MODULE_DEVICE_TABLE(of, ssm3515_dt_ids); +#endif + +static const struct i2c_device_id ssm3515_i2c_ids[] = { + { " ssm3515", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm3515_i2c_ids); + + +static struct i2c_driver ssm3515_driver = { + .driver = { + .name = "ssm3515", + .of_match_table = of_match_ptr(ssm3515_dt_ids), + }, + .probe = ssm3515_i2c_probe, + .remove = ssm3515_i2c_remove, + .id_table = ssm3515_i2c_ids, +}; +module_i2c_driver(ssm3515_driver); + +MODULE_DESCRIPTION("ASoC SSM3515 driver"); +MODULE_AUTHOR("Anatol Pomozov "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/amlogic/ssm3525.c b/sound/soc/codecs/amlogic/ssm3525.c new file mode 100644 index 0000000..6180788 --- /dev/null +++ b/sound/soc/codecs/amlogic/ssm3525.c @@ -0,0 +1,602 @@ +/* + * SSM4567 amplifier audio driver + * + * Copyright 2014 Google Chromium project. + * Author: Anatol Pomozov + * + * Based on code copyright/by: + * Copyright 2013 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSM3525_REG_VENDER_ID 0x00 +#define SSM3525_REG_DEVICE_ID1 0x01 +#define SSM3525_REG_DEVICE_ID2 0x02 +#define SSM3525_REG_REVISION_ID 0x03 +#define SSM3525_REG_REGULATOR_ENABLE 0x04 +#define SSM3525_REG_AMP_SNS_CTRL 0x05 +#define SSM3525_REG_DAC_CTRL 0x06 +#define SSM3525_REG_DAC_VOLUME 0x07 +#define SSM3525_REG_LIMITER_CTRL_1 0x08 +#define SSM3525_REG_LIMITER_CTRL_2 0x09 +#define SSM3525_REG_LIMITER_CTRL_3 0x0A +#define SSM3525_REG_VBAT_LIM_CTRL_1 0x0B +#define SSM3525_REG_VBAT_LIM_CTRL_2 0x0C +#define SSM3525_REG_VBAT_LIM_CTRL_3 0x0D +#define SSM3525_REG_LIMITER_LINK 0x0E +#define SSM3525_REG_DAC_FLIP 0x0F +#define SSM3525_REG_FAULT_CTRL 0x10 +#define SSM3525_REG_STATUS 0x11 +#define SSM3525_REG_TEMP 0x12 +#define SSM3525_REG_BATTERY_V_OUT 0x13 + +#define SSM3525_REG_POWER_CTRL 0x20 +#define SSM3525_REG_PDM_CTRL 0x21 +#define SSM3525_REG_SAI_CTRL_1 0x22 +#define SSM3525_REG_SAI_CTRL_2 0x23 +#define SSM3525_REG_SAI_PLACEMENT_1 0x24 +#define SSM3525_REG_SAI_PLACEMENT_2 0x25 +#define SSM3525_REG_SAI_PLACEMENT_3 0x26 +#define SSM3525_REG_SAI_PLACEMENT_4 0x27 +#define SSM3525_REG_SAI_PLACEMENT_5 0x28 +#define SSM3525_REG_SAI_PLACEMENT_6 0x29 + +#define SSM3525_REG_AGC_PLACEMENT_1 0x2A +#define SSM3525_REG_AGC_PLACEMENT_2 0x2B +#define SSM3525_REG_AGC_PLACEMENT_3 0x2C +#define SSM3525_REG_AGC_PLACEMENT_4 0x2D +#define SSM3525_REG_SOFT_RESET 0x2E + +/* POWER_CTRL */ +#define SSM3525_POWER_APWDN_EN BIT(7) +#define SSM3525_POWER_BSNS_PWDN BIT(6) +#define SSM3525_POWER_VSNS_PWDN BIT(5) +#define SSM3525_POWER_ISNS_PWDN BIT(4) +#define SSM3525_POWER_BOOST_PWDN BIT(3) +#define SSM3525_POWER_AMP_PWDN BIT(2) +#define SSM3525_POWER_VBAT_ONLY BIT(1) +#define SSM3525_POWER_SPWDN BIT(0) + +/* DAC_CTRL */ +#define SSM3525_DAC_HV BIT(7) +#define SSM3525_DAC_MUTE BIT(6) +#define SSM3525_DAC_HPF BIT(5) +#define SSM3525_DAC_LPM BIT(4) +#define SSM3525_DAC_FS_MASK 0x7 +#define SSM3525_DAC_FS_8000_12000 0x0 +#define SSM3525_DAC_FS_16000_24000 0x1 +#define SSM3525_DAC_FS_32000_48000 0x2 +#define SSM3525_DAC_FS_64000_96000 0x3 +#define SSM3525_DAC_FS_128000_192000 0x4 + +/* SAI_CTRL_1 */ +#define SSM3525_SAI_CTRL_1_BCLK BIT(6) +#define SSM3525_SAI_CTRL_1_TDM_BLCKS_MASK (0x3 << 4) +#define SSM3525_SAI_CTRL_1_TDM_BLCKS_32 (0x0 << 4) +#define SSM3525_SAI_CTRL_1_TDM_BLCKS_48 (0x1 << 4) +#define SSM3525_SAI_CTRL_1_TDM_BLCKS_64 (0x2 << 4) +#define SSM3525_SAI_CTRL_1_FSYNC BIT(3) +#define SSM3525_SAI_CTRL_1_LJ BIT(2) +#define SSM3525_SAI_CTRL_1_TDM BIT(1) +#define SSM3525_SAI_CTRL_1_PDM BIT(0) + +/* SAI_CTRL_2 */ +#define SSM3525_SAI_CTRL_2_AUTO_SLOT BIT(3) +#define SSM3525_SAI_CTRL_2_TDM_SLOT_MASK 0x7 +#define SSM3525_SAI_CTRL_2_TDM_SLOT(x) (x) + +static int device_num; +struct ssm3525 *g_ssm3525; + +struct ssm3525 { + struct regmap *regmap; + unsigned int slots_mask; + const char *amp_cfg; + int total_num; +}; + +static const struct reg_default ssm3525_reg_defaults[] = { + { SSM3525_REG_REGULATOR_ENABLE, 0x01 }, + { SSM3525_REG_AMP_SNS_CTRL, 0x23 }, + { SSM3525_REG_DAC_CTRL, 0x22 }, + { SSM3525_REG_SAI_PLACEMENT_1, 0x01 }, + { SSM3525_REG_SAI_PLACEMENT_2, 0x20 }, + { SSM3525_REG_SAI_PLACEMENT_3, 0x28 }, + { SSM3525_REG_SAI_PLACEMENT_4, 0x28 }, + { SSM3525_REG_SAI_PLACEMENT_5, 0x08 }, + { SSM3525_REG_SAI_PLACEMENT_6, 0x08 }, +}; + + +static bool ssm3525_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM3525_REG_VENDER_ID ... SSM3525_REG_SOFT_RESET: + return true; + default: + return false; + } + +} + +static bool ssm3525_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM3525_REG_REGULATOR_ENABLE ... SSM3525_REG_FAULT_CTRL: + case SSM3525_REG_POWER_CTRL ... SSM3525_REG_SOFT_RESET: + /*The datasheet states that soft reset register is read-only,*/ + /*but logically it is write-only. */ + return true; + default: + return false; + } +} + +static bool ssm3525_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM3525_REG_VENDER_ID ... SSM3525_REG_SOFT_RESET: + return true; + default: + return false; + } +} + +static int WL1_amplifier_power_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = 0; + int ret; + int i; + struct ssm3525 *ssm3525 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3525[i].amp_cfg, "WL1")) { + ssm3525 = g_ssm3525+i; + break; + } + } + ret = regmap_read(ssm3525->regmap, SSM3525_REG_POWER_CTRL, &value); + if (ret) + return ret; + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int WL1_amplifier_power_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value, ret, i; + struct ssm3525 *ssm3525 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3525[i].amp_cfg, "WL1")) { + ssm3525 = g_ssm3525+i; + break; + } + } + value = ucontrol->value.integer.value[0]; + ret = regmap_write(ssm3525->regmap, SSM3525_REG_POWER_CTRL, value); + if (ret) + return ret; + return 0; +} + +static int WR1_amplifier_power_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = 0; + int ret, i; + struct ssm3525 *ssm3525 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3525[i].amp_cfg, "WR1")) { + ssm3525 = g_ssm3525+i; + break; + } + } + ret = regmap_read(ssm3525->regmap, SSM3525_REG_POWER_CTRL, &value); + if (ret) + return ret; + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int WR1_amplifier_power_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value, ret, i; + struct ssm3525 *ssm3525 = NULL; + + for (i = 0; i < device_num; i++) { + if (!strcmp(g_ssm3525[i].amp_cfg, "WR1")) { + ssm3525 = g_ssm3525+i; + break; + } + } + value = ucontrol->value.integer.value[0]; + ret = regmap_write(ssm3525->regmap, SSM3525_REG_POWER_CTRL, value); + if (ret) + return ret; + return 0; +} + +static struct snd_kcontrol_new ssm3525_snd_controls[] = { + SOC_SINGLE_EXT("WL1 Amp Power Control", SND_SOC_NOPM, 0, 0xff, 0, + WL1_amplifier_power_get, WL1_amplifier_power_put), + SOC_SINGLE_EXT("WR1 Amp Power Control", SND_SOC_NOPM, 0, 0xff, 0, + WR1_amplifier_power_get, WR1_amplifier_power_put), +}; +static int ssm3525_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 ssm3525 *ssm3525 = snd_soc_codec_get_drvdata(codec); + unsigned int rate = params_rate(params); + unsigned int dacfs; + + if (rate >= 8000 && rate <= 12000) + dacfs = SSM3525_DAC_FS_8000_12000; + else if (rate >= 16000 && rate <= 24000) + dacfs = SSM3525_DAC_FS_16000_24000; + else if (rate >= 32000 && rate <= 48000) + dacfs = SSM3525_DAC_FS_32000_48000; + else if (rate >= 64000 && rate <= 96000) + dacfs = SSM3525_DAC_FS_64000_96000; + else if (rate >= 128000 && rate <= 192000) + dacfs = SSM3525_DAC_FS_128000_192000; + else + return -EINVAL; + + return regmap_update_bits(ssm3525->regmap, SSM3525_REG_DAC_CTRL, + SSM3525_DAC_FS_MASK, dacfs); +} + +static int ssm3525_mute(struct snd_soc_dai *dai, int mute) +{ + struct ssm3525 *ssm3525 = snd_soc_codec_get_drvdata(dai->codec); + unsigned int val; + + val = mute ? SSM3525_DAC_MUTE : 0; + return regmap_update_bits(ssm3525->regmap, SSM3525_REG_DAC_CTRL, + SSM3525_DAC_MUTE, val); +} + +static int ssm3525_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct ssm3525 *ssm3525 = snd_soc_dai_get_drvdata(dai); + unsigned int blcks; + int slot; + int ret; + + tx_mask = ssm3525->slots_mask; + + if (tx_mask == 0) + return -EINVAL; + + slot = __ffs(tx_mask); + if (tx_mask != BIT(slot)) + return -EINVAL; + + switch (width) { + case 32: + blcks = SSM3525_SAI_CTRL_1_TDM_BLCKS_32; + break; + case 48: + blcks = SSM3525_SAI_CTRL_1_TDM_BLCKS_48; + break; + case 64: + blcks = SSM3525_SAI_CTRL_1_TDM_BLCKS_64; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(ssm3525->regmap, SSM3525_REG_SAI_CTRL_2, + SSM3525_SAI_CTRL_2_AUTO_SLOT | SSM3525_SAI_CTRL_2_TDM_SLOT_MASK, + SSM3525_SAI_CTRL_2_TDM_SLOT(slot)); + if (ret) + return ret; + + return regmap_update_bits(ssm3525->regmap, SSM3525_REG_SAI_CTRL_1, + SSM3525_SAI_CTRL_1_TDM_BLCKS_MASK, blcks); +} + +static int ssm3525_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ +#if 0 + struct ssm3525 *ssm3525 = snd_soc_dai_get_drvdata(dai); + unsigned int ctrl1 = 0; + bool invert_fclk; + + + fmt = fmt | SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CONT; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + invert_fclk = false; + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1 |= SSM3525_SAI_CTRL_1_BCLK; + invert_fclk = false; + break; + case SND_SOC_DAIFMT_NB_IF: + ctrl1 |= SSM3525_SAI_CTRL_1_FSYNC; + invert_fclk = true; + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl1 |= SSM3525_SAI_CTRL_1_BCLK; + invert_fclk = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1 |= SSM3525_SAI_CTRL_1_LJ; + invert_fclk = !invert_fclk; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1 |= SSM3525_SAI_CTRL_1_TDM; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1 |= SSM3525_SAI_CTRL_1_TDM | SSM3525_SAI_CTRL_1_LJ; + break; + case SND_SOC_DAIFMT_PDM: + ctrl1 |= SSM3525_SAI_CTRL_1_PDM; + break; + default: + return -EINVAL; + } + + if (invert_fclk) + ctrl1 |= SSM3525_SAI_CTRL_1_FSYNC; + + return regmap_update_bits(ssm3525->regmap, SSM3525_REG_SAI_CTRL_1, + SSM3525_SAI_CTRL_1_BCLK | + SSM3525_SAI_CTRL_1_FSYNC | + SSM3525_SAI_CTRL_1_LJ | + SSM3525_SAI_CTRL_1_TDM | + SSM3525_SAI_CTRL_1_PDM, + ctrl1); +#endif + return 0; +} + +static int ssm3525_set_power(struct ssm3525 *ssm3525, bool enable) +{ + int ret = 0; + + if (!enable) { + ret = regmap_update_bits(ssm3525->regmap, + SSM3525_REG_POWER_CTRL, + SSM3525_POWER_SPWDN, SSM3525_POWER_SPWDN); + } + + + if (enable) { + ret = regmap_write(ssm3525->regmap, SSM3525_REG_SOFT_RESET, + 0x00); + if (ret) + return ret; + + ret = regmap_update_bits(ssm3525->regmap, + SSM3525_REG_POWER_CTRL, + SSM3525_POWER_SPWDN, 0x00); + } + + return ret; +} + +static int ssm3525_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct ssm3525 *ssm3525 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) + ret = ssm3525_set_power(ssm3525, true); + break; + case SND_SOC_BIAS_OFF: + ret = ssm3525_set_power(ssm3525, false); + break; + } + + return ret; +} + +static const struct snd_soc_dai_ops ssm3525_dai_ops = { + .hw_params = ssm3525_hw_params, + .digital_mute = ssm3525_mute, + .set_fmt = ssm3525_set_dai_fmt, + .set_tdm_slot = ssm3525_set_tdm_slot, +}; + +static struct snd_soc_dai_driver ssm3525_dai = { + .name = "ssm3525-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32, + }, + .capture = { + .stream_name = "Capture Sense", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32, + }, + .ops = &ssm3525_dai_ops, +}; + +static struct snd_soc_codec_driver ssm3525_codec_nocomponent = { + .set_bias_level = ssm3525_set_bias_level, + .idle_bias_off = true, +}; + +static struct snd_soc_codec_driver ssm3525_codec_driver = { + .set_bias_level = ssm3525_set_bias_level, + .idle_bias_off = true, + + .component_driver = { + .controls = ssm3525_snd_controls, + .num_controls = ARRAY_SIZE(ssm3525_snd_controls), + }, +}; + +static const struct regmap_config ssm3525_regmap_config = { + .val_bits = 8, + .reg_bits = 8, + + .max_register = SSM3525_REG_SOFT_RESET, + .readable_reg = ssm3525_readable_reg, + .writeable_reg = ssm3525_writeable_reg, + .volatile_reg = ssm3525_volatile_reg, +/* + * TODO: Activate register map cache. It has been temporarily deactivated to + * eliminate a potential source of trouble during driver development. + */ + /*.cache_type = REGCACHE_RBTREE,*/ + /*.reg_defaults = ssm3525_reg_defaults,*/ + /*.num_reg_defaults = ARRAY_SIZE(ssm3525_reg_defaults),*/ +}; + +static int ssm3525_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ssm3525 *ssm3525; + int ret, i; + + device_num++; + ssm3525 = devm_kzalloc(&i2c->dev, sizeof(*ssm3525), GFP_KERNEL); + if (ssm3525 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, ssm3525); + + ssm3525->regmap = devm_regmap_init_i2c(i2c, &ssm3525_regmap_config); + if (IS_ERR(ssm3525->regmap)) + return PTR_ERR(ssm3525->regmap); + + ret = regmap_write(ssm3525->regmap, SSM3525_REG_SOFT_RESET, 0x01); + if (ret) + return ret; + ret = regmap_write(ssm3525->regmap, SSM3525_REG_SOFT_RESET, 0x00); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(ssm3525_reg_defaults); i++) { + ret = regmap_write(ssm3525->regmap, + ssm3525_reg_defaults[i].reg, + ssm3525_reg_defaults[i].def); + if (ret) + return ret; + } +#ifdef CONFIG_OF + if (i2c->dev.of_node) { + const struct device_node *np = i2c->dev.of_node; + u32 val; + + if (of_property_read_u32(np, "slots_mask", &val) >= 0) + ssm3525->slots_mask = val; + + of_property_read_string(np, "amp_config", &ssm3525->amp_cfg); + + if (of_property_read_u32(np, "total_num", &val) >= 0) + ssm3525->total_num = val; + } +#endif + if (device_num == 1) { + g_ssm3525 = kzalloc(sizeof(struct ssm3525) * + ssm3525->total_num, GFP_KERNEL); + if (g_ssm3525 == NULL) + return -ENOMEM; + } + g_ssm3525[device_num-1] = *ssm3525; + + if (device_num == ssm3525->total_num) { + ret = snd_soc_register_codec(&i2c->dev, &ssm3525_codec_driver, + &ssm3525_dai, 1); + + } else { + ret = snd_soc_register_codec(&i2c->dev, + &ssm3525_codec_nocomponent, + &ssm3525_dai, 1); + } + if (ret) + return ret; + return 0; +} + +static int ssm3525_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id ssm3525_dt_ids[] = { + { .compatible = "adi, ssm3525", }, + { } +}; +MODULE_DEVICE_TABLE(of, ssm3525_dt_ids); +#endif +static const struct i2c_device_id ssm3525_i2c_ids[] = { + { "ssm3525", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm3525_i2c_ids); + + +static struct i2c_driver ssm3525_driver = { + .driver = { + .name = "ssm3525", + .of_match_table = of_match_ptr(ssm3525_dt_ids), + }, + .probe = ssm3525_i2c_probe, + .remove = ssm3525_i2c_remove, + .id_table = ssm3525_i2c_ids, +}; +module_i2c_driver(ssm3525_driver); + +MODULE_DESCRIPTION("ASoC SSM3525 driver"); +MODULE_AUTHOR("Anatol Pomozov "); +MODULE_LICENSE("GPL"); -- 2.7.4