diff mbox series

[V1,3/3] ASoC: codecs: Add aw87390 amplifier driver

Message ID 20230904114621.4457-4-wangweidong.a@awinic.com (mailing list archive)
State Superseded
Headers show
Series ASoC: codecs: Add aw87390 amplifier driver | expand

Commit Message

wangweidong.a@awinic.com Sept. 4, 2023, 11:46 a.m. UTC
From: Weidong Wang <wangweidong.a@awinic.com>

Add i2c and amplifier registration for aw87390 and
their associated operation functions.

Signed-off-by: Weidong Wang <wangweidong.a@awinic.com>
---
 sound/soc/codecs/Kconfig   |  15 +-
 sound/soc/codecs/Makefile  |   2 +
 sound/soc/codecs/aw87390.c | 463 +++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/aw87390.h |  85 +++++++
 4 files changed, 563 insertions(+), 2 deletions(-)
 create mode 100644 sound/soc/codecs/aw87390.c
 create mode 100644 sound/soc/codecs/aw87390.h

Comments

Krzysztof Kozlowski Sept. 4, 2023, 12:17 p.m. UTC | #1
On 04/09/2023 13:46, wangweidong.a@awinic.com wrote:
> From: Weidong Wang <wangweidong.a@awinic.com>
> 

...

> +static void aw87390_parse_channel_dt(struct aw87390 *aw87390)
> +{
> +	struct aw_device *aw_dev = aw87390->aw_pa;
> +	struct device_node *np = aw_dev->dev->of_node;
> +	u32 channel_value = AW87390_DEV_DEFAULT_CH;
> +
> +	of_property_read_u32(np, "sound-channel", &channel_value);

NAK, there is no such property. It seems you already sneaked in such for
other codecs. Please do not repeat such patterns of work.

That's also why I expect full DTS example, not some reduced pieces.

> +
> +	aw_dev->channel = channel_value;
> +}
> +
> +static int aw87390_init(struct aw87390 **aw87390, struct i2c_client *i2c, struct regmap *regmap)
> +{
> +	struct aw_device *aw_dev;
> +	unsigned int chip_id;
> +	int ret;
> +
> +	/* read chip id */
> +	ret = regmap_read(regmap, AW87390_ID_REG, &chip_id);
> +	if (ret) {
> +		dev_err(&i2c->dev, "%s read chipid error. ret = %d\n", __func__, ret);
> +		return ret;
> +	}
> +
> +	if (chip_id != AW87390_CHIP_ID) {
> +		dev_err(&i2c->dev, "unsupported device\n");

Why? The compatible tells it cannot be anything else.

> +		return -ENXIO;
> +	}
> +
> +	dev_info(&i2c->dev, "chip id = 0x%x\n", chip_id);
> +
> +	aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL);
> +	if (!aw_dev)
> +		return -ENOMEM;
> +
> +	(*aw87390)->aw_pa = aw_dev;
> +	aw_dev->i2c = i2c;
> +	aw_dev->regmap = regmap;
> +	aw_dev->dev = &i2c->dev;
> +	aw_dev->chip_id = AW87390_CHIP_ID;
> +	aw_dev->acf = NULL;
> +	aw_dev->prof_info.prof_desc = NULL;
> +	aw_dev->prof_info.count = 0;
> +	aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID;
> +	aw_dev->channel = AW87390_DEV_DEFAULT_CH;
> +	aw_dev->fw_status = AW87390_DEV_FW_FAILED;
> +	aw_dev->prof_index = AW87390_INIT_PROFILE;
> +	aw_dev->status = AW87390_DEV_PW_OFF;
> +
> +	aw87390_parse_channel_dt(*aw87390);
> +
> +	return ret;
> +}
> +
> +static int aw87390_i2c_probe(struct i2c_client *i2c)
> +{
> +	struct aw87390 *aw87390;
> +	int ret;
> +
> +	ret = i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C);
> +	if (!ret)
> +		return dev_err_probe(&i2c->dev, -ENXIO, "check_functionality failed\n");
> +
> +	aw87390 = devm_kzalloc(&i2c->dev, sizeof(*aw87390), GFP_KERNEL);
> +	if (!aw87390)
> +		return -ENOMEM;
> +
> +	mutex_init(&aw87390->lock);
> +
> +	i2c_set_clientdata(i2c, aw87390);
> +
> +	aw87390->regmap = devm_regmap_init_i2c(i2c, &aw87390_remap_config);
> +	if (IS_ERR(aw87390->regmap)) {
> +		ret = PTR_ERR(aw87390->regmap);

ret is not needed here, so just:
return dev_err_probe()

> +		return dev_err_probe(&i2c->dev, ret, "failed to init regmap: %d\n", ret);
> +	}
> +
> +	/* aw pa init */
> +	ret = aw87390_init(&aw87390, i2c, aw87390->regmap);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(aw87390->regmap, AW87390_ID_REG, AW87390_SOFT_RESET_VALUE);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_snd_soc_register_component(&i2c->dev,
> +				&soc_codec_dev_aw87390, NULL, 0);
> +	if (ret)
> +		dev_err(&i2c->dev, "failed to register aw87390: %d\n", ret);
> +
> +	return ret;
> +}


Best regards,
Krzysztof
Mark Brown Sept. 4, 2023, 12:30 p.m. UTC | #2
On Mon, Sep 04, 2023 at 02:17:43PM +0200, Krzysztof Kozlowski wrote:
> On 04/09/2023 13:46, wangweidong.a@awinic.com wrote:

> > +	ret = regmap_read(regmap, AW87390_ID_REG, &chip_id);
> > +	if (ret) {
> > +		dev_err(&i2c->dev, "%s read chipid error. ret = %d\n", __func__, ret);
> > +		return ret;
> > +	}

> > +	if (chip_id != AW87390_CHIP_ID) {
> > +		dev_err(&i2c->dev, "unsupported device\n");

> Why? The compatible tells it cannot be anything else.

This is very common good practice, as well as validating communication
with the device it verifies that the device descrbied in the DT is the
one that is actually present in the system.  This might create hassle
down the line if there is a backwards compatible upgrade but that's much
rarer for this class of hardware than cut'n'pasting of device trees.
Krzysztof Kozlowski Sept. 4, 2023, 1:02 p.m. UTC | #3
On 04/09/2023 14:30, Mark Brown wrote:
> On Mon, Sep 04, 2023 at 02:17:43PM +0200, Krzysztof Kozlowski wrote:
>> On 04/09/2023 13:46, wangweidong.a@awinic.com wrote:
> 
>>> +	ret = regmap_read(regmap, AW87390_ID_REG, &chip_id);
>>> +	if (ret) {
>>> +		dev_err(&i2c->dev, "%s read chipid error. ret = %d\n", __func__, ret);
>>> +		return ret;
>>> +	}
> 
>>> +	if (chip_id != AW87390_CHIP_ID) {
>>> +		dev_err(&i2c->dev, "unsupported device\n");
> 
>> Why? The compatible tells it cannot be anything else.
> 
> This is very common good practice, as well as validating communication

No, it is neither common nor good. The kernel's job is not to verify the
supplied DTS. Rob also made here a point:

https://lore.kernel.org/all/CAL_Jsq+wcrOjh7+0c=mrg+Qz6dbhOUE-VEeQ4FoWC3Y7ENoyfQ@mail.gmail.com/

> with the device it verifies that the device descrbied in the DT is the
> one that is actually present in the system.  This might create hassle
> down the line if there is a backwards compatible upgrade but that's much
> rarer for this class of hardware than cut'n'pasting of device trees.

Best regards,
Krzysztof
Mark Brown Sept. 4, 2023, 2:54 p.m. UTC | #4
On Mon, Sep 04, 2023 at 03:02:31PM +0200, Krzysztof Kozlowski wrote:
> On 04/09/2023 14:30, Mark Brown wrote:

> > This is very common good practice, as well as validating communication

> No, it is neither common nor good. The kernel's job is not to verify the
> supplied DTS. Rob also made here a point:

It's certainly a common practice, even if you disagree that it's good.  

> https://lore.kernel.org/all/CAL_Jsq+wcrOjh7+0c=mrg+Qz6dbhOUE-VEeQ4FoWC3Y7ENoyfQ@mail.gmail.com/

That's a very different kind of error, I'm not clear how we expect
schema checks to identify a mismatch with schematics...
Linus Walleij Sept. 4, 2023, 8:21 p.m. UTC | #5
Hi Weidong,

thanks for your patch!

On Mon, Sep 4, 2023 at 1:47 PM <wangweidong.a@awinic.com> wrote:

> From: Weidong Wang <wangweidong.a@awinic.com>
>
> Add i2c and amplifier registration for aw87390 and
> their associated operation functions.
>
> Signed-off-by: Weidong Wang <wangweidong.a@awinic.com>
(...)
> +#include <linux/of_gpio.h>

Please do not include this deprecated header.

Also: it seems you do not even use it so it's unnecessary.

Yours,
Linus Walleij
Linus Walleij Sept. 4, 2023, 8:26 p.m. UTC | #6
On Mon, Sep 4, 2023 at 3:02 PM Krzysztof Kozlowski
<krzysztof.kozlowski@linaro.org> wrote:
> On 04/09/2023 14:30, Mark Brown wrote:
> > On Mon, Sep 04, 2023 at 02:17:43PM +0200, Krzysztof Kozlowski wrote:
> >> On 04/09/2023 13:46, wangweidong.a@awinic.com wrote:
> >
> >>> +   ret = regmap_read(regmap, AW87390_ID_REG, &chip_id);
> >>> +   if (ret) {
> >>> +           dev_err(&i2c->dev, "%s read chipid error. ret = %d\n", __func__, ret);
> >>> +           return ret;
> >>> +   }
> >
> >>> +   if (chip_id != AW87390_CHIP_ID) {
> >>> +           dev_err(&i2c->dev, "unsupported device\n");
> >
> >> Why? The compatible tells it cannot be anything else.
> >
> > This is very common good practice, as well as validating communication
>
> No, it is neither common nor good. The kernel's job is not to verify the
> supplied DTS. Rob also made here a point:
>
> https://lore.kernel.org/all/CAL_Jsq+wcrOjh7+0c=mrg+Qz6dbhOUE-VEeQ4FoWC3Y7ENoyfQ@mail.gmail.com/

I disagree, if a vendor one day decides to mount a new version of a
component without notifying the community or users this is really
helpful.

The function is named "probe()" for a reason, as in "inspect
the hardware and see what we find" this has always been the case
I think.

Yours,
Linus Walleij
wangweidong.a@awinic.com Sept. 5, 2023, 6:03 a.m. UTC | #7
Thank you very much. Here are some things I'd like to discuss with you.

On 05/09/2023 11:50, krzysztof.kozlowski@linaro.org wrote:
> On 04/09/2023 13:46, wangweidong.a@awinic.com wrote:
>> From: Weidong Wang <wangweidong.a@awinic.com>
>> 

> ...

>> +static void aw87390_parse_channel_dt(struct aw87390 *aw87390)
>> +{
>> +	struct aw_device *aw_dev = aw87390->aw_pa;
>> +	struct device_node *np = aw_dev->dev->of_node;
>> +	u32 channel_value = AW87390_DEV_DEFAULT_CH;
>> +
>> +	of_property_read_u32(np, "sound-channel", &channel_value);

> NAK, there is no such property. It seems you already sneaked in such for
> other codecs. Please do not repeat such patterns of work.

> That's also why I expect full DTS example, not some reduced pieces.

Thank you very much. I would like to add a sound-channel property to awinic,aw87390.yaml
This property is used to distinguish between multiple PA's in order to 
load different configurations for different PA's

>> +
>> +	aw_dev->channel = channel_value;
>> +}
>> +
>> +static int aw87390_init(struct aw87390 **aw87390, struct i2c_client *i2c, struct regmap *regmap)
>> +{
>> +	struct aw_device *aw_dev;
>> +	unsigned int chip_id;
>> +	int ret;
>> +
>> +	/* read chip id */
>> +	ret = regmap_read(regmap, AW87390_ID_REG, &chip_id);
>> +	if (ret) {
>> +		dev_err(&i2c->dev, "%s read chipid error. ret = %d\n", __func__, ret);
>> +		return ret;
>> +	}
>> +
>> +	if (chip_id != AW87390_CHIP_ID) {
>> +		dev_err(&i2c->dev, "unsupported device\n");

> Why? The compatible tells it cannot be anything else.

>> +		return -ENXIO;
>> +	}
>> +
>> +	dev_info(&i2c->dev, "chip id = 0x%x\n", chip_id);
>> +
>> +	aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL);
>> +	if (!aw_dev)
>> +		return -ENOMEM;
>> +
>> +	(*aw87390)->aw_pa = aw_dev;
>> +	aw_dev->i2c = i2c;
>> +	aw_dev->regmap = regmap;
>> +	aw_dev->dev = &i2c->dev;
>> +	aw_dev->chip_id = AW87390_CHIP_ID;
>> +	aw_dev->acf = NULL;
>> +	aw_dev->prof_info.prof_desc = NULL;
>> +	aw_dev->prof_info.count = 0;
>> +	aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID;
>> +	aw_dev->channel = AW87390_DEV_DEFAULT_CH;
>> +	aw_dev->fw_status = AW87390_DEV_FW_FAILED;
>> +	aw_dev->prof_index = AW87390_INIT_PROFILE;
>> +	aw_dev->status = AW87390_DEV_PW_OFF;
>> +
>> +	aw87390_parse_channel_dt(*aw87390);
>> +
>> +	return ret;
>> +}
>> +
>> +static int aw87390_i2c_probe(struct i2c_client *i2c)
>> +{
>> +	struct aw87390 *aw87390;
>> +	int ret;
>> +
>> +	ret = i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C);
>> +	if (!ret)
>> +		return dev_err_probe(&i2c->dev, -ENXIO, "check_functionality failed\n");
>> +
>> +	aw87390 = devm_kzalloc(&i2c->dev, sizeof(*aw87390), GFP_KERNEL);
>> +	if (!aw87390)
>> +		return -ENOMEM;
>> +
>> +	mutex_init(&aw87390->lock);
>> +
>> +	i2c_set_clientdata(i2c, aw87390);
>> +
>> +	aw87390->regmap = devm_regmap_init_i2c(i2c, &aw87390_remap_config);
>> +	if (IS_ERR(aw87390->regmap)) {
>> +		ret = PTR_ERR(aw87390->regmap);

> ret is not needed here, so just:
> return dev_err_probe()

Thank you very much. I will modify it to 
"return dev_err_probe(&i2c->dev, PTR_ERR(aw87390->regmap), "failed to init regmap: %d\n", ret);"

>> +		return dev_err_probe(&i2c->dev, ret, "failed to init regmap: %d\n", ret);
>> +	}
>> +
>> +	/* aw pa init */
>> +	ret = aw87390_init(&aw87390, i2c, aw87390->regmap);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = regmap_write(aw87390->regmap, AW87390_ID_REG, AW87390_SOFT_RESET_VALUE);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = devm_snd_soc_register_component(&i2c->dev,
>> +				&soc_codec_dev_aw87390, NULL, 0);
>> +	if (ret)
>> +		dev_err(&i2c->dev, "failed to register aw87390: %d\n", ret);
>> +
>> +	return ret;
>> +}


Best regards,
Weidong Wang
diff mbox series

Patch

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 95b5bd883215..9f4311b13d6e 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -54,6 +54,7 @@  config SND_SOC_ALL_CODECS
 	imply SND_SOC_ALC5632
 	imply SND_SOC_AUDIO_IIO_AUX
 	imply SND_SOC_AW8738
+	imply SND_SOC_AW87390
 	imply SND_SOC_AW88395
 	imply SND_SOC_AW88261
 	imply SND_SOC_BT_SCO
@@ -638,12 +639,12 @@  config SND_SOC_AW8738
 	  operation mode using the Awinic-specific one-wire pulse control.
 
 config SND_SOC_AW88395_LIB
+	select CRC8
 	tristate
 
 config SND_SOC_AW88395
 	tristate "Soc Audio for awinic aw88395"
 	depends on I2C
-	select CRC8
 	select CRC32
 	select REGMAP_I2C
 	select GPIOLIB
@@ -657,7 +658,6 @@  config SND_SOC_AW88395
 config SND_SOC_AW88261
 	tristate "Soc Audio for awinic aw88261"
 	depends on I2C
-	select CRC8
 	select REGMAP_I2C
 	select GPIOLIB
 	select SND_SOC_AW88395_LIB
@@ -668,6 +668,17 @@  config SND_SOC_AW88261
 	  boost converter can be adjusted smartly according to
 	  the input amplitude.
 
+config SND_SOC_AW87390
+	tristate "Soc Audio for awinic aw87390"
+	depends on I2C
+	select REGMAP_I2C
+	select SND_SOC_AW88395_LIB
+	help
+	  The awinic aw87390 is specifically designed to improve
+	  the musical output dynamic range, enhance the overall
+	  sound quallity, which is a new high efficiency, low
+	  noise, constant large volume, 6th Smart K audio amplifier.
+
 config SND_SOC_BD28623
 	tristate "ROHM BD28623 CODEC"
 	help
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index c8502a49b40a..12bf52e23d65 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -47,6 +47,7 @@  snd-soc-ak5558-objs := ak5558.o
 snd-soc-arizona-objs := arizona.o arizona-jack.o
 snd-soc-audio-iio-aux-objs := audio-iio-aux.o
 snd-soc-aw8738-objs := aw8738.o
+snd-soc-aw87390-objs := aw87390.o
 snd-soc-aw88395-lib-objs := aw88395/aw88395_lib.o
 snd-soc-aw88395-objs := aw88395/aw88395.o \
 			aw88395/aw88395_device.o
@@ -433,6 +434,7 @@  obj-$(CONFIG_SND_SOC_ALC5632)	+= snd-soc-alc5632.o
 obj-$(CONFIG_SND_SOC_ARIZONA)	+= snd-soc-arizona.o
 obj-$(CONFIG_SND_SOC_AUDIO_IIO_AUX)	+= snd-soc-audio-iio-aux.o
 obj-$(CONFIG_SND_SOC_AW8738)	+= snd-soc-aw8738.o
+obj-$(CONFIG_SND_SOC_AW87390)	+= snd-soc-aw87390.o
 obj-$(CONFIG_SND_SOC_AW88395_LIB) += snd-soc-aw88395-lib.o
 obj-$(CONFIG_SND_SOC_AW88395)	+=snd-soc-aw88395.o
 obj-$(CONFIG_SND_SOC_AW88261)	+=snd-soc-aw88261.o
diff --git a/sound/soc/codecs/aw87390.c b/sound/soc/codecs/aw87390.c
new file mode 100644
index 000000000000..cf3065b0a73e
--- /dev/null
+++ b/sound/soc/codecs/aw87390.c
@@ -0,0 +1,463 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// aw87390.c  --  AW87390 ALSA SoC Audio driver
+//
+// Copyright (c) 2023 awinic Technology CO., LTD
+//
+// Author: Weidong Wang <wangweidong.a@awinic.com>
+//
+
+#include <linux/i2c.h>
+#include <linux/firmware.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include "aw87390.h"
+#include "aw88395/aw88395_data_type.h"
+#include "aw88395/aw88395_device.h"
+
+static const struct regmap_config aw87390_remap_config = {
+	.val_bits = 8,
+	.reg_bits = 8,
+	.max_register = AW87390_REG_MAX,
+	.reg_format_endian = REGMAP_ENDIAN_LITTLE,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+};
+
+static int aw87390_dev_reg_update(struct aw_device *aw_dev,
+					unsigned char *data, unsigned int len)
+{
+	int i, ret;
+
+	if (!data) {
+		dev_err(aw_dev->dev, "data is NULL\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < len; i = i + 2) {
+		if (data[i] == AW87390_DELAY_REG_ADDR) {
+			usleep_range(data[i + 1] * AW87390_REG_DELAY_TIME,
+					data[i + 1] * AW87390_REG_DELAY_TIME + 10);
+			continue;
+		}
+		ret = regmap_write(aw_dev->regmap, data[i], data[i + 1]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static char *aw87390_dev_get_prof_name(struct aw_device *aw_dev, int index)
+{
+	struct aw_prof_info *prof_info = &aw_dev->prof_info;
+	struct aw_prof_desc *prof_desc;
+
+	if ((index >= aw_dev->prof_info.count) || (index < 0)) {
+		dev_err(aw_dev->dev, "index[%d] overflow count[%d]\n",
+			index, aw_dev->prof_info.count);
+		return NULL;
+	}
+
+	prof_desc = &aw_dev->prof_info.prof_desc[index];
+
+	return prof_info->prof_name_list[prof_desc->id];
+}
+
+static int aw87390_dev_get_prof_data(struct aw_device *aw_dev, int index,
+			struct aw_prof_desc **prof_desc)
+{
+	if ((index >= aw_dev->prof_info.count) || (index < 0)) {
+		dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n",
+				__func__, index, aw_dev->prof_info.count);
+		return -EINVAL;
+	}
+
+	*prof_desc = &aw_dev->prof_info.prof_desc[index];
+
+	return 0;
+}
+
+static int aw87390_dev_fw_update(struct aw_device *aw_dev)
+{
+	struct aw_prof_desc *prof_index_desc;
+	struct aw_sec_data_desc *sec_desc;
+	char *prof_name;
+	int ret;
+
+	prof_name = aw87390_dev_get_prof_name(aw_dev, aw_dev->prof_index);
+	if (!prof_name) {
+		dev_err(aw_dev->dev, "get prof name failed\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(aw_dev->dev, "start update %s", prof_name);
+
+	ret = aw87390_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc);
+	if (ret) {
+		dev_err(aw_dev->dev, "aw87390_dev_get_prof_data failed\n");
+		return ret;
+	}
+
+	/* update reg */
+	sec_desc = prof_index_desc->sec_desc;
+	ret = aw87390_dev_reg_update(aw_dev, sec_desc[AW88395_DATA_TYPE_REG].data,
+					sec_desc[AW88395_DATA_TYPE_REG].len);
+	if (ret) {
+		dev_err(aw_dev->dev, "update reg failed\n");
+		return ret;
+	}
+
+	aw_dev->prof_cur = aw_dev->prof_index;
+
+	return ret;
+}
+
+static int aw87390_power_off(struct aw_device *aw_dev)
+{
+	int ret;
+
+	if (aw_dev->status == AW87390_DEV_PW_OFF) {
+		dev_info(aw_dev->dev, "already power off\n");
+		return 0;
+	}
+
+	ret = regmap_write(aw_dev->regmap, AW87390_SYSCTRL_REG, AW87390_POWER_DOWN_VALUE);
+	if (ret)
+		return ret;
+	aw_dev->status = AW87390_DEV_PW_OFF;
+
+	return ret;
+}
+
+static int aw87390_power_on(struct aw_device *aw_dev)
+{
+	int ret;
+
+	if (aw_dev->status == AW87390_DEV_PW_ON) {
+		dev_info(aw_dev->dev, "already power on\n");
+		return 0;
+	}
+
+	if (!aw_dev->fw_status) {
+		dev_err(aw_dev->dev, "fw not load\n");
+		return -EINVAL;
+	}
+
+	ret = regmap_write(aw_dev->regmap, AW87390_SYSCTRL_REG, AW87390_POWER_DOWN_VALUE);
+	if (ret)
+		return ret;
+
+	ret = aw87390_dev_fw_update(aw_dev);
+	if (ret) {
+		dev_err(aw_dev->dev, "%s load profile failed\n", __func__);
+		return ret;
+	}
+	aw_dev->status = AW87390_DEV_PW_ON;
+
+	return ret;
+}
+
+static int aw87390_dev_set_profile_index(struct aw_device *aw_dev, int index)
+{
+	if ((index >= aw_dev->prof_info.count) || (index < 0))
+		return -EINVAL;
+
+	if (aw_dev->prof_index == index)
+		return -EPERM;
+
+	aw_dev->prof_index = index;
+
+	return 0;
+}
+
+static int aw87390_profile_info(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+	struct aw87390 *aw87390 = snd_soc_component_get_drvdata(codec);
+	const char *prof_name;
+	char *name;
+	int count;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+
+	count = aw87390->aw_pa->prof_info.count;
+	if (count <= 0) {
+		uinfo->value.enumerated.items = 0;
+		return 0;
+	}
+
+	uinfo->value.enumerated.items = count;
+
+	if (uinfo->value.enumerated.item >= count)
+		uinfo->value.enumerated.item = count - 1;
+
+	name = uinfo->value.enumerated.name;
+	count = uinfo->value.enumerated.item;
+
+	prof_name = aw87390_dev_get_prof_name(aw87390->aw_pa, count);
+	if (!prof_name) {
+		strscpy(uinfo->value.enumerated.name, "null",
+						strlen("null") + 1);
+		return 0;
+	}
+
+	strscpy(name, prof_name, sizeof(uinfo->value.enumerated.name));
+
+	return 0;
+}
+
+static int aw87390_profile_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+	struct aw87390 *aw87390 = snd_soc_component_get_drvdata(codec);
+
+	ucontrol->value.integer.value[0] = aw87390->aw_pa->prof_index;
+
+	return 0;
+}
+
+static int aw87390_profile_set(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+	struct aw87390 *aw87390 = snd_soc_component_get_drvdata(codec);
+	int ret;
+
+	mutex_lock(&aw87390->lock);
+	ret = aw87390_dev_set_profile_index(aw87390->aw_pa, ucontrol->value.integer.value[0]);
+	if (ret) {
+		dev_dbg(codec->dev, "profile index does not change\n");
+		mutex_unlock(&aw87390->lock);
+		return 0;
+	}
+
+	if (aw87390->aw_pa->status == AW87390_DEV_PW_ON) {
+		aw87390_power_off(aw87390->aw_pa);
+		aw87390_power_on(aw87390->aw_pa);
+	}
+
+	mutex_unlock(&aw87390->lock);
+
+	return 1;
+}
+
+static const struct snd_kcontrol_new aw87390_controls[] = {
+	AW87390_PROFILE_EXT("AW87390 Profile Set", aw87390_profile_info,
+		aw87390_profile_get, aw87390_profile_set),
+};
+
+static int aw87390_request_firmware_file(struct aw87390 *aw87390)
+{
+	const struct firmware *cont = NULL;
+	int ret;
+
+	aw87390->aw_pa->fw_status = AW87390_DEV_FW_FAILED;
+
+	ret = request_firmware(&cont, AW87390_ACF_FILE, aw87390->aw_pa->dev);
+	if (ret)
+		return dev_err_probe(aw87390->aw_pa->dev, ret,
+					"load [%s] failed!\n", AW87390_ACF_FILE);
+
+	dev_dbg(aw87390->aw_pa->dev, "loaded %s - size: %zu\n",
+			AW87390_ACF_FILE, cont ? cont->size : 0);
+
+	aw87390->aw_cfg = devm_kzalloc(aw87390->aw_pa->dev, cont->size + sizeof(int), GFP_KERNEL);
+	if (!aw87390->aw_cfg) {
+		release_firmware(cont);
+		return -ENOMEM;
+	}
+
+	aw87390->aw_cfg->len = (int)cont->size;
+	memcpy(aw87390->aw_cfg->data, cont->data, cont->size);
+	release_firmware(cont);
+
+	ret = aw88395_dev_load_acf_check(aw87390->aw_pa, aw87390->aw_cfg);
+	if (ret) {
+		dev_err(aw87390->aw_pa->dev, "load [%s] failed !\n", AW87390_ACF_FILE);
+		return ret;
+	}
+
+	mutex_lock(&aw87390->lock);
+
+	ret = aw88395_dev_cfg_load(aw87390->aw_pa, aw87390->aw_cfg);
+	if (ret)
+		dev_err(aw87390->aw_pa->dev, "aw_dev acf parse failed\n");
+
+	mutex_unlock(&aw87390->lock);
+
+	return ret;
+}
+
+static int aw87390_drv_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct aw87390 *aw87390 = snd_soc_component_get_drvdata(component);
+	struct aw_device *aw_dev = aw87390->aw_pa;
+	int ret;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		ret = aw87390_power_on(aw_dev);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		ret = aw87390_power_off(aw_dev);
+		break;
+	default:
+		dev_err(aw_dev->dev, "%s: invalid event %d\n", __func__, event);
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_dapm_widget aw87390_dapm_widgets[] = {
+	SND_SOC_DAPM_INPUT("IN"),
+	SND_SOC_DAPM_PGA_E("SPK PA", SND_SOC_NOPM, 0, 0, NULL, 0, aw87390_drv_event,
+			       SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_OUTPUT("OUT"),
+};
+
+static const struct snd_soc_dapm_route aw87390_dapm_routes[] = {
+	{ "SPK PA", NULL, "IN" },
+	{ "OUT", NULL, "SPK PA" },
+};
+
+static int aw87390_codec_probe(struct snd_soc_component *component)
+{
+	struct aw87390 *aw87390 = snd_soc_component_get_drvdata(component);
+	int ret;
+
+	ret = aw87390_request_firmware_file(aw87390);
+	if (ret)
+		return dev_err_probe(aw87390->aw_pa->dev, ret,
+				"aw87390_request_firmware_file failed\n");
+
+	return 0;
+}
+
+static const struct snd_soc_component_driver soc_codec_dev_aw87390 = {
+	.probe = aw87390_codec_probe,
+	.dapm_widgets = aw87390_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(aw87390_dapm_widgets),
+	.dapm_routes = aw87390_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(aw87390_dapm_routes),
+	.controls = aw87390_controls,
+	.num_controls = ARRAY_SIZE(aw87390_controls),
+};
+
+static void aw87390_parse_channel_dt(struct aw87390 *aw87390)
+{
+	struct aw_device *aw_dev = aw87390->aw_pa;
+	struct device_node *np = aw_dev->dev->of_node;
+	u32 channel_value = AW87390_DEV_DEFAULT_CH;
+
+	of_property_read_u32(np, "sound-channel", &channel_value);
+
+	aw_dev->channel = channel_value;
+}
+
+static int aw87390_init(struct aw87390 **aw87390, struct i2c_client *i2c, struct regmap *regmap)
+{
+	struct aw_device *aw_dev;
+	unsigned int chip_id;
+	int ret;
+
+	/* read chip id */
+	ret = regmap_read(regmap, AW87390_ID_REG, &chip_id);
+	if (ret) {
+		dev_err(&i2c->dev, "%s read chipid error. ret = %d\n", __func__, ret);
+		return ret;
+	}
+
+	if (chip_id != AW87390_CHIP_ID) {
+		dev_err(&i2c->dev, "unsupported device\n");
+		return -ENXIO;
+	}
+
+	dev_info(&i2c->dev, "chip id = 0x%x\n", chip_id);
+
+	aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL);
+	if (!aw_dev)
+		return -ENOMEM;
+
+	(*aw87390)->aw_pa = aw_dev;
+	aw_dev->i2c = i2c;
+	aw_dev->regmap = regmap;
+	aw_dev->dev = &i2c->dev;
+	aw_dev->chip_id = AW87390_CHIP_ID;
+	aw_dev->acf = NULL;
+	aw_dev->prof_info.prof_desc = NULL;
+	aw_dev->prof_info.count = 0;
+	aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID;
+	aw_dev->channel = AW87390_DEV_DEFAULT_CH;
+	aw_dev->fw_status = AW87390_DEV_FW_FAILED;
+	aw_dev->prof_index = AW87390_INIT_PROFILE;
+	aw_dev->status = AW87390_DEV_PW_OFF;
+
+	aw87390_parse_channel_dt(*aw87390);
+
+	return ret;
+}
+
+static int aw87390_i2c_probe(struct i2c_client *i2c)
+{
+	struct aw87390 *aw87390;
+	int ret;
+
+	ret = i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C);
+	if (!ret)
+		return dev_err_probe(&i2c->dev, -ENXIO, "check_functionality failed\n");
+
+	aw87390 = devm_kzalloc(&i2c->dev, sizeof(*aw87390), GFP_KERNEL);
+	if (!aw87390)
+		return -ENOMEM;
+
+	mutex_init(&aw87390->lock);
+
+	i2c_set_clientdata(i2c, aw87390);
+
+	aw87390->regmap = devm_regmap_init_i2c(i2c, &aw87390_remap_config);
+	if (IS_ERR(aw87390->regmap)) {
+		ret = PTR_ERR(aw87390->regmap);
+		return dev_err_probe(&i2c->dev, ret, "failed to init regmap: %d\n", ret);
+	}
+
+	/* aw pa init */
+	ret = aw87390_init(&aw87390, i2c, aw87390->regmap);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(aw87390->regmap, AW87390_ID_REG, AW87390_SOFT_RESET_VALUE);
+	if (ret)
+		return ret;
+
+	ret = devm_snd_soc_register_component(&i2c->dev,
+				&soc_codec_dev_aw87390, NULL, 0);
+	if (ret)
+		dev_err(&i2c->dev, "failed to register aw87390: %d\n", ret);
+
+	return ret;
+}
+
+static const struct i2c_device_id aw87390_i2c_id[] = {
+	{ AW87390_I2C_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, aw87390_i2c_id);
+
+static struct i2c_driver aw87390_i2c_driver = {
+	.driver = {
+		.name = AW87390_I2C_NAME,
+	},
+	.probe = aw87390_i2c_probe,
+	.id_table = aw87390_i2c_id,
+};
+module_i2c_driver(aw87390_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC AW87390 PA Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/aw87390.h b/sound/soc/codecs/aw87390.h
new file mode 100644
index 000000000000..468e6eb2e224
--- /dev/null
+++ b/sound/soc/codecs/aw87390.h
@@ -0,0 +1,85 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// aw87390.h  --  aw87390 ALSA SoC Audio driver
+//
+// Copyright (c) 2023 awinic Technology CO., LTD
+//
+// Author: Weidong Wang <wangweidong.a@awinic.com>
+//
+
+#ifndef __AW87390_H__
+#define __AW87390_H__
+
+#define AW87390_ID_REG			(0x00)
+#define AW87390_SYSCTRL_REG		(0x01)
+#define AW87390_MDCTRL_REG		(0x02)
+#define AW87390_CPOVP_REG		(0x03)
+#define AW87390_CPP_REG		(0x04)
+#define AW87390_PAG_REG		(0x05)
+#define AW87390_AGC3P_REG		(0x06)
+#define AW87390_AGC3PA_REG		(0x07)
+#define AW87390_AGC2P_REG		(0x08)
+#define AW87390_AGC2PA_REG		(0x09)
+#define AW87390_AGC1PA_REG		(0x0A)
+#define AW87390_SYSST_REG		(0x59)
+#define AW87390_SYSINT_REG		(0x60)
+#define AW87390_DFT_SYSCTRL_REG	(0x61)
+#define AW87390_DFT_MDCTRL_REG		(0x62)
+#define AW87390_DFT_CPADP_REG		(0x63)
+#define AW87390_DFT_AGCPA_REG		(0x64)
+#define AW87390_DFT_POFR_REG		(0x65)
+#define AW87390_DFT_OC_REG		(0x66)
+#define AW87390_DFT_ADP1_REG		(0x67)
+#define AW87390_DFT_REF_REG		(0x68)
+#define AW87390_DFT_LDO_REG		(0x69)
+#define AW87390_ADP1_REG		(0x70)
+#define AW87390_ADP2_REG		(0x71)
+#define AW87390_NG1_REG		(0x72)
+#define AW87390_NG2_REG		(0x73)
+#define AW87390_NG3_REG		(0x74)
+#define AW87390_CP_REG			(0x75)
+#define AW87390_AB_REG			(0x76)
+#define AW87390_TEST_REG		(0x77)
+#define AW87390_ENCR_REG		(0x78)
+#define AW87390_DELAY_REG_ADDR		(0xFE)
+
+#define AW87390_SOFT_RESET_VALUE	(0xAA)
+#define AW87390_POWER_DOWN_VALUE	(0x00)
+#define AW87390_REG_MAX		(0xFF)
+#define AW87390_DEV_DEFAULT_CH		(0)
+#define AW87390_INIT_PROFILE		(0)
+#define AW87390_REG_DELAY_TIME		(1000)
+#define AW87390_I2C_NAME		"aw87390_pa"
+#define AW87390_ACF_FILE		"aw87390_acf.bin"
+
+#define AW87390_PROFILE_EXT(xname, profile_info, profile_get, profile_set) \
+{ \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.info = profile_info, \
+	.get = profile_get, \
+	.put = profile_set, \
+}
+
+enum aw87390_id {
+	AW87390_CHIP_ID = 0x76,
+};
+
+enum {
+	AW87390_DEV_FW_FAILED = 0,
+	AW87390_DEV_FW_OK,
+};
+
+enum {
+	AW87390_DEV_PW_OFF = 0,
+	AW87390_DEV_PW_ON,
+};
+
+struct aw87390 {
+	struct aw_device *aw_pa;
+	struct mutex lock;
+	struct regmap *regmap;
+	struct aw_container *aw_cfg;
+};
+
+#endif