[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250406-aw8898-v1-2-58a2d554693f@lucaweiss.eu>
Date: Sun, 06 Apr 2025 15:03:16 +0200
From: Luca Weiss <luca@...aweiss.eu>
To: ~postmarketos/upstreaming@...ts.sr.ht, phone-devel@...r.kernel.org,
Liam Girdwood <lgirdwood@...il.com>, Mark Brown <broonie@...nel.org>,
Rob Herring <robh@...nel.org>, Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>, Jaroslav Kysela <perex@...ex.cz>,
Takashi Iwai <tiwai@...e.com>, Bjorn Andersson <andersson@...nel.org>,
Konrad Dybcio <konradybcio@...nel.org>,
Weidong Wang <wangweidong.a@...nic.com>
Cc: linux-sound@...r.kernel.org, devicetree@...r.kernel.org,
linux-kernel@...r.kernel.org, linux-arm-msm@...r.kernel.org,
Luca Weiss <luca@...aweiss.eu>
Subject: [PATCH 2/3] ASoC: codecs: Add aw8898 amplifier driver
Add a driver for the AW8898 Audio Amplifier.
Signed-off-by: Luca Weiss <luca@...aweiss.eu>
---
sound/soc/codecs/Kconfig | 10 +
sound/soc/codecs/Makefile | 1 +
sound/soc/codecs/snd-soc-aw8898.c | 583 ++++++++++++++++++++++++++++++++++++++
3 files changed, 594 insertions(+)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 40bb7a1d44bcfa4c386f2eba0f475efeeedf1bdb..8351853d3c21522b91639ddfe862613e2cde5be3 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -61,6 +61,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_AW88166
imply SND_SOC_AW88261
imply SND_SOC_AW88399
+ imply SND_SOC_AW8898
imply SND_SOC_BT_SCO
imply SND_SOC_BD28623
imply SND_SOC_CHV3_CODEC
@@ -739,6 +740,15 @@ config SND_SOC_AW88399
digital Smart K audio amplifier and SKTune speaker
protection algorithms.
+config SND_SOC_AW8898
+ tristate "Awinic AW8898 Audio Amplifier"
+ depends on I2C
+ select REGMAP_I2C
+ select GPIOLIB
+ help
+ Enable support for the Awinic AW8898 audio amplifier with I2C
+ control.
+
config SND_SOC_BD28623
tristate "ROHM BD28623 CODEC"
help
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 10f726066b6c63736a4f533cf992c8e55a02036d..04691deb218262e2352d21d8bb231613ad1655d7 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -474,6 +474,7 @@ obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o
obj-$(CONFIG_SND_SOC_AW88166) +=snd-soc-aw88166.o
obj-$(CONFIG_SND_SOC_AW88261) +=snd-soc-aw88261.o
obj-$(CONFIG_SND_SOC_AW88399) += snd-soc-aw88399.o
+obj-$(CONFIG_SND_SOC_AW8898) += snd-soc-aw8898.o
obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o
obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o
obj-$(CONFIG_SND_SOC_CHV3_CODEC) += snd-soc-chv3-codec.o
diff --git a/sound/soc/codecs/snd-soc-aw8898.c b/sound/soc/codecs/snd-soc-aw8898.c
new file mode 100644
index 0000000000000000000000000000000000000000..204c39709f17e904f656cb6a945e6dfd969d1569
--- /dev/null
+++ b/sound/soc/codecs/snd-soc-aw8898.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2018 AWINIC Technology CO., LTD
+ * Author: Nick Li <liweilei@...nic.com.cn>
+ *
+ * Copyright (c) 2025 Luca Weiss <luca@...aweiss.eu>
+ */
+
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+/* Chip ID */
+#define AW8898_ID 0x00
+#define AW8898_CHIP_ID 0x1702
+
+/* System Status */
+#define AW8898_SYSST 0x01
+#define AW8898_SYSST_PLLS GENMASK(0, 0)
+
+/* System Control */
+#define AW8898_SYSCTRL 0x04
+#define AW8898_SYSCTRL_MODE_MASK GENMASK(7, 7)
+#define AW8898_SYSCTRL_RCV_MODE (1)
+#define AW8898_SYSCTRL_SPK_MODE (0)
+#define AW8898_SYSCTRL_PW_MASK GENMASK(0, 0)
+#define AW8898_SYSCTRL_PW_PDN (1)
+#define AW8898_SYSCTRL_PW_ACTIVE (0)
+
+/* I2S Interface Control */
+#define AW8898_I2SCTRL 0x05
+#define AW8898_I2SCTRL_FMS_MASK GENMASK(7, 6)
+#define AW8898_I2SCTRL_FMS_32BIT (3)
+#define AW8898_I2SCTRL_FMS_24BIT (2)
+#define AW8898_I2SCTRL_FMS_20BIT (1)
+#define AW8898_I2SCTRL_FMS_16BIT (0)
+#define AW8898_I2SCTRL_SR_MASK GENMASK(3, 0)
+#define AW8898_I2SCTRL_SR_48K (8)
+#define AW8898_I2SCTRL_SR_44P1K (7)
+#define AW8898_I2SCTRL_SR_32K (6)
+#define AW8898_I2SCTRL_SR_16K (3)
+#define AW8898_I2SCTRL_SR_8K (0)
+
+/* PWM Control */
+#define AW8898_PWMCTRL 0x08
+#define AW8898_PWMCTRL_HMUTE_MASK GENMASK(0, 0)
+#define AW8898_PWMCTRL_HMUTE_ENABLE (1)
+#define AW8898_PWMCTRL_HMUTE_DISABLE (0)
+
+/* Hardware AGC Configuration 7 */
+#define AW8898_HAGCCFG7 0x0f
+#define AW8898_HAGCCFG7_VOL_REG_SHIFT (8) /* bits 15-8 */
+#define AW8898_VOLUME_MAX (255)
+#define AW8898_VOLUME_MIN (0)
+
+#define AW8898_MAX_REGISTER 0xff
+
+#define AW8898_CFG_NAME "aw8898_cfg.bin"
+
+#define AW8898_NUM_SUPPLIES 3
+static const char *aw8898_supply_names[AW8898_NUM_SUPPLIES] = {
+ "vdd", /* Battery power */
+ "vddio", /* Digital IO power */
+ "dvdd", /* Digital power */
+};
+
+static const char * const aw8898_dev_mode_text[] = {
+ "Speaker",
+ "Receiver",
+};
+
+enum aw8898_mode {
+ AW8898_SPEAKER,
+ AW8898_RECEIVER,
+};
+
+static const struct soc_enum aw8898_dev_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aw8898_dev_mode_text), aw8898_dev_mode_text);
+
+struct aw8898 {
+ struct snd_soc_component *component;
+ struct regmap *regmap;
+ struct i2c_client *client;
+ struct mutex cfg_lock;
+ struct gpio_desc *reset;
+ struct regulator_bulk_data supplies[AW8898_NUM_SUPPLIES];
+ enum aw8898_mode dev_mode;
+ bool cfg_loaded;
+};
+
+struct aw8898_cfg {
+ int len;
+ struct {
+ __le16 addr;
+ __le16 val;
+ } __packed data[];
+};
+
+static void aw8898_set_mute(struct aw8898 *aw8898, bool mute)
+{
+ unsigned int val = AW8898_PWMCTRL_HMUTE_DISABLE;
+
+ if (mute)
+ val = AW8898_PWMCTRL_HMUTE_ENABLE;
+
+ regmap_update_bits(aw8898->regmap, AW8898_PWMCTRL,
+ AW8898_PWMCTRL_HMUTE_MASK,
+ FIELD_PREP(AW8898_PWMCTRL_HMUTE_MASK, val));
+}
+
+static void aw8898_set_power(struct aw8898 *aw8898, bool on)
+{
+ unsigned int val = AW8898_SYSCTRL_PW_PDN;
+
+ if (on)
+ val = AW8898_SYSCTRL_PW_ACTIVE;
+
+ regmap_update_bits(aw8898->regmap, AW8898_SYSCTRL,
+ AW8898_SYSCTRL_PW_MASK,
+ FIELD_PREP(AW8898_SYSCTRL_PW_MASK, val));
+}
+
+static void aw8898_update_dev_mode(struct aw8898 *aw8898)
+{
+ unsigned int mode = AW8898_SYSCTRL_SPK_MODE;
+
+ if (aw8898->dev_mode == AW8898_RECEIVER)
+ mode = AW8898_SYSCTRL_RCV_MODE;
+
+ regmap_update_bits(aw8898->regmap, AW8898_SYSCTRL,
+ AW8898_SYSCTRL_MODE_MASK,
+ FIELD_PREP(AW8898_SYSCTRL_MODE_MASK, mode));
+}
+
+static void aw8898_start(struct aw8898 *aw8898)
+{
+ unsigned int val;
+ int err;
+
+ aw8898_set_power(aw8898, true);
+
+ fsleep(2000);
+
+ err = regmap_read_poll_timeout(aw8898->regmap, AW8898_SYSST,
+ val, val & AW8898_SYSST_PLLS,
+ 2000, 1 * USEC_PER_SEC);
+ if (err) {
+ dev_err(&aw8898->client->dev, "iis signal check error: %d\n", err);
+ aw8898_set_power(aw8898, false);
+ return;
+ }
+
+ aw8898_set_mute(aw8898, false);
+}
+
+static void aw8898_stop(struct aw8898 *aw8898)
+{
+ aw8898_set_mute(aw8898, true);
+ aw8898_set_power(aw8898, false);
+}
+
+static void aw8898_cfg_write(struct aw8898 *aw8898,
+ struct aw8898_cfg *aw8898_cfg)
+{
+ for (int i = 0; i < aw8898_cfg->len; i++) {
+ unsigned int addr = __le32_to_cpu(aw8898_cfg->data[i].addr);
+ unsigned int val = __le32_to_cpu(aw8898_cfg->data[i].val);
+
+ dev_dbg(&aw8898->client->dev, "cfg reg = 0x%04x, val = 0x%04x\n", addr, val);
+ regmap_write(aw8898->regmap, addr, val);
+ }
+}
+
+static void aw8898_fw_loaded(const struct firmware *fw, void *context)
+{
+ struct aw8898 *aw8898 = context;
+ struct aw8898_cfg *aw8898_cfg;
+
+ if (!fw) {
+ dev_err(&aw8898->client->dev, "Failed to load firmware\n");
+ return;
+ }
+
+ dev_dbg(&aw8898->client->dev, "Loaded %s - size: %zu\n", AW8898_CFG_NAME, fw->size);
+
+ if (fw->size % 4 != 0) {
+ dev_err(&aw8898->client->dev, "Invalid firmware size %zu\n", fw->size);
+ release_firmware(fw);
+ return;
+ }
+
+ aw8898_cfg = kzalloc(sizeof(int) + fw->size, GFP_KERNEL);
+ if (!aw8898_cfg) {
+ release_firmware(fw);
+ return;
+ }
+
+ aw8898_cfg->len = fw->size / 4;
+ memcpy(aw8898_cfg->data, fw->data, fw->size);
+
+ release_firmware(fw);
+
+ aw8898_cfg_write(aw8898, aw8898_cfg);
+
+ kfree(aw8898_cfg);
+
+ aw8898->cfg_loaded = true;
+
+ aw8898_update_dev_mode(aw8898);
+ aw8898_start(aw8898);
+}
+
+static void aw8898_cold_start(struct aw8898 *aw8898)
+{
+ int err;
+
+ err = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+ AW8898_CFG_NAME, &aw8898->client->dev, GFP_KERNEL,
+ aw8898, aw8898_fw_loaded);
+ if (err)
+ dev_err(&aw8898->client->dev, "Firmware load request failed: %d\n", err);
+}
+
+static int aw8898_dev_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct aw8898 *aw8898 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.enumerated.item[0] = aw8898->dev_mode;
+
+ return 0;
+}
+
+static int aw8898_dev_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct aw8898 *aw8898 = snd_soc_component_get_drvdata(component);
+
+ if (aw8898->dev_mode == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ aw8898->dev_mode = ucontrol->value.enumerated.item[0];
+
+ aw8898_update_dev_mode(aw8898);
+
+ return 1;
+}
+
+/*
+ * -127.5 dB min, 0.5 dB steps, no mute
+ * Note: The official datasheet claims to be able to attenuate between 0 dB and
+ * -96 dB with 0.5 dB/step, but the register values are 0-255 so this doesn't
+ * really line up. It's a best guess.
+ */
+static const DECLARE_TLV_DB_SCALE(vol_tlv, -12750, 50, 0);
+
+static struct snd_kcontrol_new aw8898_controls[] = {
+ SOC_ENUM_EXT("Amp Mode", aw8898_dev_mode_enum,
+ aw8898_dev_mode_get, aw8898_dev_mode_put),
+ SOC_SINGLE_RANGE_TLV("RX Volume", AW8898_HAGCCFG7,
+ AW8898_HAGCCFG7_VOL_REG_SHIFT,
+ AW8898_VOLUME_MIN, AW8898_VOLUME_MAX, 1, vol_tlv),
+};
+
+static int aw8898_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct aw8898 *aw8898 = snd_soc_component_get_drvdata(dai->component);
+
+ aw8898_set_power(aw8898, true);
+
+ return 0;
+}
+
+static int aw8898_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK)
+ != SND_SOC_DAIFMT_CBC_CFC) {
+ dev_err(component->dev, "Invalid codec master mode: %d\n",
+ fmt & SND_SOC_DAIFMT_MASTER_MASK);
+ return -EINVAL;
+ }
+ break;
+ default:
+ dev_err(component->dev, "Unsupported DAI format %d\n",
+ fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int aw8898_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct aw8898 *aw8898 = snd_soc_component_get_drvdata(dai->component);
+ unsigned int val;
+
+ switch (params_rate(params)) {
+ case 8000:
+ val = AW8898_I2SCTRL_SR_8K;
+ break;
+ case 16000:
+ val = AW8898_I2SCTRL_SR_16K;
+ break;
+ case 32000:
+ val = AW8898_I2SCTRL_SR_32K;
+ break;
+ case 44100:
+ val = AW8898_I2SCTRL_SR_44P1K;
+ break;
+ case 48000:
+ val = AW8898_I2SCTRL_SR_48K;
+ break;
+ default:
+ dev_err(component->dev, "Unsupported sample rate: %d\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ regmap_update_bits(aw8898->regmap, AW8898_I2SCTRL,
+ AW8898_I2SCTRL_SR_MASK,
+ FIELD_PREP(AW8898_I2SCTRL_SR_MASK, val));
+
+ switch (params_width(params)) {
+ case 16:
+ val = AW8898_I2SCTRL_FMS_16BIT;
+ break;
+ case 20:
+ val = AW8898_I2SCTRL_FMS_20BIT;
+ break;
+ case 24:
+ val = AW8898_I2SCTRL_FMS_24BIT;
+ break;
+ case 32:
+ val = AW8898_I2SCTRL_FMS_32BIT;
+ break;
+ default:
+ dev_err(component->dev, "Unsupported sample size: %d\n",
+ params_width(params));
+ return -EINVAL;
+ }
+
+ regmap_update_bits(aw8898->regmap, AW8898_I2SCTRL,
+ AW8898_I2SCTRL_FMS_MASK,
+ FIELD_PREP(AW8898_I2SCTRL_FMS_MASK, val));
+
+ return 0;
+}
+
+static int aw8898_mute(struct snd_soc_dai *dai, int mute, int stream)
+{
+ struct aw8898 *aw8898 = snd_soc_component_get_drvdata(dai->component);
+
+ mutex_lock(&aw8898->cfg_lock);
+
+ if (mute) {
+ aw8898_stop(aw8898);
+ } else {
+ if (!aw8898->cfg_loaded) {
+ aw8898_cold_start(aw8898);
+ } else {
+ aw8898_update_dev_mode(aw8898);
+ aw8898_start(aw8898);
+ }
+ }
+
+ mutex_unlock(&aw8898->cfg_lock);
+
+ return 0;
+}
+
+static void aw8898_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct aw8898 *aw8898 = snd_soc_component_get_drvdata(dai->component);
+
+ aw8898_set_power(aw8898, false);
+}
+
+static const struct snd_soc_dai_ops aw8898_dai_ops = {
+ .startup = aw8898_startup,
+ .set_fmt = aw8898_set_fmt,
+ .hw_params = aw8898_hw_params,
+ .mute_stream = aw8898_mute,
+ .shutdown = aw8898_shutdown,
+};
+
+#define AW8898_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver aw8898_dai[] = {
+ {
+ .name = "aw8898-amplifier",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = AW8898_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = AW8898_FORMATS,
+ },
+ .ops = &aw8898_dai_ops,
+ .symmetric_rate = 1,
+ .symmetric_channels = 1,
+ .symmetric_sample_bits = 1,
+ },
+};
+
+static int aw8898_component_probe(struct snd_soc_component *component)
+{
+ struct aw8898 *aw8898 = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ aw8898->component = component;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(aw8898->supplies),
+ aw8898->supplies);
+ if (ret) {
+ dev_err(component->dev, "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ snd_soc_add_component_controls(component, aw8898_controls,
+ ARRAY_SIZE(aw8898_controls));
+
+ return 0;
+}
+
+static void aw8898_component_remove(struct snd_soc_component *component)
+{
+ struct aw8898 *aw8898 = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(aw8898->supplies),
+ aw8898->supplies);
+ if (ret)
+ dev_err(component->dev, "Failed to disable supplies: %d\n",
+ ret);
+};
+
+static const struct snd_soc_component_driver soc_component_dev_aw8898 = {
+ .probe = aw8898_component_probe,
+ .remove = aw8898_component_remove,
+};
+
+static const struct regmap_config aw8898_regmap = {
+ .reg_bits = 8,
+ .val_bits = 16,
+
+ .max_register = AW8898_MAX_REGISTER,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static void aw8898_reset(struct aw8898 *aw8898)
+{
+ gpiod_set_value_cansleep(aw8898->reset, 1);
+ fsleep(1000);
+ gpiod_set_value_cansleep(aw8898->reset, 0);
+ fsleep(1000);
+}
+
+static int aw8898_check_chipid(struct aw8898 *aw8898)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(aw8898->regmap, AW8898_ID, ®);
+ if (ret < 0) {
+ dev_err(&aw8898->client->dev,
+ "Failed to read register AW8898_ID: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(&aw8898->client->dev, "Read chip ID 0x%x\n", reg);
+
+ if (reg != AW8898_CHIP_ID) {
+ dev_err(&aw8898->client->dev, "Unexpected chip ID: 0x%x\n",
+ reg);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int aw8898_probe(struct i2c_client *client)
+{
+ struct aw8898 *aw8898;
+ int ret;
+
+ aw8898 = devm_kzalloc(&client->dev, sizeof(*aw8898), GFP_KERNEL);
+ if (!aw8898)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, aw8898);
+ aw8898->client = client;
+
+ aw8898->regmap = devm_regmap_init_i2c(client, &aw8898_regmap);
+ if (IS_ERR(aw8898->regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(aw8898->regmap),
+ "Failed to allocate register map\n");
+
+ ret = devm_mutex_init(&client->dev, &aw8898->cfg_lock);
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to init mutex\n");
+
+ for (int i = 0; i < ARRAY_SIZE(aw8898->supplies); i++)
+ aw8898->supplies[i].supply = aw8898_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&client->dev, ARRAY_SIZE(aw8898->supplies),
+ aw8898->supplies);
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to get regulators\n");
+
+ aw8898->reset = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(aw8898->reset))
+ return dev_err_probe(&client->dev, PTR_ERR(aw8898->reset),
+ "Failed to get reset GPIO\n");
+
+ aw8898_reset(aw8898);
+
+ ret = aw8898_check_chipid(aw8898);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Chip ID check failed\n");
+
+ dev_set_drvdata(&client->dev, aw8898);
+
+ ret = devm_snd_soc_register_component(&client->dev, &soc_component_dev_aw8898,
+ aw8898_dai, ARRAY_SIZE(aw8898_dai));
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret, "Failed to register component\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id aw8898_id[] = {
+ { "aw8898" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, aw8898_id);
+
+static const struct of_device_id aw8898_of_match[] = {
+ { .compatible = "awinic,aw8898" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, aw8898_of_match);
+
+static struct i2c_driver aw8898_driver = {
+ .driver = {
+ .name = "aw8898",
+ .of_match_table = of_match_ptr(aw8898_of_match),
+ },
+ .probe = aw8898_probe,
+ .id_table = aw8898_id,
+};
+
+module_i2c_driver(aw8898_driver);
+
+MODULE_DESCRIPTION("AW8898 Audio amplifier driver");
+MODULE_LICENSE("GPL");
--
2.49.0
Powered by blists - more mailing lists