From mboxrd@z Thu Jan 1 00:00:00 1970 From: Pierre-Louis Bossart Subject: Re: [PATCH v5 2/2] ASoC: Add support for Conexant CX2092X DSP Date: Thu, 6 Apr 2017 08:13:26 +0200 Message-ID: References: <1e118d265847bf96722b8898ee11891c047558c6.1491446448.git.simon.ho@conexant.com> Mime-Version: 1.0 Content-Type: text/plain; charset="us-ascii"; Format="flowed" Content-Transfer-Encoding: 7bit Return-path: Received: from mga14.intel.com (mga14.intel.com [192.55.52.115]) by alsa0.perex.cz (Postfix) with ESMTP id EAD81267255 for ; Thu, 6 Apr 2017 08:13:30 +0200 (CEST) In-Reply-To: <1e118d265847bf96722b8898ee11891c047558c6.1491446448.git.simon.ho@conexant.com> List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org To: simon.ho.cnxt@gmail.com, alsa-devel@alsa-project.org Cc: broonie@kernel.org, Simon Ho , ckeepax@opensource.wolfsonmicro.com, tiwai@suse.com, lgirdwood@gmail.com List-Id: alsa-devel@alsa-project.org On 4/6/17 5:04 AM, simon.ho.cnxt@gmail.com wrote: > From: Simon Ho > > Initial commit of Conexant CX20921/CX20924 I2S Audio DSP driver > > The CX2092X devices are designed for virtual assisant application need to > be always open, listening for users to summon it. There is no any power > saving mode support on this device. The processed voice data will be sent > to automatic speech recognition (ASR) application for further processing. > > Signed-off-by: Simon Ho > --- > sound/soc/codecs/Kconfig | 6 + > sound/soc/codecs/Makefile | 2 + > sound/soc/codecs/cx2092x.c | 440 +++++++++++++++++++++++++++++++++++++++++++++ > sound/soc/codecs/cx2092x.h | 19 ++ > 4 files changed, 467 insertions(+) > 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 70decab..2e1d468 100644 > --- a/sound/soc/codecs/Kconfig > +++ b/sound/soc/codecs/Kconfig > @@ -64,6 +64,7 @@ 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 if I2C > select SND_SOC_DA7210 if SND_SOC_I2C_AND_SPI > select SND_SOC_DA7213 if I2C > select SND_SOC_DA7218 if I2C > @@ -500,6 +501,11 @@ config SND_SOC_CX20442 > tristate > depends on TTY > > +config SND_SOC_CX2092X > + tristate "Conexant CX20921/CX2094 CODEC (I2C)" > + depends on I2C > + select REGMAP_I2C > + > config SND_SOC_JZ4740_CODEC > select REGMAP_MMIO > tristate > diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile > index bbef31e..11d3906 100644 > --- a/sound/soc/codecs/Makefile > +++ b/sound/soc/codecs/Makefile > @@ -57,6 +57,7 @@ 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-da7210-objs := da7210.o > snd-soc-da7213-objs := da7213.o > snd-soc-da7218-objs := da7218.o > @@ -290,6 +291,7 @@ 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_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.c b/sound/soc/codecs/cx2092x.c > new file mode 100644 > index 0000000..edad9c5 > --- /dev/null > +++ b/sound/soc/codecs/cx2092x.c > @@ -0,0 +1,440 @@ > +/* > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#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*/ > + 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*/ for first. > + ret = regmap_bulk_write(cx2092x->regmap, 4, &i2c_data[1], size - 1); > + if (ret < 0) { > + dev_err(cx2092x->dev, > + "sendcmd: Failed to write command data, err = %d\n", > + ret); > + 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, > + "sendcmd: Failed to write command, err = %d\n", ret); > + 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; > + msleep(10); > + > + } while (!time_after(jiffies, time_out)); > + > + if (cmd->reply == 1) { > + ret = cmd->num_32b_words; > + /* check if there is any returned data. If yes, then 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); > + /* print and return error code if operation is not successful*/ > + } else if (cmd->num_32b_words < 0) { > + dev_err(cx2092x->dev, "sendcmd failed: %d\n", > + cmd->num_32b_words); > + ret = -EINVAL; > + } so if num_32b_words is zero what happens? it's not clear if the case is omitted intentionally or the error case is not handled. > + } else { > + dev_err(cx2092x->dev, "SendCmd timeouti!\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; > +} > + > +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', ' '); > + 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", > + .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); msleep()? > + gpiod_set_value_cansleep(cx2092x->gpiod_reset, 1); > + } > + > + return 0; > +} > + > +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); > + > +static int cx2092x_i2c_probe(struct i2c_client *i2c, > + const struct i2c_device_id *id) > +{ > + struct cx2092x_priv *cx2092x; > + int ret; > + struct device *dev = &i2c->dev; > + struct regmap *regmap; > + > + regmap = devm_regmap_init_i2c(i2c, &cx2092x_regmap_config); > + 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; > +} > + > +static int cx2092x_i2c_remove(struct i2c_client *client) > +{ > + snd_soc_unregister_codec(&client->dev); > + > + return 0; > +} > + > +const struct of_device_id cx2092x_dt_ids[] = { > + { .compatible = "cnxt,cx20921", }, > + { .compatible = "cnxt,cx20924", }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, cx2092x_dt_ids); > + > +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", > + .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 ALSA SoC Driver"); > +MODULE_AUTHOR("Simon Ho "); > +MODULE_LICENSE("GPL"); > diff --git a/sound/soc/codecs/cx2092x.h b/sound/soc/codecs/cx2092x.h > new file mode 100644 > index 0000000..09dd610 > --- /dev/null > +++ b/sound/soc/codecs/cx2092x.h > @@ -0,0 +1,19 @@ > +/* > + * 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__ > + > +#define CX2092X_REG_MAX 0x2000 > + > +#endif >