]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ASoC: codecs: aw87390: Add Anbernic RG-DS amp driver
authorChris Morgan <macromorgan@hotmail.com>
Wed, 28 Jan 2026 17:46:07 +0000 (11:46 -0600)
committerMark Brown <broonie@kernel.org>
Mon, 2 Feb 2026 12:18:46 +0000 (12:18 +0000)
Add support for Anbernic's RG-DS audio amplifiers, powered by
Awinic AW87391 amplifier ICs. These chips typically require an
init sequence provided by firmware, but the manufacturer did not
provide firmware in this case. As a result we had to hard-code
the init sequence and use a device specific binding (rather than
a binding just for the aw87391).

Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Link: https://patch.msgid.link/20260128174608.1498-3-macroalpha82@gmail.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/aw87390.c
sound/soc/codecs/aw87390.h

index d7fd865c349fbe339f7df273b7594c42ea001b87..613daccca3af8bb851a8fff56f4c08ac0d838f39 100644 (file)
@@ -314,6 +314,45 @@ static int aw87390_drv_event(struct snd_soc_dapm_widget *w,
        return ret;
 }
 
+static int aw87391_rgds_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;
+
+       switch (event) {
+       case SND_SOC_DAPM_PRE_PMU:
+               if (!IS_ERR(aw87390->vdd_reg)) {
+                       if (regulator_enable(aw87390->vdd_reg))
+                               dev_warn(aw_dev->dev, "Failed to enable vdd\n");
+       }
+               break;
+       case SND_SOC_DAPM_POST_PMU:
+               regmap_write(aw_dev->regmap, AW87391_SYSCTRL_REG,
+                            AW87391_REG_VER_SEL_LOW | AW87391_REG_EN_ADAP |
+                            AW87391_REG_EN_2X | AW87391_EN_SPK |
+                            AW87391_EN_PA | AW87391_REG_EN_CP |
+                            AW87391_EN_SW);
+               break;
+       case SND_SOC_DAPM_PRE_PMD:
+               regmap_write(aw_dev->regmap, AW87390_SYSCTRL_REG,
+                            AW87390_POWER_DOWN_VALUE);
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               if (!IS_ERR(aw87390->vdd_reg)) {
+                       if (regulator_disable(aw87390->vdd_reg))
+                               dev_warn(aw_dev->dev, "Failed to disable vdd\n");
+       }
+               break;
+       default:
+               dev_err(aw_dev->dev, "%s: invalid event %d\n", __func__, event);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 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,
@@ -321,6 +360,14 @@ static const struct snd_soc_dapm_widget aw87390_dapm_widgets[] = {
        SND_SOC_DAPM_OUTPUT("OUT"),
 };
 
+static const struct snd_soc_dapm_widget aw87391_rgds_dapm_widgets[] = {
+       SND_SOC_DAPM_INPUT("IN"),
+       SND_SOC_DAPM_PGA_E("SPK PA", SND_SOC_NOPM, 0, 0, NULL, 0, aw87391_rgds_drv_event,
+                          SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+                          SND_SOC_DAPM_PRE_PMD | 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" },
@@ -339,6 +386,80 @@ static int aw87390_codec_probe(struct snd_soc_component *component)
        return 0;
 }
 
+/*
+ * Firmware typically is used to load the sequence of init commands,
+ * however for the Anbernic RG-DS we don't have a firmware file just
+ * a list of registers and values. Most of these values are undocumented
+ * in the AW87391 datasheet.
+ */
+static void aw87391_rgds_codec_init(struct aw87390 *aw87390)
+{
+       struct aw_device *aw_dev = aw87390->aw_pa;
+
+       /* Undocumented command per datasheet. */
+       regmap_write(aw_dev->regmap, 0x64, 0x3a);
+
+       /* Bits 7:4 are undocumented but provided by manufacturer. */
+       regmap_write(aw_dev->regmap, AW87391_CP_REG,
+                    (5 << 4) | AW87391_REG_CP_OVP_8_50V);
+
+       regmap_write(aw_dev->regmap, AW87391_AGCPO_REG,
+                    AW87391_AK1_S_016 | AW87391_AGC2PO_MW(500));
+
+       regmap_write(aw_dev->regmap, AW87391_AGC2PA_REG,
+                    AW87391_RK_S_20_48 | AW87391_AK2_S_41 | AW87391_AK2F_S_41);
+
+       /* Undocumented commands per datasheet. */
+       regmap_write(aw_dev->regmap, 0x5d, 0x00);
+       regmap_write(aw_dev->regmap, 0x5e, 0xb4);
+       regmap_write(aw_dev->regmap, 0x5f, 0x30);
+       regmap_write(aw_dev->regmap, 0x60, 0x39);
+       regmap_write(aw_dev->regmap, 0x61, 0x10);
+       regmap_write(aw_dev->regmap, 0x62, 0x03);
+       regmap_write(aw_dev->regmap, 0x63, 0x7d);
+       regmap_write(aw_dev->regmap, 0x65, 0xa0);
+       regmap_write(aw_dev->regmap, 0x66, 0x21);
+       regmap_write(aw_dev->regmap, 0x67, 0x41);
+       regmap_write(aw_dev->regmap, 0x68, 0x3b);
+       regmap_write(aw_dev->regmap, 0x6e, 0x00);
+       regmap_write(aw_dev->regmap, 0x6f, 0x00);
+       regmap_write(aw_dev->regmap, 0x70, 0x00);
+       regmap_write(aw_dev->regmap, 0x71, 0x00);
+       regmap_write(aw_dev->regmap, 0x72, 0x34);
+       regmap_write(aw_dev->regmap, 0x73, 0x06);
+       regmap_write(aw_dev->regmap, 0x74, 0x10);
+       regmap_write(aw_dev->regmap, 0x75, 0x00);
+       regmap_write(aw_dev->regmap, 0x7a, 0x00);
+       regmap_write(aw_dev->regmap, 0x7b, 0x00);
+       regmap_write(aw_dev->regmap, 0x7c, 0x00);
+       regmap_write(aw_dev->regmap, 0x7d, 0x00);
+
+       regmap_write(aw_dev->regmap, AW87391_PAG_REG, AW87391_GAIN_12DB);
+       regmap_write(aw_dev->regmap, AW87391_SYSCTRL_REG,
+                    AW87391_EN_PA | AW87391_REG_EN_CP | AW87391_EN_SW);
+       regmap_write(aw_dev->regmap, AW87391_SYSCTRL_REG,
+                    AW87391_REG_VER_SEL_LOW | AW87391_REG_EN_ADAP |
+                    AW87391_REG_EN_2X | AW87391_EN_SPK | AW87391_EN_PA |
+                    AW87391_REG_EN_CP | AW87391_EN_SW);
+       regmap_write(aw_dev->regmap, AW87391_PAG_REG, AW87391_GAIN_15DB);
+}
+
+static int aw87391_rgds_codec_probe(struct snd_soc_component *component)
+{
+       struct aw87390 *aw87390 = snd_soc_component_get_drvdata(component);
+
+       aw87390->vdd_reg = devm_regulator_get_optional(aw87390->aw_pa->dev,
+                                                      "vdd");
+       if (IS_ERR(aw87390->vdd_reg) && PTR_ERR(aw87390->vdd_reg) != -ENODEV)
+               return dev_err_probe(aw87390->aw_pa->dev,
+                                    PTR_ERR(aw87390->vdd_reg),
+                                    "Could not get vdd regulator\n");
+
+       aw87391_rgds_codec_init(aw87390);
+
+       return 0;
+}
+
 static const struct snd_soc_component_driver soc_codec_dev_aw87390 = {
        .probe = aw87390_codec_probe,
        .dapm_widgets = aw87390_dapm_widgets,
@@ -349,6 +470,14 @@ static const struct snd_soc_component_driver soc_codec_dev_aw87390 = {
        .num_controls = ARRAY_SIZE(aw87390_controls),
 };
 
+static const struct snd_soc_component_driver soc_codec_dev_anbernic_rgds = {
+       .probe = aw87391_rgds_codec_probe,
+       .dapm_widgets = aw87391_rgds_dapm_widgets,
+       .num_dapm_widgets = ARRAY_SIZE(aw87391_rgds_dapm_widgets),
+       .dapm_routes = aw87390_dapm_routes,
+       .num_dapm_routes = ARRAY_SIZE(aw87390_dapm_routes),
+};
+
 static void aw87390_parse_channel_dt(struct aw87390 *aw87390)
 {
        struct aw_device *aw_dev = aw87390->aw_pa;
@@ -366,6 +495,10 @@ static int aw87390_init(struct aw87390 *aw87390, struct i2c_client *i2c, struct
        unsigned int chip_id;
        int ret;
 
+       aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL);
+       if (!aw_dev)
+               return -ENOMEM;
+
        /* read chip id */
        ret = regmap_read(regmap, AW87390_ID_REG, &chip_id);
        if (ret) {
@@ -373,22 +506,24 @@ static int aw87390_init(struct aw87390 *aw87390, struct i2c_client *i2c, struct
                return ret;
        }
 
-       if (chip_id != AW87390_CHIP_ID) {
+       switch (chip_id) {
+       case AW87390_CHIP_ID:
+               aw_dev->chip_id = AW87390_CHIP_ID;
+               break;
+       case AW87391_CHIP_ID:
+               aw_dev->chip_id = AW87391_CHIP_ID;
+               break;
+       default:
                dev_err(&i2c->dev, "unsupported device\n");
                return -ENXIO;
        }
 
        dev_dbg(&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;
@@ -406,6 +541,7 @@ static int aw87390_init(struct aw87390 *aw87390, struct i2c_client *i2c, struct
 static int aw87390_i2c_probe(struct i2c_client *i2c)
 {
        struct aw87390 *aw87390;
+       const struct snd_soc_component_driver *priv;
        int ret;
 
        ret = i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C);
@@ -434,16 +570,38 @@ static int aw87390_i2c_probe(struct i2c_client *i2c)
        if (ret)
                return ret;
 
-       ret = devm_snd_soc_register_component(&i2c->dev,
-                               &soc_codec_dev_aw87390, NULL, 0);
+       switch (aw87390->aw_pa->chip_id) {
+       case AW87390_CHIP_ID:
+               ret = devm_snd_soc_register_component(&i2c->dev,
+                                       &soc_codec_dev_aw87390, NULL, 0);
+               break;
+       case AW87391_CHIP_ID:
+               priv = of_device_get_match_data(&i2c->dev);
+               if (!priv)
+                       return dev_err_probe(&i2c->dev, -EINVAL,
+                                            "aw87391 not currently supported\n");
+               ret = devm_snd_soc_register_component(&i2c->dev, priv, NULL, 0);
+               break;
+       default:
+               return -ENXIO;
+       }
+
        if (ret)
                dev_err(&i2c->dev, "failed to register aw87390: %d\n", ret);
 
        return ret;
 }
 
+static const struct of_device_id aw87390_of_match[] = {
+       { .compatible = "awinic,aw87390" },
+       { .compatible = "anbernic,rgds-amp", .data = &soc_codec_dev_anbernic_rgds },
+       {},
+};
+MODULE_DEVICE_TABLE(of, aw87390_of_match);
+
 static const struct i2c_device_id aw87390_i2c_id[] = {
        { AW87390_I2C_NAME },
+       { AW87391_I2C_NAME },
        { }
 };
 MODULE_DEVICE_TABLE(i2c, aw87390_i2c_id);
@@ -451,6 +609,7 @@ MODULE_DEVICE_TABLE(i2c, aw87390_i2c_id);
 static struct i2c_driver aw87390_i2c_driver = {
        .driver = {
                .name = AW87390_I2C_NAME,
+               .of_match_table = of_match_ptr(aw87390_of_match),
        },
        .probe = aw87390_i2c_probe,
        .id_table = aw87390_i2c_id,
index d0d049e65991f922048639bd6ad12532189524a8..f48b207e4bb4471c90127d8fc96f6208809dbcd5 100644 (file)
 #define AW87390_I2C_NAME               "aw87390"
 #define AW87390_ACF_FILE               "aw87390_acf.bin"
 
+#define AW87391_SYSCTRL_REG            (0x01)
+#define AW87391_REG_VER_SEL_LOW                (0 << 6)
+#define AW87391_REG_VER_SEL_NORMAL     (1 << 6)
+#define AW87391_REG_VER_SEL_SUPER      (2 << 6)
+#define AW87391_REG_EN_ADAP            BIT(5)
+#define AW87391_REG_EN_2X              BIT(4)
+#define AW87391_EN_SPK                 BIT(3)
+#define AW87391_EN_PA                  BIT(2)
+#define AW87391_REG_EN_CP              BIT(1)
+#define AW87391_EN_SW                  BIT(0)
+
+#define AW87391_CP_REG                  (0x02)
+#define AW87391_REG_CP_OVP_6_50V       0
+#define AW87391_REG_CP_OVP_6_75V       1
+#define AW87391_REG_CP_OVP_7_00V       2
+#define AW87391_REG_CP_OVP_7_25V       3
+#define AW87391_REG_CP_OVP_7_50V       4
+#define AW87391_REG_CP_OVP_7_75V       5
+#define AW87391_REG_CP_OVP_8_00V       6
+#define AW87391_REG_CP_OVP_8_25V       7
+#define AW87391_REG_CP_OVP_8_50V       8
+
+#define AW87391_PAG_REG                 (0x03)
+#define AW87391_GAIN_12DB              0
+#define AW87391_GAIN_15DB              1
+#define AW87391_GAIN_18DB              2
+#define AW87391_GAIN_21DB              3
+#define AW87391_GAIN_24DB              4
+
+#define AW87391_AGCPO_REG               (0x04)
+#define AW87391_AK1_S_016              (2 << 5)
+#define AW87391_AK1_S_032              (3 << 5)
+#define AW87391_PD_AGC1_PWRDN          BIT(4)
+/* AGC2PO supports values between 500mW (0000) to 1600mW (1011) */
+#define AW87391_AGC2PO_MW(n)           ((n / 100) - 5)
+
+#define AW87391_AGC2PA_REG              (0x05)
+#define AW87391_RK_S_5_12              (0 << 5)
+#define AW87391_RK_S_10_24             (1 << 5)
+#define AW87391_RK_S_20_48             (2 << 5)
+#define AW87391_RK_S_41                        (3 << 5)
+#define AW87391_RK_S_82                        (4 << 5)
+#define AW87391_RK_S_164               (5 << 5)
+#define AW87391_RK_S_328               (6 << 5)
+#define AW87391_RK_S_656               (7 << 5)
+#define AW87391_AK2_S_1_28             (0 << 2)
+#define AW87391_AK2_S_2_56             (1 << 2)
+#define AW87391_AK2_S_10_24            (2 << 2)
+#define AW87391_AK2_S_41               (3 << 2)
+#define AW87391_AK2_S_82               (4 << 2)
+#define AW87391_AK2_S_164              (5 << 2)
+#define AW87391_AK2_S_328              (6 << 2)
+#define AW87391_AK2_S_656              (7 << 2)
+#define AW87391_AK2F_S_10_24           0
+#define AW87391_AK2F_S_20_48           1
+#define AW87391_AK2F_S_41              2
+#define AW87391_AK2F_S_82              3
+
+#define AW87391_SYSST_REG               (0x06)
+#define AW87391_UVLO                   BIT(7)
+#define AW87391_OTN                    BIT(6)
+#define AW87391_OC_FLAG                        BIT(5)
+#define AW87391_ADAP_CP                        BIT(4)
+#define AW87391_STARTOK                        BIT(3)
+#define AW87391_CP_OVP                 BIT(2)
+#define AW87391_PORN                   BIT(1)
+
+#define AW87391_SYSINT_REG              (0x07)
+#define AW87391_UVLOI                  BIT(7)
+#define AW87391_ONTI                   BIT(6)
+#define AW87391_OC_FLAGI               BIT(5)
+#define AW87391_ADAP_CPI               BIT(4)
+#define AW87391_STARTOKI               BIT(3)
+#define AW87391_CP_OVPI                        BIT(2)
+#define AW87391_PORNI                  BIT(1)
+
+#define AW87391_DFT_THGEN0_REG          (0x63)
+#define AW87391_ADAPVTH_01W            (0 << 2)
+#define AW87391_ADAPVTH_02W            (1 << 2)
+#define AW87391_ADAPVTH_03W            (2 << 2)
+#define AW87391_ADAPVTH_04W            (3 << 2)
+
+#define AW87391_I2C_NAME                "aw87391"
+
 #define AW87390_PROFILE_EXT(xname, profile_info, profile_get, profile_set) \
 { \
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
 
 enum aw87390_id {
        AW87390_CHIP_ID = 0x76,
+       AW87391_CHIP_ID = 0xc1,
 };
 
 enum {
@@ -80,6 +165,7 @@ struct aw87390 {
        struct mutex lock;
        struct regmap *regmap;
        struct aw_container *aw_cfg;
+       struct regulator *vdd_reg;
 };
 
 #endif