audio: add pcm186x, ssm3515, ssm3525 codec driver

PD#145486: audio: add codec driver

Change-Id: Ief27ff9892ffabba81f676f4bed2ec6b4ead74f5
Signed-off-by: Peipeng Zhao <peipeng.zhao@amlogic.com>
This commit is contained in:
Peipeng Zhao
2017-05-12 18:38:18 +08:00
committed by Victor Wan
parent 7ca387861a
commit c1403bcff9
12 changed files with 3125 additions and 12 deletions

View File

@@ -13929,4 +13929,13 @@ AMLOGIC Security Support
M: Peifu Jiang <peifu.jiang@amlogic.com>
F: include/linux/amlogic/meson-secure.h
F: arch/arm/mach-meson/meson-smc.S
F: arch/arm/mach-meson/meson-secure.c
F: arch/arm/mach-meson/meson-secure.c
AMLOGIC Audio codec driver
M: Peipeng Zhao <peipeng.zhao@amlogic.com>
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

View File

@@ -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

View File

@@ -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),

View File

@@ -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:

View File

@@ -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

View File

@@ -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
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

View File

@@ -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 <dannenberg@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#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 <dannenberg@ti.com>");
MODULE_LICENSE("GPL");

View File

@@ -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 <dannenberg@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#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 <dannenberg@ti.com>");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load Diff

View File

@@ -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 <dannenberg@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* 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 <linux/pm.h>
#include <linux/regmap.h>
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_ */

View File

@@ -0,0 +1,652 @@
/*
* SSM3515 amplifier audio driver
*
* Copyright 2014 Google Chromium project.
* Author: Anatol Pomozov <anatol@chromium.org>
*
* Based on code copyright/by:
* Copyright 2013 Analog Devices Inc.
*
* Licensed under the GPL-2.
*/
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#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 <anatol@chromium.org>");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,602 @@
/*
* SSM4567 amplifier audio driver
*
* Copyright 2014 Google Chromium project.
* Author: Anatol Pomozov <anatol@chromium.org>
*
* Based on code copyright/by:
* Copyright 2013 Analog Devices Inc.
*
* Licensed under the GPL-2.
*/
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#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 <anatol@chromium.org>");
MODULE_LICENSE("GPL");