On 02/20/2017 07:01 PM, simon.ho.cnxt@gmail.com wrote:
From: Simon Ho simon.ho@conexant.com
Initial commit of Conexant CX20921/CX20924 I2S Audio DSP driver
Signed-off-by: Simon Ho simon.ho@conexant.com
sound/soc/codecs/Kconfig | 17 ++ sound/soc/codecs/Makefile | 6 + sound/soc/codecs/cx2092x-i2c.c | 54 ++++++ sound/soc/codecs/cx2092x-spi.c | 57 ++++++ sound/soc/codecs/cx2092x.c | 411 +++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/cx2092x.h | 26 +++ 6 files changed, 571 insertions(+) create mode 100644 sound/soc/codecs/cx2092x-i2c.c create mode 100644 sound/soc/codecs/cx2092x-spi.c create mode 100644 sound/soc/codecs/cx2092x.c create mode 100644 sound/soc/codecs/cx2092x.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e49e9da..a98d3c2 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -63,6 +63,8 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS47L24 if MFD_CS47L24 select SND_SOC_CS53L30 if I2C select SND_SOC_CX20442 if TTY
- select SND_SOC_CX2092X_I2C if I2C
- select SND_SOC_CX2092X_SPI if SPI_MASTER select SND_SOC_DA7210 if SND_SOC_I2C_AND_SPI select SND_SOC_DA7213 if I2C select SND_SOC_DA7218 if I2C
@@ -491,6 +493,21 @@ config SND_SOC_CX20442 tristate depends on TTY
+config SND_SOC_CX2092X
- tristate
+config SND_SOC_CX2092X_I2C
- tristate "Conexant CX20921/CX2094 CODEC (I2C)"
- depends on I2C
- select SND_SOC_CX2092X
- select REGMAP_I2C
+config SND_SOC_CX2092X_SPI
- tristate "Conexant CX20921/CX2094 CODEC (SPI)"
- depends on SPI_MASTER
- select SND_SOC_CX2092X
- select REGMAP_SPI
- config SND_SOC_JZ4740_CODEC select REGMAP_MMIO tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 1796cb9..e30a398 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -56,6 +56,9 @@ snd-soc-cs4349-objs := cs4349.o snd-soc-cs47l24-objs := cs47l24.o snd-soc-cs53l30-objs := cs53l30.o snd-soc-cx20442-objs := cx20442.o +snd-soc-cx2092x-objs := cx2092x.o +snd-soc-cx2092x-i2c-objs := cx2092x-i2c.o +snd-soc-cx2092x-spi-objs := cx2092x-spi.o snd-soc-da7210-objs := da7210.o snd-soc-da7213-objs := da7213.o snd-soc-da7218-objs := da7218.o @@ -286,6 +289,9 @@ obj-$(CONFIG_SND_SOC_CS4349) += snd-soc-cs4349.o obj-$(CONFIG_SND_SOC_CS47L24) += snd-soc-cs47l24.o obj-$(CONFIG_SND_SOC_CS53L30) += snd-soc-cs53l30.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o +obj-$(CONFIG_SND_SOC_CX2092X) += snd-soc-cx2092x.o +obj-$(CONFIG_SND_SOC_CX2092X_I2C) += snd-soc-cx2092x-i2c.o +obj-$(CONFIG_SND_SOC_CX2092X_SPI) += snd-soc-cx2092x-spi.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o obj-$(CONFIG_SND_SOC_DA7213) += snd-soc-da7213.o obj-$(CONFIG_SND_SOC_DA7218) += snd-soc-da7218.o diff --git a/sound/soc/codecs/cx2092x-i2c.c b/sound/soc/codecs/cx2092x-i2c.c new file mode 100644 index 0000000..a07800e --- /dev/null +++ b/sound/soc/codecs/cx2092x-i2c.c @@ -0,0 +1,54 @@ +/*
- cx2092x-i2c.c -- CX20921 and CX20924 I2C Audio driver
- Copyright: (C) 2017 Conexant Systems, Inc.
- This is based on Alexander Sverdlin's CS4271 driver code.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- */
+#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include "cx2092x.h"
+static int cx2092x_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
+{
- return cx2092x_dev_probe(&i2c->dev,
devm_regmap_init_i2c(i2c, &cx2092x_regmap_config));
+} +static int cx2092x_i2c_remove(struct i2c_client *client) +{
- snd_soc_unregister_codec(&client->dev);
- return 0;
+}
+static const struct i2c_device_id cx2092x_i2c_id[] = {
- {"cx20921", 0},
- {"cx20924", 0},
- {}
+};
+MODULE_DEVICE_TABLE(i2c, cx2092x_i2c_id);
+static struct i2c_driver cx2092x_i2c_driver = {
- .driver = {
.name = "cx2092x",
.owner = THIS_MODULE,
field not needed
.of_match_table = of_match_ptr(cx2092x_dt_ids),
- },
- .id_table = cx2092x_i2c_id,
- .probe = cx2092x_i2c_probe,
- .remove = cx2092x_i2c_remove,
+}; +module_i2c_driver(cx2092x_i2c_driver);
+MODULE_DESCRIPTION("ASoC CX2092X I2C Driver"); +MODULE_AUTHOR("Simon Ho simon.ho@conexant.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cx2092x-spi.c b/sound/soc/codecs/cx2092x-spi.c new file mode 100644 index 0000000..3723909 --- /dev/null +++ b/sound/soc/codecs/cx2092x-spi.c @@ -0,0 +1,57 @@ +/*
- cx2092x-spi.c -- CX20921 and CX20924 SPI Audio driver
- Copyright: (C) 2017 Conexant Systems, Inc.
- This is based on Alexander Sverdlin's CS4271 driver code.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- */
+#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include "cx2092x.h"
+static int cx2092x_spi_probe(struct spi_device *spi) +{
- struct regmap_config config;
- config = cx2092x_regmap_config;
- config.write_flag_mask = 0x81;
human-readable value to explain what this does?
- return cx2092x_dev_probe(&spi->dev,
devm_regmap_init_spi(spi, &cx2092x_regmap_config));
+}
+static int cx2092x_spi_remove(struct spi_device *spi) +{
- snd_soc_unregister_codec(&spi->dev);
- return 0;
+}
+static const struct spi_device_id cx2092x_spi_id[] = {
- {"cx20921", 0},
- {"cx20924", 0},
- {}
+};
+static struct spi_driver cx2092x_spi_driver = {
- .driver = {
.name = "cx2092x",
.of_match_table = of_match_ptr(cx2092x_dt_ids),
- },
- .id_table = cx2092x_spi_id,
- .probe = cx2092x_spi_probe,
- .remove = cx2092x_spi_remove,
+};
+module_spi_driver(cx2092x_spi_driver);
+MODULE_DESCRIPTION("ASoC CX2092X SPI Driver"); +MODULE_AUTHOR("Simon Ho simon.ho@conexant.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cx2092x.c b/sound/soc/codecs/cx2092x.c new file mode 100644 index 0000000..a15e3f9 --- /dev/null +++ b/sound/soc/codecs/cx2092x.c @@ -0,0 +1,411 @@ +/*
- cx2092x.c -- CX20921 and CX20924 ALSA SoC Audio driver
- Copyright: (C) 2017 Conexant Systems, Inc.
- This is based on Alexander Sverdlin's CS4271 driver code.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- */
+#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/gpio/consumer.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include "cx2092x.h"
+#define CX2092X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
+#define CX2092X_CAPE_ID(a, b, c, d) (((a) - 0x20) << 8 | \
((b) - 0x20) << 14| \
((c) - 0x20) << 20| \
((d) - 0x20) << 26)
+#define CX2092X_ID2CH_A(id) (((((unsigned int)(id)) >> 8) & 0x3f) + 0x20) +#define CX2092X_ID2CH_B(id) (((((unsigned int)(id)) >> 14) & 0x3f) + 0x20) +#define CX2092X_ID2CH_C(id) (((((unsigned int)(id)) >> 20) & 0x3f) + 0x20) +#define CX2092X_ID2CH_D(id) (((((unsigned int)(id)) >> 26) & 0x3f) + 0x20)
+#define CX2092X_CONTROL(xname, xinfo, xget, xput, xaccess) { \
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
- .access = xaccess, .info = xinfo, .get = xget, .put = xput, \
- }
+#define CX2092X_CMD_GET(item) ((item) | 0x0100) +#define CX2092X_CMD_SIZE 13
+/*
- Defines the command format which is used to communicate with cx2092x device.
- */
+struct cx2092x_cmd {
- int num_32b_words:16; /* Indicates how many data to be sent.
* If operation is successful, this will
* be updated with the number of returned
* data in word. one word == 4 bytes.
*/
- u32 command_id:15;
- u32 reply:1; /* The device will set this flag once
* the operation is complete.
*/
- u32 app_module_id;
- u32 data[CX2092X_CMD_SIZE]; /* Used for storing parameters and
* receiving the returned data from
* device.
*/
+};
+/* codec private data*/ +struct cx2092x_priv {
- struct device *dev;
- struct regmap *regmap;
- struct gpio_desc *gpiod_reset;
- struct cx2092x_cmd cmd;
- int cmd_res;
+};
+/*
- This functions takes cx2092x_cmd structure as input and output parameters
- to communicate CX2092X. If operation is successfully, it returns number of
- returned data and stored the returned data in "cmd->data" array.
- Otherwise, it returns the error code.
- */
+static int cx2092x_sendcmd(struct snd_soc_codec *codec,
struct cx2092x_cmd *cmd)
+{
- struct cx2092x_priv *cx2092x = snd_soc_codec_get_drvdata(codec);
- int ret = 0;
- int num_32b_words = cmd->num_32b_words;
- unsigned long time_out;
- u32 *i2c_data = (u32 *)cmd;
- int size = num_32b_words + 2;
- /* calculate how many WORD that will be wrote to device*/
written to
- cmd->num_32b_words = cmd->command_id & CX2092X_CMD_GET(0) ?
CX2092X_CMD_SIZE : num_32b_words;
- /* write all command data except fo frist 4 bytes*/
first
- ret = regmap_bulk_write(cx2092x->regmap, 4, &i2c_data[1], size - 1);
shouldn't size be set on the actual cmd->num_32b_words, here you are using the value prior to the test?
- if (ret < 0) {
dev_err(cx2092x->dev, "Failed to write command data\n");
goto LEAVE;
- }
- /* write first 4 bytes command data*/
- ret = regmap_bulk_write(cx2092x->regmap, 0, i2c_data, 1);
- if (ret < 0) {
dev_err(cx2092x->dev, "Failed to write command\n");
goto LEAVE;
- }
- /* continuously read the first bytes data from device until
* either timeout or the flag 'reply' is set.
*/
- time_out = msecs_to_jiffies(2000);
- time_out += jiffies;
- do {
regmap_bulk_read(cx2092x->regmap, 0, &i2c_data[0], 1);
if (cmd->reply == 1)
break;
mdelay(10);
- } while (!time_after(jiffies, time_out));
- if (cmd->reply == 1) {
/* check if there is returned data. If yes copy the returned
* data to cmd->data array
*/
if (cmd->num_32b_words > 0)
regmap_bulk_read(cx2092x->regmap, 8, &i2c_data[2],
cmd->num_32b_words);
/* return error code if operation is not successful.*/
else if (cmd->num_32b_words < 0)
dev_err(cx2092x->dev, "SendCmd failed, err = %d\n",
cmd->num_32b_words);
ret = cmd->num_32b_words;
- } else {
dev_err(cx2092x->dev, "SendCmd timeout\n");
ret = -EBUSY;
- }
+LEAVE:
- return ret;
+}
+static int cmd_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
+{
- uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
- uinfo->count = sizeof(struct cx2092x_cmd);
- return 0;
+}
+static int cmd_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
- struct cx2092x_priv *cx2092x =
snd_soc_component_get_drvdata(component);
- memcpy(ucontrol->value.bytes.data, &cx2092x->cmd,
sizeof(cx2092x->cmd));
- return 0;
+}
+static int cmd_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
- struct cx2092x_priv *cx2092x = snd_soc_component_get_drvdata(component);
- struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
- memcpy(&cx2092x->cmd, ucontrol->value.bytes.data,
sizeof(cx2092x->cmd));
- cx2092x->cmd_res = cx2092x_sendcmd(codec, &cx2092x->cmd);
- if (cx2092x->cmd_res < 0)
dev_err(codec->dev, "Failed to send cmd, ret = %d\n",
cx2092x->cmd_res);
- return cx2092x->cmd_res < 0 ? cx2092x->cmd_res : 0;
return cmd_res in if block and return 0 otherwise?
+}
+static int mode_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
+{
- uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
- uinfo->count = 4;
- return 0;
+}
+static int mode_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
- struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
- struct cx2092x_cmd cmd;
- int ret = 0;
- cmd.command_id = 0x12f; /*CX2092X_CMD_GET(SOS_RESOURCE);*/
- cmd.reply = 0;
- cmd.app_module_id = CX2092X_CAPE_ID('S', 'O', 'S', ' ');
SOS :-) ?
- cmd.num_32b_words = 1;
- cmd.data[0] = CX2092X_CAPE_ID('C', 'T', 'R', 'L');
- ret = cx2092x_sendcmd(codec, &cmd);
- if (ret <= 0)
dev_err(codec->dev, "Failed to get current mode, ret = %d\n",
ret);
- else {
ucontrol->value.bytes.data[0] = CX2092X_ID2CH_A(cmd.data[0]);
ucontrol->value.bytes.data[1] = CX2092X_ID2CH_B(cmd.data[0]);
ucontrol->value.bytes.data[2] = CX2092X_ID2CH_C(cmd.data[0]);
ucontrol->value.bytes.data[3] = CX2092X_ID2CH_D(cmd.data[0]);
dev_dbg(codec->dev, "Current mode = %c%c%c%c\n",
ucontrol->value.bytes.data[0],
ucontrol->value.bytes.data[1],
ucontrol->value.bytes.data[2],
ucontrol->value.bytes.data[3]);
ret = 0;
- }
- return ret;
+}
+static int mode_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
- struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
- struct cx2092x_cmd cmd;
- int ret = -1;
- cmd.command_id = 4;
- cmd.reply = 0;
- cmd.app_module_id = CX2092X_CAPE_ID('C', 'T', 'R', 'L');
- cmd.num_32b_words = 1;
- cmd.data[0] = CX2092X_CAPE_ID(ucontrol->value.bytes.data[0],
ucontrol->value.bytes.data[1],
ucontrol->value.bytes.data[2],
ucontrol->value.bytes.data[3]);
- ret = cx2092x_sendcmd(codec, &cmd);
- if (ret < 0) {
dev_err(codec->dev, "Failed to set mode, ret =%d\n", ret);
- } else {
dev_dbg(codec->dev, "Set mode successfully, ret = %d\n", ret);
ret = 0;
- }
- return ret;
+}
+static const struct snd_kcontrol_new cx2092x_snd_controls[] = {
- CX2092X_CONTROL("SendCmd", cmd_info, cmd_get, cmd_put,
SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE|
SNDRV_CTL_ELEM_ACCESS_VOLATILE),
- CX2092X_CONTROL("Mode", mode_info, mode_get, mode_put,
SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE|
SNDRV_CTL_ELEM_ACCESS_VOLATILE),
+};
+static const struct snd_soc_dapm_widget cx2092x_dapm_widgets[] = {
- SND_SOC_DAPM_AIF_OUT("Mic AIF", "Capture", 0,
SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_INPUT("MIC"),
+};
+static const struct snd_soc_dapm_route cx2092x_intercon[] = {
- {"Mic AIF", NULL, "MIC"},
+};
+static struct snd_soc_dai_driver soc_codec_cx2092x_dai[] = {
- {
.name = "cx2092x-aif",
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.formats = CX2092X_FORMATS,
},
- },
- {
.name = "cx2092x-dsp",
.capture = {
.stream_name = "AEC Ref",
explain what AEC ref means if there is no playback?
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.formats = CX2092X_FORMATS,
},
- },
+};
+static int cx2092x_reset(struct snd_soc_codec *codec) +{
- struct cx2092x_priv *cx2092x = snd_soc_codec_get_drvdata(codec);
- if (cx2092x->gpiod_reset) {
gpiod_set_value_cansleep(cx2092x->gpiod_reset, 0);
mdelay(10);
gpiod_set_value_cansleep(cx2092x->gpiod_reset, 1);
- }
- return 0;
+}
+const struct of_device_id cx2092x_dt_ids[] = {
- { .compatible = "cnxt,cx20921", },
- { .compatible = "cnxt,cx20924", },
- { }
+}; +EXPORT_SYMBOL_GPL(cx2092x_dt_ids); +MODULE_DEVICE_TABLE(of, cx2092x_dt_ids);
+static int cx2092x_probe(struct snd_soc_codec *codec) +{
- return cx2092x_reset(codec);
+}
+static int cx2092x_remove(struct snd_soc_codec *codec) +{
- struct cx2092x_priv *cx2092x = snd_soc_codec_get_drvdata(codec);
- if (cx2092x->gpiod_reset)
/* Set codec to the reset state */
gpiod_set_value_cansleep(cx2092x->gpiod_reset, 0);
- return 0;
+}
+static const struct snd_soc_codec_driver soc_codec_driver_cx2092x = {
- .probe = cx2092x_probe,
- .remove = cx2092x_remove,
- .component_driver = {
.controls = cx2092x_snd_controls,
.num_controls = ARRAY_SIZE(cx2092x_snd_controls),
.dapm_widgets = cx2092x_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(cx2092x_dapm_widgets),
.dapm_routes = cx2092x_intercon,
.num_dapm_routes = ARRAY_SIZE(cx2092x_intercon),
- },
+}; +EXPORT_SYMBOL_GPL(soc_codec_driver_cx2092x);
+static bool cx2092x_volatile_register(struct device *dev, unsigned int reg) +{
- return true; /*all register are volatile*/
+}
+const struct regmap_config cx2092x_regmap_config = {
- .reg_bits = 16,
- .val_bits = 32,
- .reg_stride = 4,
- .max_register = CX2092X_REG_MAX,
- .cache_type = REGCACHE_NONE,
- .volatile_reg = cx2092x_volatile_register,
- .val_format_endian = REGMAP_ENDIAN_NATIVE,
+}; +EXPORT_SYMBOL_GPL(cx2092x_regmap_config);
+int cx2092x_dev_probe(struct device *dev, struct regmap *regmap) +{
- struct cx2092x_priv *cx2092x;
- int ret;
- if (IS_ERR(regmap))
return PTR_ERR(regmap);
- cx2092x = devm_kzalloc(dev, sizeof(*cx2092x), GFP_KERNEL);
- if (!cx2092x)
return -ENOMEM;
- /* GPIOs */
- cx2092x->gpiod_reset = devm_gpiod_get_optional(dev, "reset",
GPIOD_OUT_LOW);
- if (IS_ERR(cx2092x->gpiod_reset))
return PTR_ERR(cx2092x->gpiod_reset);
- dev_set_drvdata(dev, cx2092x);
- cx2092x->regmap = regmap;
- cx2092x->dev = dev;
- ret = snd_soc_register_codec(cx2092x->dev, &soc_codec_driver_cx2092x,
soc_codec_cx2092x_dai,
ARRAY_SIZE(soc_codec_cx2092x_dai));
- if (ret < 0)
dev_err(dev, "Failed to register codec: %d\n", ret);
- else
dev_dbg(dev, "%s: Register codec.\n", __func__);
- return ret;
+} +EXPORT_SYMBOL_GPL(cx2092x_dev_probe);
+MODULE_DESCRIPTION("ASoC CX2092X ALSA SoC Driver"); +MODULE_AUTHOR("Simon Ho simon.ho@conexant.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cx2092x.h b/sound/soc/codecs/cx2092x.h new file mode 100644 index 0000000..9577be8 --- /dev/null +++ b/sound/soc/codecs/cx2092x.h @@ -0,0 +1,26 @@ +/*
- cx2092x.h -- CX20921 and CX20924 Audio driver
- Copyright: (C) 2017 Conexant Systems, Inc.
- This is based on Alexander Sverdlin's CS4271 driver code.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- */
+#ifndef __CX2092X_PRIV_H__ +#define __CX2092X_PRIV_H__
+#include <linux/regmap.h>
+extern const struct of_device_id cx2092x_dt_ids[]; +extern const struct regmap_config cx2092x_regmap_config;
+int cx2092x_dev_probe(struct device *dev, struct regmap *regmap);
+#define CX2092X_REG_MAX 0x2000
+#endif